1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SVGSMILElement.h"
28
29#include "CSSPropertyNames.h"
30#include "Document.h"
31#include "Event.h"
32#include "EventListener.h"
33#include "EventNames.h"
34#include "EventSender.h"
35#include "FloatConversion.h"
36#include "FrameView.h"
37#include "SMILTimeContainer.h"
38#include "SVGDocumentExtensions.h"
39#include "SVGNames.h"
40#include "SVGParserUtilities.h"
41#include "SVGSVGElement.h"
42#include "SVGURIReference.h"
43#include "SVGUseElement.h"
44#include "XLinkNames.h"
45#include <wtf/IsoMallocInlines.h>
46#include <wtf/MathExtras.h>
47#include <wtf/StdLibExtras.h>
48#include <wtf/Vector.h>
49
50namespace WebCore {
51
52WTF_MAKE_ISO_ALLOCATED_IMPL(SVGSMILElement);
53
54static SMILEventSender& smilBeginEventSender()
55{
56 static NeverDestroyed<SMILEventSender> sender(eventNames().beginEventEvent);
57 return sender;
58}
59
60static SMILEventSender& smilEndEventSender()
61{
62 static NeverDestroyed<SMILEventSender> sender(eventNames().endEventEvent);
63 return sender;
64}
65
66// This is used for duration type time values that can't be negative.
67static const double invalidCachedTime = -1.;
68
69class ConditionEventListener final : public EventListener {
70public:
71 static Ref<ConditionEventListener> create(SVGSMILElement* animation, SVGSMILElement::Condition* condition)
72 {
73 return adoptRef(*new ConditionEventListener(animation, condition));
74 }
75
76 static const ConditionEventListener* cast(const EventListener* listener)
77 {
78 return listener->type() == ConditionEventListenerType
79 ? static_cast<const ConditionEventListener*>(listener)
80 : nullptr;
81 }
82
83 bool operator==(const EventListener& other) const final;
84
85 void disconnectAnimation()
86 {
87 m_animation = nullptr;
88 }
89
90private:
91 ConditionEventListener(SVGSMILElement* animation, SVGSMILElement::Condition* condition)
92 : EventListener(ConditionEventListenerType)
93 , m_animation(animation)
94 , m_condition(condition)
95 {
96 }
97
98 void handleEvent(ScriptExecutionContext&, Event&) final;
99
100 SVGSMILElement* m_animation;
101 SVGSMILElement::Condition* m_condition;
102};
103
104bool ConditionEventListener::operator==(const EventListener& listener) const
105{
106 if (const ConditionEventListener* conditionEventListener = ConditionEventListener::cast(&listener))
107 return m_animation == conditionEventListener->m_animation && m_condition == conditionEventListener->m_condition;
108 return false;
109}
110
111void ConditionEventListener::handleEvent(ScriptExecutionContext&, Event&)
112{
113 if (!m_animation)
114 return;
115 m_animation->handleConditionEvent(m_condition);
116}
117
118SVGSMILElement::Condition::Condition(Type type, BeginOrEnd beginOrEnd, const String& baseID, const String& name, SMILTime offset, int repeats)
119 : m_type(type)
120 , m_beginOrEnd(beginOrEnd)
121 , m_baseID(baseID)
122 , m_name(name)
123 , m_offset(offset)
124 , m_repeats(repeats)
125{
126}
127
128SVGSMILElement::SVGSMILElement(const QualifiedName& tagName, Document& doc)
129 : SVGElement(tagName, doc)
130 , m_attributeName(anyQName())
131 , m_targetElement(nullptr)
132 , m_conditionsConnected(false)
133 , m_hasEndEventConditions(false)
134 , m_isWaitingForFirstInterval(true)
135 , m_intervalBegin(SMILTime::unresolved())
136 , m_intervalEnd(SMILTime::unresolved())
137 , m_previousIntervalBegin(SMILTime::unresolved())
138 , m_activeState(Inactive)
139 , m_lastPercent(0)
140 , m_lastRepeat(0)
141 , m_nextProgressTime(0)
142 , m_documentOrderIndex(0)
143 , m_cachedDur(invalidCachedTime)
144 , m_cachedRepeatDur(invalidCachedTime)
145 , m_cachedRepeatCount(invalidCachedTime)
146 , m_cachedMin(invalidCachedTime)
147 , m_cachedMax(invalidCachedTime)
148{
149 resolveFirstInterval();
150}
151
152SVGSMILElement::~SVGSMILElement()
153{
154 clearResourceReferences();
155 smilBeginEventSender().cancelEvent(*this);
156 smilEndEventSender().cancelEvent(*this);
157 disconnectConditions();
158 if (m_timeContainer && m_targetElement && hasValidAttributeName())
159 m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
160}
161
162void SVGSMILElement::clearResourceReferences()
163{
164 document().accessSVGExtensions().removeAllTargetReferencesForElement(*this);
165}
166
167void SVGSMILElement::clearTarget()
168{
169 setTargetElement(nullptr);
170}
171
172void SVGSMILElement::buildPendingResource()
173{
174 clearResourceReferences();
175
176 if (!isConnected()) {
177 // Reset the target element if we are no longer in the document.
178 setTargetElement(nullptr);
179 return;
180 }
181
182 String id;
183 RefPtr<Element> target;
184 auto& href = getAttribute(XLinkNames::hrefAttr);
185 if (href.isEmpty())
186 target = parentElement();
187 else {
188 auto result = SVGURIReference::targetElementFromIRIString(href.string(), treeScope());
189 target = WTFMove(result.element);
190 id = WTFMove(result.identifier);
191 }
192 SVGElement* svgTarget = is<SVGElement>(target) ? downcast<SVGElement>(target.get()) : nullptr;
193
194 if (svgTarget && !svgTarget->isConnected())
195 svgTarget = nullptr;
196
197 if (svgTarget != targetElement())
198 setTargetElement(svgTarget);
199
200 if (!svgTarget) {
201 // Do not register as pending if we are already pending this resource.
202 if (document().accessSVGExtensions().isPendingResource(*this, id))
203 return;
204
205 if (!id.isEmpty()) {
206 document().accessSVGExtensions().addPendingResource(id, *this);
207 ASSERT(hasPendingResources());
208 }
209 } else {
210 // Register us with the target in the dependencies map. Any change of hrefElement
211 // that leads to relayout/repainting now informs us, so we can react to it.
212 document().accessSVGExtensions().addElementReferencingTarget(*this, *svgTarget);
213 }
214}
215
216inline QualifiedName SVGSMILElement::constructAttributeName() const
217{
218 auto parseResult = Document::parseQualifiedName(attributeWithoutSynchronization(SVGNames::attributeNameAttr));
219 if (parseResult.hasException())
220 return anyQName();
221
222 AtomicString prefix, localName;
223 std::tie(prefix, localName) = parseResult.releaseReturnValue();
224
225 if (prefix.isNull())
226 return { nullAtom(), localName, nullAtom() };
227
228 auto namespaceURI = lookupNamespaceURI(prefix);
229 if (namespaceURI.isEmpty())
230 return anyQName();
231
232 return { nullAtom(), localName, namespaceURI };
233}
234
235inline void SVGSMILElement::updateAttributeName()
236{
237 setAttributeName(constructAttributeName());
238}
239
240static inline void clearTimesWithDynamicOrigins(Vector<SMILTimeWithOrigin>& timeList)
241{
242 timeList.removeAllMatching([] (const SMILTimeWithOrigin& time) {
243 return time.originIsScript();
244 });
245}
246
247void SVGSMILElement::reset()
248{
249 clearAnimatedType(m_targetElement);
250
251 m_activeState = Inactive;
252 m_isWaitingForFirstInterval = true;
253 m_intervalBegin = SMILTime::unresolved();
254 m_intervalEnd = SMILTime::unresolved();
255 m_previousIntervalBegin = SMILTime::unresolved();
256 m_lastPercent = 0;
257 m_lastRepeat = 0;
258 m_nextProgressTime = 0;
259 resolveFirstInterval();
260}
261
262Node::InsertedIntoAncestorResult SVGSMILElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
263{
264 SVGElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
265 if (!insertionType.connectedToDocument)
266 return InsertedIntoAncestorResult::Done;
267
268 // Verify we are not in <use> instance tree.
269 ASSERT(!isInShadowTree() || !is<SVGUseElement>(shadowHost()));
270
271 updateAttributeName();
272
273 auto owner = makeRefPtr(ownerSVGElement());
274 if (!owner)
275 return InsertedIntoAncestorResult::Done;
276
277 m_timeContainer = &owner->timeContainer();
278 m_timeContainer->setDocumentOrderIndexesDirty();
279
280 // "If no attribute is present, the default begin value (an offset-value of 0) must be evaluated."
281 if (!hasAttributeWithoutSynchronization(SVGNames::beginAttr))
282 m_beginTimes.append(SMILTimeWithOrigin());
283
284 if (m_isWaitingForFirstInterval)
285 resolveFirstInterval();
286
287 if (m_timeContainer)
288 m_timeContainer->notifyIntervalsChanged();
289
290 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
291}
292
293void SVGSMILElement::didFinishInsertingNode()
294{
295 buildPendingResource();
296}
297
298void SVGSMILElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
299{
300 if (removalType.disconnectedFromDocument) {
301 clearResourceReferences();
302 disconnectConditions();
303 setTargetElement(nullptr);
304 setAttributeName(anyQName());
305 animationAttributeChanged();
306 m_timeContainer = nullptr;
307 }
308
309 SVGElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
310}
311
312bool SVGSMILElement::hasValidAttributeName() const
313{
314 return attributeName() != anyQName();
315}
316
317SMILTime SVGSMILElement::parseOffsetValue(const String& data)
318{
319 bool ok;
320 double result = 0;
321 String parse = data.stripWhiteSpace();
322 if (parse.endsWith('h'))
323 result = parse.left(parse.length() - 1).toDouble(&ok) * 60 * 60;
324 else if (parse.endsWith("min"))
325 result = parse.left(parse.length() - 3).toDouble(&ok) * 60;
326 else if (parse.endsWith("ms"))
327 result = parse.left(parse.length() - 2).toDouble(&ok) / 1000;
328 else if (parse.endsWith('s'))
329 result = parse.left(parse.length() - 1).toDouble(&ok);
330 else
331 result = parse.toDouble(&ok);
332 if (!ok || !SMILTime(result).isFinite())
333 return SMILTime::unresolved();
334 return result;
335}
336
337SMILTime SVGSMILElement::parseClockValue(const String& data)
338{
339 if (data.isNull())
340 return SMILTime::unresolved();
341
342 String parse = data.stripWhiteSpace();
343
344 static NeverDestroyed<const AtomicString> indefiniteValue("indefinite", AtomicString::ConstructFromLiteral);
345 if (parse == indefiniteValue)
346 return SMILTime::indefinite();
347
348 double result = 0;
349 bool ok;
350 size_t doublePointOne = parse.find(':');
351 size_t doublePointTwo = parse.find(':', doublePointOne + 1);
352 if (doublePointOne == 2 && doublePointTwo == 5 && parse.length() >= 8) {
353 result += parse.substring(0, 2).toUIntStrict(&ok) * 60 * 60;
354 if (!ok)
355 return SMILTime::unresolved();
356 result += parse.substring(3, 2).toUIntStrict(&ok) * 60;
357 if (!ok)
358 return SMILTime::unresolved();
359 result += parse.substring(6).toDouble(&ok);
360 } else if (doublePointOne == 2 && doublePointTwo == notFound && parse.length() >= 5) {
361 result += parse.substring(0, 2).toUIntStrict(&ok) * 60;
362 if (!ok)
363 return SMILTime::unresolved();
364 result += parse.substring(3).toDouble(&ok);
365 } else
366 return parseOffsetValue(parse);
367
368 if (!ok || !SMILTime(result).isFinite())
369 return SMILTime::unresolved();
370 return result;
371}
372
373static void sortTimeList(Vector<SMILTimeWithOrigin>& timeList)
374{
375 std::sort(timeList.begin(), timeList.end());
376}
377
378bool SVGSMILElement::parseCondition(const String& value, BeginOrEnd beginOrEnd)
379{
380 String parseString = value.stripWhiteSpace();
381
382 double sign = 1.;
383 bool ok;
384 size_t pos = parseString.find('+');
385 if (pos == notFound) {
386 pos = parseString.find('-');
387 if (pos != notFound)
388 sign = -1.;
389 }
390 String conditionString;
391 SMILTime offset = 0;
392 if (pos == notFound)
393 conditionString = parseString;
394 else {
395 conditionString = parseString.left(pos).stripWhiteSpace();
396 String offsetString = parseString.substring(pos + 1).stripWhiteSpace();
397 offset = parseOffsetValue(offsetString);
398 if (offset.isUnresolved())
399 return false;
400 offset = offset * sign;
401 }
402 if (conditionString.isEmpty())
403 return false;
404 pos = conditionString.find('.');
405
406 String baseID;
407 String nameString;
408 if (pos == notFound)
409 nameString = conditionString;
410 else {
411 baseID = conditionString.left(pos);
412 nameString = conditionString.substring(pos + 1);
413 }
414 if (nameString.isEmpty())
415 return false;
416
417 Condition::Type type;
418 int repeats = -1;
419 if (nameString.startsWith("repeat(") && nameString.endsWith(')')) {
420 // FIXME: For repeat events we just need to add the data carrying TimeEvent class and
421 // fire the events at appropiate times.
422 repeats = nameString.substring(7, nameString.length() - 8).toUIntStrict(&ok);
423 if (!ok)
424 return false;
425 nameString = "repeat";
426 type = Condition::EventBase;
427 } else if (nameString == "begin" || nameString == "end") {
428 if (baseID.isEmpty())
429 return false;
430 type = Condition::Syncbase;
431 } else if (nameString.startsWith("accesskey(")) {
432 // FIXME: accesskey() support.
433 type = Condition::AccessKey;
434 } else
435 type = Condition::EventBase;
436
437 m_conditions.append(Condition(type, beginOrEnd, baseID, nameString, offset, repeats));
438
439 if (type == Condition::EventBase && beginOrEnd == End)
440 m_hasEndEventConditions = true;
441
442 return true;
443}
444
445void SVGSMILElement::parseBeginOrEnd(const String& parseString, BeginOrEnd beginOrEnd)
446{
447 Vector<SMILTimeWithOrigin>& timeList = beginOrEnd == Begin ? m_beginTimes : m_endTimes;
448 if (beginOrEnd == End)
449 m_hasEndEventConditions = false;
450 HashSet<double> existing;
451 for (auto& time : timeList)
452 existing.add(time.time().value());
453 for (auto& string : parseString.split(';')) {
454 SMILTime value = parseClockValue(string);
455 if (value.isUnresolved())
456 parseCondition(string, beginOrEnd);
457 else if (!existing.contains(value.value()))
458 timeList.append(SMILTimeWithOrigin(value, SMILTimeWithOrigin::ParserOrigin));
459 }
460 sortTimeList(timeList);
461}
462
463bool SVGSMILElement::isSupportedAttribute(const QualifiedName& attrName)
464{
465 static const auto supportedAttributes = makeNeverDestroyed(HashSet<QualifiedName> {
466 SVGNames::beginAttr,
467 SVGNames::endAttr,
468 SVGNames::durAttr,
469 SVGNames::repeatDurAttr,
470 SVGNames::repeatCountAttr,
471 SVGNames::minAttr,
472 SVGNames::maxAttr,
473 SVGNames::attributeNameAttr,
474 SVGNames::hrefAttr,
475 XLinkNames::hrefAttr,
476 });
477 return supportedAttributes.get().contains<SVGAttributeHashTranslator>(attrName);
478}
479
480void SVGSMILElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
481{
482 if (name == SVGNames::beginAttr) {
483 if (!m_conditions.isEmpty()) {
484 disconnectConditions();
485 m_conditions.clear();
486 parseBeginOrEnd(attributeWithoutSynchronization(SVGNames::endAttr), End);
487 }
488 parseBeginOrEnd(value.string(), Begin);
489 if (isConnected())
490 connectConditions();
491 } else if (name == SVGNames::endAttr) {
492 if (!m_conditions.isEmpty()) {
493 disconnectConditions();
494 m_conditions.clear();
495 parseBeginOrEnd(attributeWithoutSynchronization(SVGNames::beginAttr), Begin);
496 }
497 parseBeginOrEnd(value.string(), End);
498 if (isConnected())
499 connectConditions();
500 } else if (name == SVGNames::onendAttr)
501 setAttributeEventListener(eventNames().endEventEvent, name, value);
502 else if (name == SVGNames::onbeginAttr)
503 setAttributeEventListener(eventNames().beginEventEvent, name, value);
504 else
505 SVGElement::parseAttribute(name, value);
506}
507
508void SVGSMILElement::svgAttributeChanged(const QualifiedName& attrName)
509{
510 if (!isSupportedAttribute(attrName)) {
511 SVGElement::svgAttributeChanged(attrName);
512 return;
513 }
514
515 if (attrName == SVGNames::durAttr)
516 m_cachedDur = invalidCachedTime;
517 else if (attrName == SVGNames::repeatDurAttr)
518 m_cachedRepeatDur = invalidCachedTime;
519 else if (attrName == SVGNames::repeatCountAttr)
520 m_cachedRepeatCount = invalidCachedTime;
521 else if (attrName == SVGNames::minAttr)
522 m_cachedMin = invalidCachedTime;
523 else if (attrName == SVGNames::maxAttr)
524 m_cachedMax = invalidCachedTime;
525 else if (attrName == SVGNames::attributeNameAttr)
526 updateAttributeName();
527 else if (attrName.matches(SVGNames::hrefAttr) || attrName.matches(XLinkNames::hrefAttr)) {
528 InstanceInvalidationGuard guard(*this);
529 buildPendingResource();
530 } else if (isConnected()) {
531 if (attrName == SVGNames::beginAttr)
532 beginListChanged(elapsed());
533 else if (attrName == SVGNames::endAttr)
534 endListChanged(elapsed());
535 }
536
537 animationAttributeChanged();
538}
539
540inline Element* SVGSMILElement::eventBaseFor(const Condition& condition)
541{
542 return condition.m_baseID.isEmpty() ? targetElement() : treeScope().getElementById(condition.m_baseID);
543}
544
545void SVGSMILElement::connectConditions()
546{
547 if (m_conditionsConnected)
548 disconnectConditions();
549 m_conditionsConnected = true;
550 for (auto& condition : m_conditions) {
551 if (condition.m_type == Condition::EventBase) {
552 ASSERT(!condition.m_syncbase);
553 auto eventBase = makeRefPtr(eventBaseFor(condition));
554 if (!eventBase)
555 continue;
556 ASSERT(!condition.m_eventListener);
557 condition.m_eventListener = ConditionEventListener::create(this, &condition);
558 eventBase->addEventListener(condition.m_name, *condition.m_eventListener, false);
559 } else if (condition.m_type == Condition::Syncbase) {
560 ASSERT(!condition.m_baseID.isEmpty());
561 condition.m_syncbase = treeScope().getElementById(condition.m_baseID);
562 if (!condition.m_syncbase)
563 continue;
564 if (!is<SVGSMILElement>(*condition.m_syncbase)) {
565 condition.m_syncbase = nullptr;
566 continue;
567 }
568 downcast<SVGSMILElement>(*condition.m_syncbase).addTimeDependent(this);
569 }
570 }
571}
572
573void SVGSMILElement::disconnectConditions()
574{
575 if (!m_conditionsConnected)
576 return;
577 m_conditionsConnected = false;
578 for (auto& condition : m_conditions) {
579 if (condition.m_type == Condition::EventBase) {
580 ASSERT(!condition.m_syncbase);
581 if (!condition.m_eventListener)
582 continue;
583 // Note: It's a memory optimization to try to remove our condition
584 // event listener, but it's not guaranteed to work, since we have
585 // no guarantee that eventBaseFor() will be able to find our condition's
586 // original eventBase. So, we also have to disconnect ourselves from
587 // our condition event listener, in case it later fires.
588 auto eventBase = makeRefPtr(eventBaseFor(condition));
589 if (eventBase)
590 eventBase->removeEventListener(condition.m_name, *condition.m_eventListener, false);
591 condition.m_eventListener->disconnectAnimation();
592 condition.m_eventListener = nullptr;
593 } else if (condition.m_type == Condition::Syncbase) {
594 if (condition.m_syncbase)
595 downcast<SVGSMILElement>(condition.m_syncbase.get())->removeTimeDependent(this);
596 }
597 condition.m_syncbase = nullptr;
598 }
599}
600
601void SVGSMILElement::setAttributeName(const QualifiedName& attributeName)
602{
603 if (m_timeContainer && m_targetElement && m_attributeName != attributeName) {
604 if (hasValidAttributeName())
605 m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
606 m_attributeName = attributeName;
607 if (hasValidAttributeName())
608 m_timeContainer->schedule(this, m_targetElement, m_attributeName);
609 } else
610 m_attributeName = attributeName;
611
612 // Only clear the animated type, if we had a target before.
613 if (m_targetElement)
614 clearAnimatedType(m_targetElement);
615}
616
617void SVGSMILElement::setTargetElement(SVGElement* target)
618{
619 if (m_timeContainer && hasValidAttributeName()) {
620 if (m_targetElement)
621 m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
622 if (target)
623 m_timeContainer->schedule(this, target, m_attributeName);
624 }
625
626 if (m_targetElement) {
627 // Clear values that may depend on the previous target.
628 clearAnimatedType(m_targetElement);
629 disconnectConditions();
630 }
631
632 // If the animation state is not Inactive, always reset to a clear state before leaving the old target element.
633 if (m_activeState != Inactive)
634 endedActiveInterval();
635
636 m_targetElement = target;
637}
638
639SMILTime SVGSMILElement::elapsed() const
640{
641 return m_timeContainer ? m_timeContainer->elapsed() : 0;
642}
643
644bool SVGSMILElement::isInactive() const
645{
646 return m_activeState == Inactive;
647}
648
649bool SVGSMILElement::isFrozen() const
650{
651 return m_activeState == Frozen;
652}
653
654SVGSMILElement::Restart SVGSMILElement::restart() const
655{
656 static NeverDestroyed<const AtomicString> never("never", AtomicString::ConstructFromLiteral);
657 static NeverDestroyed<const AtomicString> whenNotActive("whenNotActive", AtomicString::ConstructFromLiteral);
658 const AtomicString& value = attributeWithoutSynchronization(SVGNames::restartAttr);
659 if (value == never)
660 return RestartNever;
661 if (value == whenNotActive)
662 return RestartWhenNotActive;
663 return RestartAlways;
664}
665
666SVGSMILElement::FillMode SVGSMILElement::fill() const
667{
668 static NeverDestroyed<const AtomicString> freeze("freeze", AtomicString::ConstructFromLiteral);
669 const AtomicString& value = attributeWithoutSynchronization(SVGNames::fillAttr);
670 return value == freeze ? FillFreeze : FillRemove;
671}
672
673SMILTime SVGSMILElement::dur() const
674{
675 if (m_cachedDur != invalidCachedTime)
676 return m_cachedDur;
677 const AtomicString& value = attributeWithoutSynchronization(SVGNames::durAttr);
678 SMILTime clockValue = parseClockValue(value);
679 return m_cachedDur = clockValue <= 0 ? SMILTime::unresolved() : clockValue;
680}
681
682SMILTime SVGSMILElement::repeatDur() const
683{
684 if (m_cachedRepeatDur != invalidCachedTime)
685 return m_cachedRepeatDur;
686 const AtomicString& value = attributeWithoutSynchronization(SVGNames::repeatDurAttr);
687 SMILTime clockValue = parseClockValue(value);
688 m_cachedRepeatDur = clockValue <= 0 ? SMILTime::unresolved() : clockValue;
689 return m_cachedRepeatDur;
690}
691
692// So a count is not really a time but let just all pretend we did not notice.
693SMILTime SVGSMILElement::repeatCount() const
694{
695 if (m_cachedRepeatCount != invalidCachedTime)
696 return m_cachedRepeatCount;
697 const AtomicString& value = attributeWithoutSynchronization(SVGNames::repeatCountAttr);
698 if (value.isNull())
699 return SMILTime::unresolved();
700
701 static NeverDestroyed<const AtomicString> indefiniteValue("indefinite", AtomicString::ConstructFromLiteral);
702 if (value == indefiniteValue)
703 return SMILTime::indefinite();
704 bool ok;
705 double result = value.string().toDouble(&ok);
706 return m_cachedRepeatCount = ok && result > 0 ? result : SMILTime::unresolved();
707}
708
709SMILTime SVGSMILElement::maxValue() const
710{
711 if (m_cachedMax != invalidCachedTime)
712 return m_cachedMax;
713 const AtomicString& value = attributeWithoutSynchronization(SVGNames::maxAttr);
714 SMILTime result = parseClockValue(value);
715 return m_cachedMax = (result.isUnresolved() || result <= 0) ? SMILTime::indefinite() : result;
716}
717
718SMILTime SVGSMILElement::minValue() const
719{
720 if (m_cachedMin != invalidCachedTime)
721 return m_cachedMin;
722 const AtomicString& value = attributeWithoutSynchronization(SVGNames::minAttr);
723 SMILTime result = parseClockValue(value);
724 return m_cachedMin = (result.isUnresolved() || result < 0) ? 0 : result;
725}
726
727SMILTime SVGSMILElement::simpleDuration() const
728{
729 return std::min(dur(), SMILTime::indefinite());
730}
731
732void SVGSMILElement::addBeginTime(SMILTime eventTime, SMILTime beginTime, SMILTimeWithOrigin::Origin origin)
733{
734 ASSERT(!std::isnan(beginTime.value()));
735 m_beginTimes.append(SMILTimeWithOrigin(beginTime, origin));
736 sortTimeList(m_beginTimes);
737 beginListChanged(eventTime);
738}
739
740void SVGSMILElement::addEndTime(SMILTime eventTime, SMILTime endTime, SMILTimeWithOrigin::Origin origin)
741{
742 ASSERT(!std::isnan(endTime.value()));
743 m_endTimes.append(SMILTimeWithOrigin(endTime, origin));
744 sortTimeList(m_endTimes);
745 endListChanged(eventTime);
746}
747
748inline SMILTime extractTimeFromVector(const SMILTimeWithOrigin* position)
749{
750 return position->time();
751}
752
753SMILTime SVGSMILElement::findInstanceTime(BeginOrEnd beginOrEnd, SMILTime minimumTime, bool equalsMinimumOK) const
754{
755 const Vector<SMILTimeWithOrigin>& list = beginOrEnd == Begin ? m_beginTimes : m_endTimes;
756 int sizeOfList = list.size();
757
758 if (!sizeOfList)
759 return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
760
761 const SMILTimeWithOrigin* result = approximateBinarySearch<const SMILTimeWithOrigin, SMILTime>(list, sizeOfList, minimumTime, extractTimeFromVector);
762 int indexOfResult = result - list.begin();
763 ASSERT_WITH_SECURITY_IMPLICATION(indexOfResult < sizeOfList);
764
765 if (list[indexOfResult].time() < minimumTime && indexOfResult < sizeOfList - 1)
766 ++indexOfResult;
767
768 const SMILTime& currentTime = list[indexOfResult].time();
769
770 // The special value "indefinite" does not yield an instance time in the begin list.
771 if (currentTime.isIndefinite() && beginOrEnd == Begin)
772 return SMILTime::unresolved();
773
774 if (currentTime < minimumTime)
775 return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
776 if (currentTime > minimumTime)
777 return currentTime;
778
779 ASSERT(currentTime == minimumTime);
780 if (equalsMinimumOK)
781 return currentTime;
782
783 // If the equals is not accepted, return the next bigger item in the list.
784 SMILTime nextTime = currentTime;
785 while (indexOfResult < sizeOfList - 1) {
786 nextTime = list[indexOfResult + 1].time();
787 if (nextTime > minimumTime)
788 return nextTime;
789 ++indexOfResult;
790 }
791
792 return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
793}
794
795SMILTime SVGSMILElement::repeatingDuration() const
796{
797 // Computing the active duration
798 // http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur
799 SMILTime repeatCount = this->repeatCount();
800 SMILTime repeatDur = this->repeatDur();
801 SMILTime simpleDuration = this->simpleDuration();
802 if (!simpleDuration || (repeatDur.isUnresolved() && repeatCount.isUnresolved()))
803 return simpleDuration;
804 SMILTime repeatCountDuration = simpleDuration * repeatCount;
805 return std::min(repeatCountDuration, std::min(repeatDur, SMILTime::indefinite()));
806}
807
808SMILTime SVGSMILElement::resolveActiveEnd(SMILTime resolvedBegin, SMILTime resolvedEnd) const
809{
810 // Computing the active duration
811 // http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur
812 SMILTime preliminaryActiveDuration;
813 if (!resolvedEnd.isUnresolved() && dur().isUnresolved() && repeatDur().isUnresolved() && repeatCount().isUnresolved())
814 preliminaryActiveDuration = resolvedEnd - resolvedBegin;
815 else if (!resolvedEnd.isFinite())
816 preliminaryActiveDuration = repeatingDuration();
817 else
818 preliminaryActiveDuration = std::min(repeatingDuration(), resolvedEnd - resolvedBegin);
819
820 SMILTime minValue = this->minValue();
821 SMILTime maxValue = this->maxValue();
822 if (minValue > maxValue) {
823 // Ignore both.
824 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#MinMax
825 minValue = 0;
826 maxValue = SMILTime::indefinite();
827 }
828 return resolvedBegin + std::min(maxValue, std::max(minValue, preliminaryActiveDuration));
829}
830
831void SVGSMILElement::resolveInterval(bool first, SMILTime& beginResult, SMILTime& endResult) const
832{
833 // See the pseudocode in http://www.w3.org/TR/SMIL3/smil-timing.html#q90.
834 SMILTime beginAfter = first ? -std::numeric_limits<double>::infinity() : m_intervalEnd;
835 SMILTime lastIntervalTempEnd = std::numeric_limits<double>::infinity();
836 while (true) {
837 bool equalsMinimumOK = !first || m_intervalEnd > m_intervalBegin;
838 SMILTime tempBegin = findInstanceTime(Begin, beginAfter, equalsMinimumOK);
839 if (tempBegin.isUnresolved())
840 break;
841 SMILTime tempEnd;
842 if (m_endTimes.isEmpty())
843 tempEnd = resolveActiveEnd(tempBegin, SMILTime::indefinite());
844 else {
845 tempEnd = findInstanceTime(End, tempBegin, true);
846 if ((first && tempBegin == tempEnd && tempEnd == lastIntervalTempEnd) || (!first && tempEnd == m_intervalEnd))
847 tempEnd = findInstanceTime(End, tempBegin, false);
848 if (tempEnd.isUnresolved()) {
849 if (!m_endTimes.isEmpty() && !m_hasEndEventConditions)
850 break;
851 }
852 tempEnd = resolveActiveEnd(tempBegin, tempEnd);
853 }
854 if (!first || (tempEnd > 0 || (!tempBegin.value() && !tempEnd.value()))) {
855 beginResult = tempBegin;
856 endResult = tempEnd;
857 return;
858 }
859
860 beginAfter = tempEnd;
861 lastIntervalTempEnd = tempEnd;
862 }
863 beginResult = SMILTime::unresolved();
864 endResult = SMILTime::unresolved();
865}
866
867void SVGSMILElement::resolveFirstInterval()
868{
869 SMILTime begin;
870 SMILTime end;
871 resolveInterval(true, begin, end);
872 ASSERT(!begin.isIndefinite());
873
874 if (!begin.isUnresolved() && (begin != m_intervalBegin || end != m_intervalEnd)) {
875 bool wasUnresolved = m_intervalBegin.isUnresolved();
876 m_intervalBegin = begin;
877 m_intervalEnd = end;
878 notifyDependentsIntervalChanged(wasUnresolved ? NewInterval : ExistingInterval);
879 m_nextProgressTime = std::min(m_nextProgressTime, m_intervalBegin);
880
881 if (m_timeContainer)
882 m_timeContainer->notifyIntervalsChanged();
883 }
884}
885
886void SVGSMILElement::resolveNextInterval(bool notifyDependents)
887{
888 SMILTime begin;
889 SMILTime end;
890 resolveInterval(false, begin, end);
891 ASSERT(!begin.isIndefinite());
892
893 if (!begin.isUnresolved() && begin != m_intervalBegin) {
894 m_intervalBegin = begin;
895 m_intervalEnd = end;
896 if (notifyDependents)
897 notifyDependentsIntervalChanged(NewInterval);
898 m_nextProgressTime = std::min(m_nextProgressTime, m_intervalBegin);
899 }
900}
901
902SMILTime SVGSMILElement::nextProgressTime() const
903{
904 return m_nextProgressTime;
905}
906
907void SVGSMILElement::beginListChanged(SMILTime eventTime)
908{
909 if (m_isWaitingForFirstInterval)
910 resolveFirstInterval();
911 else {
912 SMILTime newBegin = findInstanceTime(Begin, eventTime, true);
913 if (newBegin.isFinite() && (m_intervalEnd <= eventTime || newBegin < m_intervalBegin)) {
914 // Begin time changed, re-resolve the interval.
915 SMILTime oldBegin = m_intervalBegin;
916 m_intervalEnd = eventTime;
917 resolveInterval(false, m_intervalBegin, m_intervalEnd);
918 ASSERT(!m_intervalBegin.isUnresolved());
919 if (m_intervalBegin != oldBegin) {
920 if (m_activeState == Active && m_intervalBegin > eventTime) {
921 m_activeState = determineActiveState(eventTime);
922 if (m_activeState != Active)
923 endedActiveInterval();
924 }
925 notifyDependentsIntervalChanged(ExistingInterval);
926 }
927 }
928 }
929 m_nextProgressTime = elapsed();
930
931 if (m_timeContainer)
932 m_timeContainer->notifyIntervalsChanged();
933}
934
935void SVGSMILElement::endListChanged(SMILTime)
936{
937 SMILTime elapsed = this->elapsed();
938 if (m_isWaitingForFirstInterval)
939 resolveFirstInterval();
940 else if (elapsed < m_intervalEnd && m_intervalBegin.isFinite()) {
941 SMILTime newEnd = findInstanceTime(End, m_intervalBegin, false);
942 if (newEnd < m_intervalEnd) {
943 newEnd = resolveActiveEnd(m_intervalBegin, newEnd);
944 if (newEnd != m_intervalEnd) {
945 m_intervalEnd = newEnd;
946 notifyDependentsIntervalChanged(ExistingInterval);
947 }
948 }
949 }
950 m_nextProgressTime = elapsed;
951
952 if (m_timeContainer)
953 m_timeContainer->notifyIntervalsChanged();
954}
955
956void SVGSMILElement::checkRestart(SMILTime elapsed)
957{
958 ASSERT(!m_isWaitingForFirstInterval);
959 ASSERT(elapsed >= m_intervalBegin);
960
961 Restart restart = this->restart();
962 if (restart == RestartNever)
963 return;
964
965 if (elapsed < m_intervalEnd) {
966 if (restart != RestartAlways)
967 return;
968 SMILTime nextBegin = findInstanceTime(Begin, m_intervalBegin, false);
969 if (nextBegin < m_intervalEnd) {
970 m_intervalEnd = nextBegin;
971 notifyDependentsIntervalChanged(ExistingInterval);
972 }
973 }
974
975 if (elapsed >= m_intervalEnd)
976 resolveNextInterval(true);
977}
978
979void SVGSMILElement::seekToIntervalCorrespondingToTime(SMILTime elapsed)
980{
981 ASSERT(!m_isWaitingForFirstInterval);
982 ASSERT(elapsed >= m_intervalBegin);
983
984 // Manually seek from interval to interval, just as if the animation would run regulary.
985 while (true) {
986 // Figure out the next value in the begin time list after the current interval begin.
987 SMILTime nextBegin = findInstanceTime(Begin, m_intervalBegin, false);
988
989 // If the 'nextBegin' time is unresolved (eg. just one defined interval), we're done seeking.
990 if (nextBegin.isUnresolved())
991 return;
992
993 // If the 'nextBegin' time is larger than or equal to the current interval end time, we're done seeking.
994 // If the 'elapsed' time is smaller than the next begin interval time, we're done seeking.
995 if (nextBegin < m_intervalEnd && elapsed >= nextBegin) {
996 // End current interval, and start a new interval from the 'nextBegin' time.
997 m_intervalEnd = nextBegin;
998 resolveNextInterval(false);
999 continue;
1000 }
1001
1002 // If the desired 'elapsed' time is past the current interval, advance to the next.
1003 if (elapsed >= m_intervalEnd) {
1004 resolveNextInterval(false);
1005 continue;
1006 }
1007
1008 return;
1009 }
1010}
1011
1012float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsigned& repeat) const
1013{
1014 SMILTime simpleDuration = this->simpleDuration();
1015 repeat = 0;
1016 if (simpleDuration.isIndefinite()) {
1017 repeat = 0;
1018 return 0.f;
1019 }
1020 if (!simpleDuration) {
1021 repeat = 0;
1022 return 1.f;
1023 }
1024 ASSERT(m_intervalBegin.isFinite());
1025 ASSERT(simpleDuration.isFinite());
1026 SMILTime activeTime = elapsed - m_intervalBegin;
1027 SMILTime repeatingDuration = this->repeatingDuration();
1028 if (elapsed >= m_intervalEnd || activeTime > repeatingDuration) {
1029 repeat = static_cast<unsigned>(repeatingDuration.value() / simpleDuration.value()) - 1;
1030
1031 double percent = (m_intervalEnd.value() - m_intervalBegin.value()) / simpleDuration.value();
1032 percent = percent - floor(percent);
1033 if (percent < std::numeric_limits<float>::epsilon() || 1 - percent < std::numeric_limits<float>::epsilon())
1034 return 1.0f;
1035 return narrowPrecisionToFloat(percent);
1036 }
1037 repeat = static_cast<unsigned>(activeTime.value() / simpleDuration.value());
1038 SMILTime simpleTime = fmod(activeTime.value(), simpleDuration.value());
1039 return narrowPrecisionToFloat(simpleTime.value() / simpleDuration.value());
1040}
1041
1042SMILTime SVGSMILElement::calculateNextProgressTime(SMILTime elapsed) const
1043{
1044 ASSERT(m_timeContainer);
1045 if (m_activeState == Active) {
1046 // If duration is indefinite the value does not actually change over time. Same is true for <set>.
1047 SMILTime simpleDuration = this->simpleDuration();
1048 if (simpleDuration.isIndefinite() || hasTagName(SVGNames::setTag)) {
1049 SMILTime repeatingDurationEnd = m_intervalBegin + repeatingDuration();
1050 // We are supposed to do freeze semantics when repeating ends, even if the element is still active.
1051 // Take care that we get a timer callback at that point.
1052 if (elapsed < repeatingDurationEnd && repeatingDurationEnd < m_intervalEnd && repeatingDurationEnd.isFinite())
1053 return repeatingDurationEnd;
1054 return m_intervalEnd;
1055 }
1056 return elapsed + m_timeContainer->animationFrameDelay();
1057 }
1058 return m_intervalBegin >= elapsed ? m_intervalBegin : SMILTime::unresolved();
1059}
1060
1061SVGSMILElement::ActiveState SVGSMILElement::determineActiveState(SMILTime elapsed) const
1062{
1063 if (elapsed >= m_intervalBegin && elapsed < m_intervalEnd)
1064 return Active;
1065
1066 return fill() == FillFreeze ? Frozen : Inactive;
1067}
1068
1069bool SVGSMILElement::isContributing(SMILTime elapsed) const
1070{
1071 // Animation does not contribute during the active time if it is past its repeating duration and has fill=remove.
1072 return (m_activeState == Active && (fill() == FillFreeze || elapsed <= m_intervalBegin + repeatingDuration())) || m_activeState == Frozen;
1073}
1074
1075bool SVGSMILElement::progress(SMILTime elapsed, SVGSMILElement* resultElement, bool seekToTime)
1076{
1077 ASSERT(resultElement);
1078 ASSERT(m_timeContainer);
1079 ASSERT(m_isWaitingForFirstInterval || m_intervalBegin.isFinite());
1080
1081 if (!m_intervalBegin.isFinite()) {
1082 ASSERT(m_activeState == Inactive);
1083 m_nextProgressTime = SMILTime::unresolved();
1084 return false;
1085 }
1086
1087 if (elapsed < m_intervalBegin) {
1088 ASSERT(m_activeState != Active);
1089 if (m_activeState == Frozen) {
1090 if (this == resultElement)
1091 resetAnimatedType();
1092 updateAnimation(m_lastPercent, m_lastRepeat, resultElement);
1093 }
1094 m_nextProgressTime = m_intervalBegin;
1095 return false;
1096 }
1097
1098 m_previousIntervalBegin = m_intervalBegin;
1099
1100 if (m_isWaitingForFirstInterval) {
1101 m_isWaitingForFirstInterval = false;
1102 resolveFirstInterval();
1103 }
1104
1105 // This call may obtain a new interval -- never call calculateAnimationPercentAndRepeat() before!
1106 if (seekToTime) {
1107 seekToIntervalCorrespondingToTime(elapsed);
1108 if (elapsed < m_intervalBegin) {
1109 // elapsed is not within an interval.
1110 m_nextProgressTime = m_intervalBegin;
1111 return false;
1112 }
1113 }
1114
1115 unsigned repeat = 0;
1116 float percent = calculateAnimationPercentAndRepeat(elapsed, repeat);
1117 checkRestart(elapsed);
1118
1119 ActiveState oldActiveState = m_activeState;
1120 m_activeState = determineActiveState(elapsed);
1121 bool animationIsContributing = isContributing(elapsed);
1122
1123 // Only reset the animated type to the base value once for the lowest priority animation that animates and contributes to a particular element/attribute pair.
1124 if (this == resultElement && animationIsContributing)
1125 resetAnimatedType();
1126
1127 if (animationIsContributing) {
1128 if (oldActiveState == Inactive)
1129 startedActiveInterval();
1130
1131 updateAnimation(percent, repeat, resultElement);
1132 m_lastPercent = percent;
1133 m_lastRepeat = repeat;
1134 }
1135
1136 if (oldActiveState == Active && m_activeState != Active) {
1137 smilEndEventSender().dispatchEventSoon(*this);
1138 endedActiveInterval();
1139 if (m_activeState != Frozen)
1140 clearAnimatedType(m_targetElement);
1141 } else if (oldActiveState != Active && m_activeState == Active)
1142 smilBeginEventSender().dispatchEventSoon(*this);
1143
1144 // Triggering all the pending events if the animation timeline is changed.
1145 if (seekToTime) {
1146 if (m_activeState == Inactive || m_activeState == Frozen)
1147 smilEndEventSender().dispatchEventSoon(*this);
1148 }
1149
1150 m_nextProgressTime = calculateNextProgressTime(elapsed);
1151 return animationIsContributing;
1152}
1153
1154void SVGSMILElement::notifyDependentsIntervalChanged(NewOrExistingInterval newOrExisting)
1155{
1156 ASSERT(m_intervalBegin.isFinite());
1157 static NeverDestroyed<HashSet<SVGSMILElement*>> loopBreaker;
1158 if (loopBreaker.get().contains(this))
1159 return;
1160 loopBreaker.get().add(this);
1161
1162 for (auto& dependent : m_timeDependents) {
1163 dependent->createInstanceTimesFromSyncbase(this, newOrExisting);
1164 }
1165
1166 loopBreaker.get().remove(this);
1167}
1168
1169void SVGSMILElement::createInstanceTimesFromSyncbase(SVGSMILElement* syncbase, NewOrExistingInterval)
1170{
1171 // FIXME: To be really correct, this should handle updating exising interval by changing
1172 // the associated times instead of creating new ones.
1173 for (auto& condition : m_conditions) {
1174 if (condition.m_type == Condition::Syncbase && condition.m_syncbase == syncbase) {
1175 ASSERT(condition.m_name == "begin" || condition.m_name == "end");
1176 // No nested time containers in SVG, no need for crazy time space conversions. Phew!
1177 SMILTime time = 0;
1178 if (condition.m_name == "begin")
1179 time = syncbase->m_intervalBegin + condition.m_offset;
1180 else
1181 time = syncbase->m_intervalEnd + condition.m_offset;
1182 if (!time.isFinite())
1183 continue;
1184 if (condition.m_beginOrEnd == Begin)
1185 addBeginTime(elapsed(), time);
1186 else
1187 addEndTime(elapsed(), time);
1188 }
1189 }
1190}
1191
1192void SVGSMILElement::addTimeDependent(SVGSMILElement* animation)
1193{
1194 m_timeDependents.add(animation);
1195 if (m_intervalBegin.isFinite())
1196 animation->createInstanceTimesFromSyncbase(this, NewInterval);
1197}
1198
1199void SVGSMILElement::removeTimeDependent(SVGSMILElement* animation)
1200{
1201 m_timeDependents.remove(animation);
1202}
1203
1204void SVGSMILElement::handleConditionEvent(Condition* condition)
1205{
1206 SMILTime elapsed = this->elapsed();
1207 if (condition->m_beginOrEnd == Begin)
1208 addBeginTime(elapsed, elapsed + condition->m_offset);
1209 else
1210 addEndTime(elapsed, elapsed + condition->m_offset);
1211}
1212
1213void SVGSMILElement::beginByLinkActivation()
1214{
1215 SMILTime elapsed = this->elapsed();
1216 addBeginTime(elapsed, elapsed);
1217}
1218
1219void SVGSMILElement::endedActiveInterval()
1220{
1221 clearTimesWithDynamicOrigins(m_beginTimes);
1222 clearTimesWithDynamicOrigins(m_endTimes);
1223}
1224
1225void SVGSMILElement::dispatchPendingEvent(SMILEventSender* eventSender)
1226{
1227 ASSERT(eventSender == &smilBeginEventSender() || eventSender == &smilEndEventSender());
1228 const AtomicString& eventType = eventSender->eventType();
1229 dispatchEvent(Event::create(eventType, Event::CanBubble::No, Event::IsCancelable::No));
1230}
1231
1232}
1233