Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / svg / animation / SVGSMILElement.cpp
index 73c8785..dcae8f1 100644 (file)
  */
 
 #include "config.h"
-
-#if ENABLE(SVG)
 #include "SVGSMILElement.h"
 
-#include "Attribute.h"
 #include "CSSPropertyNames.h"
 #include "Document.h"
 #include "Event.h"
 #include "EventListener.h"
+#include "EventNames.h"
+#include "EventSender.h"
 #include "FloatConversion.h"
 #include "FrameView.h"
-#include "HTMLNames.h"
 #include "SMILTimeContainer.h"
 #include "SVGDocumentExtensions.h"
 #include "SVGNames.h"
 #include <wtf/StdLibExtras.h>
 #include <wtf/Vector.h>
 
-using namespace std;
-
 namespace WebCore {
-    
+
+static SMILEventSender& smilBeginEventSender()
+{
+    static NeverDestroyed<SMILEventSender> sender(eventNames().beginEventEvent);
+    return sender;
+}
+
+static SMILEventSender& smilEndEventSender()
+{
+    static NeverDestroyed<SMILEventSender> sender(eventNames().endEventEvent);
+    return sender;
+}
+
 // This is used for duration type time values that can't be negative.
 static const double invalidCachedTime = -1.;
     
-class ConditionEventListener : public EventListener {
+class ConditionEventListener final : public EventListener {
 public:
-    static PassRefPtr<ConditionEventListener> create(SVGSMILElement* animation, SVGSMILElement::Condition* condition)
+    static Ref<ConditionEventListener> create(SVGSMILElement* animation, SVGSMILElement::Condition* condition)
     {
-        return adoptRef(new ConditionEventListener(animation, condition));
+        return adoptRef(*new ConditionEventListener(animation, condition));
     }
 
     static const ConditionEventListener* cast(const EventListener* listener)
     {
         return listener->type() == ConditionEventListenerType
             ? static_cast<const ConditionEventListener*>(listener)
-            : 0;
+            : nullptr;
     }
 
-    virtual bool operator==(const EventListener& other);
+    bool operator==(const EventListener& other) const final;
     
     void disconnectAnimation()
     {
-        m_animation = 0;
+        m_animation = nullptr;
     }
 
 private:
@@ -83,13 +91,13 @@ private:
     {
     }
 
-    virtual void handleEvent(ScriptExecutionContext*, Event*);
+    void handleEvent(ScriptExecutionContext*, Event*) final;
 
     SVGSMILElement* m_animation;
     SVGSMILElement::Condition* m_condition;
 };
 
-bool ConditionEventListener::operator==(const EventListener& listener)
+bool ConditionEventListener::operator==(const EventListener& listener) const
 {
     if (const ConditionEventListener* conditionEventListener = ConditionEventListener::cast(&listener))
         return m_animation == conditionEventListener->m_animation && m_condition == conditionEventListener->m_condition;
@@ -113,10 +121,10 @@ SVGSMILElement::Condition::Condition(Type type, BeginOrEnd beginOrEnd, const Str
 {
 }
     
-SVGSMILElement::SVGSMILElement(const QualifiedName& tagName, Document* doc)
+SVGSMILElement::SVGSMILElement(const QualifiedName& tagName, Document& doc)
     : SVGElement(tagName, doc)
     , m_attributeName(anyQName())
-    , m_targetElement(0)
+    , m_targetElement(nullptr)
     , m_conditionsConnected(false)
     , m_hasEndEventConditions(false)
     , m_isWaitingForFirstInterval(true)
@@ -139,45 +147,100 @@ SVGSMILElement::SVGSMILElement(const QualifiedName& tagName, Document* doc)
 
 SVGSMILElement::~SVGSMILElement()
 {
+    clearResourceReferences();
+    smilBeginEventSender().cancelEvent(*this);
+    smilEndEventSender().cancelEvent(*this);
     disconnectConditions();
-    if (m_timeContainer)
-        m_timeContainer->unschedule(this);
-    if (m_targetElement)
-        document()->accessSVGExtensions()->removeAnimationElementFromTarget(this, m_targetElement);
+    if (m_timeContainer && m_targetElement && hasValidAttributeName())
+        m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
 }
 
-static inline QualifiedName constructQualifiedName(const SVGElement* svgElement, const String& attributeName)
+void SVGSMILElement::clearResourceReferences()
 {
-    ASSERT(svgElement);
-    if (attributeName.isEmpty())
-        return anyQName();
-    if (!attributeName.contains(':'))
-        return QualifiedName(nullAtom, attributeName, nullAtom);
-    
-    String prefix;
-    String localName;
-    ExceptionCode ec = 0;
-    if (!Document::parseQualifiedName(attributeName, prefix, localName, ec))
+    document().accessSVGExtensions().removeAllTargetReferencesForElement(this);
+}
+
+void SVGSMILElement::clearTarget()
+{
+    setTargetElement(nullptr);
+}
+
+void SVGSMILElement::buildPendingResource()
+{
+    clearResourceReferences();
+
+    if (!isConnected()) {
+        // Reset the target element if we are no longer in the document.
+        setTargetElement(nullptr);
+        return;
+    }
+
+    String id;
+    Element* target;
+    auto& href = getAttribute(XLinkNames::hrefAttr);
+    if (href.isEmpty())
+        target = parentElement();
+    else
+        target = SVGURIReference::targetElementFromIRIString(href.string(), document(), &id);
+    SVGElement* svgTarget = is<SVGElement>(target) ? downcast<SVGElement>(target) : nullptr;
+
+    if (svgTarget && !svgTarget->isConnected())
+        svgTarget = nullptr;
+
+    if (svgTarget != targetElement())
+        setTargetElement(svgTarget);
+
+    if (!svgTarget) {
+        // Do not register as pending if we are already pending this resource.
+        if (document().accessSVGExtensions().isPendingResource(this, id))
+            return;
+
+        if (!id.isEmpty()) {
+            document().accessSVGExtensions().addPendingResource(id, this);
+            ASSERT(hasPendingResources());
+        }
+    } else {
+        // Register us with the target in the dependencies map. Any change of hrefElement
+        // that leads to relayout/repainting now informs us, so we can react to it.
+        document().accessSVGExtensions().addElementReferencingTarget(this, svgTarget);
+    }
+}
+
+inline QualifiedName SVGSMILElement::constructAttributeName() const
+{
+    auto parseResult = Document::parseQualifiedName(attributeWithoutSynchronization(SVGNames::attributeNameAttr));
+    if (parseResult.hasException())
         return anyQName();
-    ASSERT(!ec);
-    
-    String namespaceURI = svgElement->lookupNamespaceURI(prefix);    
+
+    AtomicString prefix, localName;
+    std::tie(prefix, localName) = parseResult.releaseReturnValue();
+
+    if (prefix.isNull())
+        return { nullAtom(), localName, nullAtom() };
+
+    auto namespaceURI = lookupNamespaceURI(prefix);
     if (namespaceURI.isEmpty())
         return anyQName();
-    
-    return QualifiedName(nullAtom, localName, namespaceURI);
+
+    return { nullAtom(), localName, namespaceURI };
+}
+
+inline void SVGSMILElement::updateAttributeName()
+{
+    setAttributeName(constructAttributeName());
 }
 
 static inline void clearTimesWithDynamicOrigins(Vector<SMILTimeWithOrigin>& timeList)
 {
-    for (int i = timeList.size() - 1; i >= 0; --i) {
-        if (timeList[i].originIsScript())
-            timeList.remove(i);
-    }
+    timeList.removeAllMatching([] (const SMILTimeWithOrigin& time) {
+        return time.originIsScript();
+    });
 }
 
 void SVGSMILElement::reset()
 {
+    clearAnimatedType(m_targetElement);
+
     m_activeState = Inactive;
     m_isWaitingForFirstInterval = true;
     m_intervalBegin = SMILTime::unresolved();
@@ -189,68 +252,77 @@ void SVGSMILElement::reset()
     resolveFirstInterval();
 }
 
-void SVGSMILElement::insertedIntoDocument()
+Node::InsertionNotificationRequest SVGSMILElement::insertedInto(ContainerNode& rootParent)
 {
-    SVGElement::insertedIntoDocument();
+    SVGElement::insertedInto(rootParent);
+    if (!rootParent.isConnected())
+        return InsertionDone;
 
     // Verify we are not in <use> instance tree.
     ASSERT(!isInShadowTree());
 
-    m_attributeName = constructQualifiedName(this, fastGetAttribute(SVGNames::attributeNameAttr));
+    updateAttributeName();
+
     SVGSVGElement* owner = ownerSVGElement();
     if (!owner)
-        return;
-    m_timeContainer = owner->timeContainer();
-    ASSERT(m_timeContainer);
+        return InsertionDone;
+
+    m_timeContainer = &owner->timeContainer();
     m_timeContainer->setDocumentOrderIndexesDirty();
 
     // "If no attribute is present, the default begin value (an offset-value of 0) must be evaluated."
-    if (!fastHasAttribute(SVGNames::beginAttr))
+    if (!hasAttributeWithoutSynchronization(SVGNames::beginAttr))
         m_beginTimes.append(SMILTimeWithOrigin());
 
-    if (m_isWaitingForFirstInterval) {
+    if (m_isWaitingForFirstInterval)
         resolveFirstInterval();
-        reschedule();
-    }
+
+    if (m_timeContainer)
+        m_timeContainer->notifyIntervalsChanged();
+
+    return InsertionShouldCallFinishedInsertingSubtree;
 }
 
-void SVGSMILElement::removedFromDocument()
+void SVGSMILElement::finishedInsertingSubtree()
 {
-    m_attributeName = anyQName();
-    if (m_timeContainer) {
-        m_timeContainer->unschedule(this);
-        m_timeContainer = 0;
-    }
-    // Calling disconnectConditions() may kill us if there are syncbase conditions.
-    // OK, but we don't want to die inside the call.
-    RefPtr<SVGSMILElement> keepAlive(this);
-    disconnectConditions();
+    buildPendingResource();
+}
 
-    // Clear target now, because disconnectConditions calls targetElement() which will recreate the target if we removed it sooner. 
-    if (m_targetElement) {
-        document()->accessSVGExtensions()->removeAnimationElementFromTarget(this, m_targetElement);
-        m_targetElement = 0;
+void SVGSMILElement::removedFrom(ContainerNode& rootParent)
+{
+    if (rootParent.isConnected()) {
+        clearResourceReferences();
+        disconnectConditions();
+        setTargetElement(nullptr);
+        setAttributeName(anyQName());
+        animationAttributeChanged();
+        m_timeContainer = nullptr;
     }
 
-    SVGElement::removedFromDocument();
+    SVGElement::removedFrom(rootParent);
 }
-   
+
+bool SVGSMILElement::hasValidAttributeName()
+{
+    return attributeName() != anyQName();
+}
+
 SMILTime SVGSMILElement::parseOffsetValue(const String& data)
 {
     bool ok;
     double result = 0;
     String parse = data.stripWhiteSpace();
-    if (parse.endsWith("h"))
+    if (parse.endsWith('h'))
         result = parse.left(parse.length() - 1).toDouble(&ok) * 60 * 60;
     else if (parse.endsWith("min"))
         result = parse.left(parse.length() - 3).toDouble(&ok) * 60;
     else if (parse.endsWith("ms"))
         result = parse.left(parse.length() - 2).toDouble(&ok) / 1000;
-    else if (parse.endsWith("s"))
+    else if (parse.endsWith('s'))
         result = parse.left(parse.length() - 1).toDouble(&ok);
     else
         result = parse.toDouble(&ok);
-    if (!ok)
+    if (!ok || !SMILTime(result).isFinite())
         return SMILTime::unresolved();
     return result;
 }
@@ -262,7 +334,7 @@ SMILTime SVGSMILElement::parseClockValue(const String& data)
     
     String parse = data.stripWhiteSpace();
 
-    DEFINE_STATIC_LOCAL(const AtomicString, indefiniteValue, ("indefinite"));
+    static NeverDestroyed<const AtomicString> indefiniteValue("indefinite", AtomicString::ConstructFromLiteral);
     if (parse == indefiniteValue)
         return SMILTime::indefinite();
 
@@ -286,7 +358,7 @@ SMILTime SVGSMILElement::parseClockValue(const String& data)
     } else
         return parseOffsetValue(parse);
 
-    if (!ok)
+    if (!ok || !SMILTime(result).isFinite())
         return SMILTime::unresolved();
     return result;
 }
@@ -337,7 +409,7 @@ bool SVGSMILElement::parseCondition(const String& value, BeginOrEnd beginOrEnd)
 
     Condition::Type type;
     int repeats = -1;
-    if (nameString.startsWith("repeat(") && nameString.endsWith(")")) {
+    if (nameString.startsWith("repeat(") && nameString.endsWith(')')) {
         // FIXME: For repeat events we just need to add the data carrying TimeEvent class and 
         // fire the events at appropiate times.
         repeats = nameString.substring(7, nameString.length() - 8).toUIntStrict(&ok);
@@ -363,63 +435,77 @@ bool SVGSMILElement::parseCondition(const String& value, BeginOrEnd beginOrEnd)
     return true;
 }
 
-bool SVGSMILElement::isSMILElement(Node* node)
-{
-    if (!node)
-        return false;
-    return node->hasTagName(SVGNames::setTag) || node->hasTagName(SVGNames::animateTag) || node->hasTagName(SVGNames::animateMotionTag)
-            || node->hasTagName(SVGNames::animateTransformTag) || node->hasTagName(SVGNames::animateColorTag);
-}
-    
 void SVGSMILElement::parseBeginOrEnd(const String& parseString, BeginOrEnd beginOrEnd)
 {
     Vector<SMILTimeWithOrigin>& timeList = beginOrEnd == Begin ? m_beginTimes : m_endTimes;
     if (beginOrEnd == End)
         m_hasEndEventConditions = false;
     HashSet<double> existing;
-    for (unsigned n = 0; n < timeList.size(); ++n)
-        existing.add(timeList[n].time().value());
+    for (auto& time : timeList)
+        existing.add(time.time().value());
     Vector<String> splitString;
     parseString.split(';', splitString);
-    for (unsigned n = 0; n < splitString.size(); ++n) {
-        SMILTime value = parseClockValue(splitString[n]);
+    for (auto& string : splitString) {
+        SMILTime value = parseClockValue(string);
         if (value.isUnresolved())
-            parseCondition(splitString[n], beginOrEnd);
+            parseCondition(string, beginOrEnd);
         else if (!existing.contains(value.value()))
             timeList.append(SMILTimeWithOrigin(value, SMILTimeWithOrigin::ParserOrigin));
     }
     sortTimeList(timeList);
 }
 
-void SVGSMILElement::parseAttribute(Attribute* attr)
+bool SVGSMILElement::isSupportedAttribute(const QualifiedName& attrName)
+{
+    static const auto supportedAttributes = makeNeverDestroyed(HashSet<QualifiedName> {
+        SVGNames::beginAttr,
+        SVGNames::endAttr,
+        SVGNames::durAttr,
+        SVGNames::repeatDurAttr,
+        SVGNames::repeatCountAttr,
+        SVGNames::minAttr,
+        SVGNames::maxAttr,
+        SVGNames::attributeNameAttr,
+        XLinkNames::hrefAttr,
+    });
+    return supportedAttributes.get().contains<SVGAttributeHashTranslator>(attrName);
+}
+
+void SVGSMILElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
-    if (attr->name() == SVGNames::beginAttr) {
+    if (name == SVGNames::beginAttr) {
         if (!m_conditions.isEmpty()) {
             disconnectConditions();
             m_conditions.clear();
-            parseBeginOrEnd(fastGetAttribute(SVGNames::endAttr), End);
+            parseBeginOrEnd(attributeWithoutSynchronization(SVGNames::endAttr), End);
         }
-        parseBeginOrEnd(attr->value().string(), Begin);
-        if (inDocument())
+        parseBeginOrEnd(value.string(), Begin);
+        if (isConnected())
             connectConditions();
-    } else if (attr->name() == SVGNames::endAttr) {
+    } else if (name == SVGNames::endAttr) {
         if (!m_conditions.isEmpty()) {
             disconnectConditions();
             m_conditions.clear();
-            parseBeginOrEnd(fastGetAttribute(SVGNames::beginAttr), Begin);
+            parseBeginOrEnd(attributeWithoutSynchronization(SVGNames::beginAttr), Begin);
         }
-        parseBeginOrEnd(attr->value().string(), End);
-        if (inDocument())
+        parseBeginOrEnd(value.string(), End);
+        if (isConnected())
             connectConditions();
-    } else
-        SVGElement::parseAttribute(attr);
+    } else if (name == SVGNames::onendAttr)
+        setAttributeEventListener(eventNames().endEventEvent, name, value);
+    else if (name == SVGNames::onbeginAttr)
+        setAttributeEventListener(eventNames().beginEventEvent, name, value);
+    else
+        SVGElement::parseAttribute(name, value);
 }
 
-void SVGSMILElement::attributeChanged(Attribute* attr)
+void SVGSMILElement::svgAttributeChanged(const QualifiedName& attrName)
 {
-    SVGElement::attributeChanged(attr);
+    if (!isSupportedAttribute(attrName)) {
+        SVGElement::svgAttributeChanged(attrName);
+        return;
+    }
 
-    const QualifiedName& attrName = attr->name();
     if (attrName == SVGNames::durAttr)
         m_cachedDur = invalidCachedTime;
     else if (attrName == SVGNames::repeatDurAttr)
@@ -430,22 +516,24 @@ void SVGSMILElement::attributeChanged(Attribute* attr)
         m_cachedMin = invalidCachedTime;
     else if (attrName == SVGNames::maxAttr)
         m_cachedMax = invalidCachedTime;
-    else if (attrName == SVGNames::attributeNameAttr) {
-        if (inDocument())
-            m_attributeName = constructQualifiedName(this, attr->value());
-    }
-
-    if (inDocument()) {
+    else if (attrName == SVGNames::attributeNameAttr)
+        updateAttributeName();
+    else if (attrName.matches(XLinkNames::hrefAttr)) {
+        InstanceInvalidationGuard guard(*this);
+        buildPendingResource();
+    } else if (isConnected()) {
         if (attrName == SVGNames::beginAttr)
             beginListChanged(elapsed());
         else if (attrName == SVGNames::endAttr)
             endListChanged(elapsed());
     }
+
+    animationAttributeChanged();
 }
 
 inline Element* SVGSMILElement::eventBaseFor(const Condition& condition)
 {
-    return condition.m_baseID.isEmpty() ? targetElement() : treeScope()->getElementById(condition.m_baseID);
+    return condition.m_baseID.isEmpty() ? targetElement() : treeScope().getElementById(condition.m_baseID);
 }
 
 void SVGSMILElement::connectConditions()
@@ -453,8 +541,7 @@ void SVGSMILElement::connectConditions()
     if (m_conditionsConnected)
         disconnectConditions();
     m_conditionsConnected = true;
-    for (unsigned n = 0; n < m_conditions.size(); ++n) {
-        Condition& condition = m_conditions[n];
+    for (auto& condition : m_conditions) {
         if (condition.m_type == Condition::EventBase) {
             ASSERT(!condition.m_syncbase);
             Element* eventBase = eventBaseFor(condition);
@@ -462,16 +549,17 @@ void SVGSMILElement::connectConditions()
                 continue;
             ASSERT(!condition.m_eventListener);
             condition.m_eventListener = ConditionEventListener::create(this, &condition);
-            eventBase->addEventListener(condition.m_name, condition.m_eventListener, false);
+            eventBase->addEventListener(condition.m_name, *condition.m_eventListener, false);
         } else if (condition.m_type == Condition::Syncbase) {
             ASSERT(!condition.m_baseID.isEmpty());
-            condition.m_syncbase = treeScope()->getElementById(condition.m_baseID);
-            if (!isSMILElement(condition.m_syncbase.get())) {
-                condition.m_syncbase = 0;
+            condition.m_syncbase = treeScope().getElementById(condition.m_baseID);
+            if (!condition.m_syncbase)
+                continue;
+            if (!is<SVGSMILElement>(*condition.m_syncbase)) {
+                condition.m_syncbase = nullptr;
                 continue;
             }
-            SVGSMILElement* syncbase = static_cast<SVGSMILElement*>(condition.m_syncbase.get());
-            syncbase->addTimeDependent(this);
+            downcast<SVGSMILElement>(*condition.m_syncbase).addTimeDependent(this);
         }
     }
 }
@@ -481,8 +569,7 @@ void SVGSMILElement::disconnectConditions()
     if (!m_conditionsConnected)
         return;
     m_conditionsConnected = false;
-    for (unsigned n = 0; n < m_conditions.size(); ++n) {
-        Condition& condition = m_conditions[n];
+    for (auto& condition : m_conditions) {
         if (condition.m_type == Condition::EventBase) {
             ASSERT(!condition.m_syncbase);
             if (!condition.m_eventListener)
@@ -494,53 +581,53 @@ void SVGSMILElement::disconnectConditions()
             // our condition event listener, in case it later fires.
             Element* eventBase = eventBaseFor(condition);
             if (eventBase)
-                eventBase->removeEventListener(condition.m_name, condition.m_eventListener.get(), false);
+                eventBase->removeEventListener(condition.m_name, *condition.m_eventListener, false);
             condition.m_eventListener->disconnectAnimation();
-            condition.m_eventListener = 0;
+            condition.m_eventListener = nullptr;
         } else if (condition.m_type == Condition::Syncbase) {
-            if (condition.m_syncbase) {
-                ASSERT(isSMILElement(condition.m_syncbase.get()));
-                static_cast<SVGSMILElement*>(condition.m_syncbase.get())->removeTimeDependent(this);
-            }
+            if (condition.m_syncbase)
+                downcast<SVGSMILElement>(condition.m_syncbase.get())->removeTimeDependent(this);
         }
-        condition.m_syncbase = 0;
+        condition.m_syncbase = nullptr;
     }
 }
 
-void SVGSMILElement::reschedule()
+void SVGSMILElement::setAttributeName(const QualifiedName& attributeName)
 {
-    if (m_timeContainer)
-        m_timeContainer->schedule(this);
-}
+    if (m_timeContainer && m_targetElement && m_attributeName != attributeName) {
+        if (hasValidAttributeName())
+            m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
+        m_attributeName = attributeName;
+        if (hasValidAttributeName())
+            m_timeContainer->schedule(this, m_targetElement, m_attributeName);
+    } else
+        m_attributeName = attributeName;
 
-SVGElement* SVGSMILElement::targetElement()
-{
+    // Only clear the animated type, if we had a target before.
     if (m_targetElement)
-        return m_targetElement;
-
-    String href = xlinkHref();
-    ContainerNode* target = href.isEmpty() ? parentNode() : SVGURIReference::targetElementFromIRIString(href, document());
-    if (!target || !target->isSVGElement())
-        return 0;
-
-    m_targetElement = static_cast<SVGElement*>(target);
-    document()->accessSVGExtensions()->addAnimationElementToTarget(this, m_targetElement);
-
-    targetElementDidChange(m_targetElement);
-
-    return m_targetElement;
+        clearAnimatedType(m_targetElement);
 }
 
-void SVGSMILElement::resetTargetElement()
+void SVGSMILElement::setTargetElement(SVGElement* target)
 {
-    m_targetElement = 0;
+    if (m_timeContainer && hasValidAttributeName()) {
+        if (m_targetElement)
+            m_timeContainer->unschedule(this, m_targetElement, m_attributeName);
+        if (target)
+            m_timeContainer->schedule(this, target, m_attributeName);
+    }
 
-    // Force the animation to recompute values that are only calculated when an animation becomes active.
-    // Failing to do this means that a target reset and change during active animation may result in
-    // invalid state.
-    m_activeState = Inactive;
+    if (m_targetElement) {
+        // Clear values that may depend on the previous target.
+        clearAnimatedType(m_targetElement);
+        disconnectConditions();
+    }
+
+    // If the animation state is not Inactive, always reset to a clear state before leaving the old target element.
+    if (m_activeState != Inactive)
+        endedActiveInterval();
 
-    targetElementDidChange(0);
+    m_targetElement = target;
 }
 
 SMILTime SVGSMILElement::elapsed() const
@@ -560,9 +647,9 @@ bool SVGSMILElement::isFrozen() const
     
 SVGSMILElement::Restart SVGSMILElement::restart() const
 {    
-    DEFINE_STATIC_LOCAL(const AtomicString, never, ("never"));
-    DEFINE_STATIC_LOCAL(const AtomicString, whenNotActive, ("whenNotActive"));
-    const AtomicString& value = fastGetAttribute(SVGNames::restartAttr);
+    static NeverDestroyed<const AtomicString> never("never", AtomicString::ConstructFromLiteral);
+    static NeverDestroyed<const AtomicString> whenNotActive("whenNotActive", AtomicString::ConstructFromLiteral);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::restartAttr);
     if (value == never)
         return RestartNever;
     if (value == whenNotActive)
@@ -572,21 +659,16 @@ SVGSMILElement::Restart SVGSMILElement::restart() const
     
 SVGSMILElement::FillMode SVGSMILElement::fill() const
 {   
-    DEFINE_STATIC_LOCAL(const AtomicString, freeze, ("freeze"));
-    const AtomicString& value = fastGetAttribute(SVGNames::fillAttr);
+    static NeverDestroyed<const AtomicString> freeze("freeze", AtomicString::ConstructFromLiteral);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::fillAttr);
     return value == freeze ? FillFreeze : FillRemove;
 }
     
-String SVGSMILElement::xlinkHref() const
-{    
-    return getAttribute(XLinkNames::hrefAttr);
-}
-    
 SMILTime SVGSMILElement::dur() const
 {   
     if (m_cachedDur != invalidCachedTime)
         return m_cachedDur;
-    const AtomicString& value = fastGetAttribute(SVGNames::durAttr);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::durAttr);
     SMILTime clockValue = parseClockValue(value);
     return m_cachedDur = clockValue <= 0 ? SMILTime::unresolved() : clockValue;
 }
@@ -595,7 +677,7 @@ SMILTime SVGSMILElement::repeatDur() const
 {    
     if (m_cachedRepeatDur != invalidCachedTime)
         return m_cachedRepeatDur;
-    const AtomicString& value = fastGetAttribute(SVGNames::repeatDurAttr);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::repeatDurAttr);
     SMILTime clockValue = parseClockValue(value);
     m_cachedRepeatDur = clockValue <= 0 ? SMILTime::unresolved() : clockValue;
     return m_cachedRepeatDur;
@@ -606,11 +688,11 @@ SMILTime SVGSMILElement::repeatCount() const
 {    
     if (m_cachedRepeatCount != invalidCachedTime)
         return m_cachedRepeatCount;
-    const AtomicString& value = fastGetAttribute(SVGNames::repeatCountAttr);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::repeatCountAttr);
     if (value.isNull())
         return SMILTime::unresolved();
 
-    DEFINE_STATIC_LOCAL(const AtomicString, indefiniteValue, ("indefinite"));
+    static NeverDestroyed<const AtomicString> indefiniteValue("indefinite", AtomicString::ConstructFromLiteral);
     if (value == indefiniteValue)
         return SMILTime::indefinite();
     bool ok;
@@ -622,27 +704,28 @@ SMILTime SVGSMILElement::maxValue() const
 {    
     if (m_cachedMax != invalidCachedTime)
         return m_cachedMax;
-    const AtomicString& value = fastGetAttribute(SVGNames::maxAttr);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::maxAttr);
     SMILTime result = parseClockValue(value);
-    return m_cachedMax = (result.isUnresolved() || result < 0) ? SMILTime::indefinite() : result;
+    return m_cachedMax = (result.isUnresolved() || result <= 0) ? SMILTime::indefinite() : result;
 }
     
 SMILTime SVGSMILElement::minValue() const
 {    
     if (m_cachedMin != invalidCachedTime)
         return m_cachedMin;
-    const AtomicString& value = fastGetAttribute(SVGNames::minAttr);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::minAttr);
     SMILTime result = parseClockValue(value);
     return m_cachedMin = (result.isUnresolved() || result < 0) ? 0 : result;
 }
                  
 SMILTime SVGSMILElement::simpleDuration() const
 {
-    return min(dur(), SMILTime::indefinite());
+    return std::min(dur(), SMILTime::indefinite());
 }
 
 void SVGSMILElement::addBeginTime(SMILTime eventTime, SMILTime beginTime, SMILTimeWithOrigin::Origin origin)
 {
+    ASSERT(!std::isnan(beginTime.value()));
     m_beginTimes.append(SMILTimeWithOrigin(beginTime, origin));
     sortTimeList(m_beginTimes);
     beginListChanged(eventTime);
@@ -650,6 +733,7 @@ void SVGSMILElement::addBeginTime(SMILTime eventTime, SMILTime beginTime, SMILTi
 
 void SVGSMILElement::addEndTime(SMILTime eventTime, SMILTime endTime, SMILTimeWithOrigin::Origin origin)
 {
+    ASSERT(!std::isnan(endTime.value()));
     m_endTimes.append(SMILTimeWithOrigin(endTime, origin));
     sortTimeList(m_endTimes);
     endListChanged(eventTime);
@@ -668,39 +752,38 @@ SMILTime SVGSMILElement::findInstanceTime(BeginOrEnd beginOrEnd, SMILTime minimu
     if (!sizeOfList)
         return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
 
-    const SMILTimeWithOrigin* result = binarySearch<const SMILTimeWithOrigin, SMILTime, extractTimeFromVector>(list.begin(), sizeOfList, minimumTime, WTF::KeyMustNotBePresentInArray);
+    const SMILTimeWithOrigin* result = approximateBinarySearch<const SMILTimeWithOrigin, SMILTime>(list, sizeOfList, minimumTime, extractTimeFromVector);
     int indexOfResult = result - list.begin();
-    if (sizeOfList - 1 > indexOfResult && list[indexOfResult].time() < minimumTime)
+    ASSERT_WITH_SECURITY_IMPLICATION(indexOfResult < sizeOfList);
+
+    if (list[indexOfResult].time() < minimumTime && indexOfResult < sizeOfList - 1)
         ++indexOfResult;
 
-    ASSERT(indexOfResult < sizeOfList);
     const SMILTime& currentTime = list[indexOfResult].time();
 
-    // "The special value "indefinite" does not yield an instance time in the begin list."
+    // The special value "indefinite" does not yield an instance time in the begin list.
     if (currentTime.isIndefinite() && beginOrEnd == Begin)
         return SMILTime::unresolved();
 
     if (currentTime < minimumTime)
         return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
+    if (currentTime > minimumTime)
+        return currentTime;
 
-    // If the equals is NOT accepted, we have to find a bigger one.
-    if (currentTime == minimumTime && !equalsMinimumOK) {
-        if (indexOfResult + 1 >= sizeOfList)
-            return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
-    }
+    ASSERT(currentTime == minimumTime);
+    if (equalsMinimumOK)
+        return currentTime;
 
-    while (indexOfResult < sizeOfList - 1 && currentTime == list[indexOfResult + 1].time())
+    // If the equals is not accepted, return the next bigger item in the list.
+    SMILTime nextTime = currentTime;
+    while (indexOfResult < sizeOfList - 1) {
+        nextTime = list[indexOfResult + 1].time();
+        if (nextTime > minimumTime)
+            return nextTime;
         ++indexOfResult;
-
-    if (currentTime > minimumTime)
-        return currentTime;
-    if (currentTime == minimumTime) {
-        if (indexOfResult + 1 < sizeOfList - 1)
-            return list[indexOfResult + 1].time();
-        return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
     }
 
-    return currentTime;
+    return beginOrEnd == Begin ? SMILTime::unresolved() : SMILTime::indefinite();
 }
 
 SMILTime SVGSMILElement::repeatingDuration() const
@@ -713,7 +796,7 @@ SMILTime SVGSMILElement::repeatingDuration() const
     if (!simpleDuration || (repeatDur.isUnresolved() && repeatCount.isUnresolved()))
         return simpleDuration;
     SMILTime repeatCountDuration = simpleDuration * repeatCount;
-    return min(repeatCountDuration, min(repeatDur, SMILTime::indefinite()));
+    return std::min(repeatCountDuration, std::min(repeatDur, SMILTime::indefinite()));
 }
 
 SMILTime SVGSMILElement::resolveActiveEnd(SMILTime resolvedBegin, SMILTime resolvedEnd) const
@@ -726,7 +809,7 @@ SMILTime SVGSMILElement::resolveActiveEnd(SMILTime resolvedBegin, SMILTime resol
     else if (!resolvedEnd.isFinite())
         preliminaryActiveDuration = repeatingDuration();
     else
-        preliminaryActiveDuration = min(repeatingDuration(), resolvedEnd - resolvedBegin);
+        preliminaryActiveDuration = std::min(repeatingDuration(), resolvedEnd - resolvedBegin);
     
     SMILTime minValue = this->minValue();
     SMILTime maxValue = this->maxValue();
@@ -736,17 +819,17 @@ SMILTime SVGSMILElement::resolveActiveEnd(SMILTime resolvedBegin, SMILTime resol
         minValue = 0;
         maxValue = SMILTime::indefinite();
     }
-    return resolvedBegin + min(maxValue, max(minValue, preliminaryActiveDuration));
+    return resolvedBegin + std::min(maxValue, std::max(minValue, preliminaryActiveDuration));
 }
 
 void SVGSMILElement::resolveInterval(bool first, SMILTime& beginResult, SMILTime& endResult) const
 {
-    // See the pseudocode in
-    // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LifeCycle
-    SMILTime beginAfter = first ? -numeric_limits<double>::infinity() : m_intervalEnd;
-    SMILTime lastIntervalTempEnd = numeric_limits<double>::infinity();
+    // See the pseudocode in http://www.w3.org/TR/SMIL3/smil-timing.html#q90.
+    SMILTime beginAfter = first ? -std::numeric_limits<double>::infinity() : m_intervalEnd;
+    SMILTime lastIntervalTempEnd = std::numeric_limits<double>::infinity();
     while (true) {
-        SMILTime tempBegin = findInstanceTime(Begin, beginAfter, true);
+        bool equalsMinimumOK = !first || m_intervalEnd > m_intervalBegin;
+        SMILTime tempBegin = findInstanceTime(Begin, beginAfter, equalsMinimumOK);
         if (tempBegin.isUnresolved())
             break;
         SMILTime tempEnd;
@@ -762,13 +845,11 @@ void SVGSMILElement::resolveInterval(bool first, SMILTime& beginResult, SMILTime
             }
             tempEnd = resolveActiveEnd(tempBegin, tempEnd);
         }
-        if (tempEnd > 0 || !first) {
+        if (!first || (tempEnd > 0 || (!tempBegin.value() && !tempEnd.value()))) {
             beginResult = tempBegin;
             endResult = tempEnd;
             return;
         }
-        if (restart() == RestartNever)
-            break;
 
         beginAfter = tempEnd;
         lastIntervalTempEnd = tempEnd;
@@ -789,12 +870,14 @@ void SVGSMILElement::resolveFirstInterval()
         m_intervalBegin = begin;
         m_intervalEnd = end;
         notifyDependentsIntervalChanged(wasUnresolved ? NewInterval : ExistingInterval);
-        m_nextProgressTime = min(m_nextProgressTime, m_intervalBegin);
-        reschedule();
+        m_nextProgressTime = std::min(m_nextProgressTime, m_intervalBegin);
+
+        if (m_timeContainer)
+            m_timeContainer->notifyIntervalsChanged();
     }
 }
 
-void SVGSMILElement::resolveNextInterval()
+void SVGSMILElement::resolveNextInterval(bool notifyDependents)
 {
     SMILTime begin;
     SMILTime end;
@@ -804,8 +887,9 @@ void SVGSMILElement::resolveNextInterval()
     if (!begin.isUnresolved() && begin != m_intervalBegin) {
         m_intervalBegin = begin;
         m_intervalEnd = end;
-        notifyDependentsIntervalChanged(NewInterval);
-        m_nextProgressTime = min(m_nextProgressTime, m_intervalBegin);
+        if (notifyDependents)
+            notifyDependentsIntervalChanged(NewInterval);
+        m_nextProgressTime = std::min(m_nextProgressTime, m_intervalBegin);
     }
 }
 
@@ -837,7 +921,9 @@ void SVGSMILElement::beginListChanged(SMILTime eventTime)
         }
     }
     m_nextProgressTime = elapsed();
-    reschedule();
+
+    if (m_timeContainer)
+        m_timeContainer->notifyIntervalsChanged();
 }
 
 void SVGSMILElement::endListChanged(SMILTime)
@@ -856,18 +942,20 @@ void SVGSMILElement::endListChanged(SMILTime)
         }
     }
     m_nextProgressTime = elapsed;
-    reschedule();
+
+    if (m_timeContainer)
+        m_timeContainer->notifyIntervalsChanged();
 }
-    
+
 void SVGSMILElement::checkRestart(SMILTime elapsed)
 {
     ASSERT(!m_isWaitingForFirstInterval);
     ASSERT(elapsed >= m_intervalBegin);
-    
+
     Restart restart = this->restart();
     if (restart == RestartNever)
         return;
-    
+
     if (elapsed < m_intervalEnd) {
         if (restart != RestartAlways)
             return;
@@ -876,11 +964,45 @@ void SVGSMILElement::checkRestart(SMILTime elapsed)
             m_intervalEnd = nextBegin;
             notifyDependentsIntervalChanged(ExistingInterval);
         }
-    } 
+    }
+
     if (elapsed >= m_intervalEnd)
-        resolveNextInterval();
+        resolveNextInterval(true);
 }
-    
+
+void SVGSMILElement::seekToIntervalCorrespondingToTime(SMILTime elapsed)
+{
+    ASSERT(!m_isWaitingForFirstInterval);
+    ASSERT(elapsed >= m_intervalBegin);
+
+    // Manually seek from interval to interval, just as if the animation would run regulary.
+    while (true) {
+        // Figure out the next value in the begin time list after the current interval begin.
+        SMILTime nextBegin = findInstanceTime(Begin, m_intervalBegin, false);
+
+        // If the 'nextBegin' time is unresolved (eg. just one defined interval), we're done seeking.
+        if (nextBegin.isUnresolved())
+            return;
+
+        // If the 'nextBegin' time is larger than or equal to the current interval end time, we're done seeking.
+        // If the 'elapsed' time is smaller than the next begin interval time, we're done seeking.
+        if (nextBegin < m_intervalEnd && elapsed >= nextBegin) {
+            // End current interval, and start a new interval from the 'nextBegin' time.
+            m_intervalEnd = nextBegin;
+            resolveNextInterval(false);
+            continue;
+        }
+
+        // If the desired 'elapsed' time is past the current interval, advance to the next.
+        if (elapsed >= m_intervalEnd) {
+            resolveNextInterval(false);
+            continue;
+        }
+
+        return;
+    }
+}
+
 float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsigned& repeat) const
 {
     SMILTime simpleDuration = this->simpleDuration();
@@ -898,15 +1020,13 @@ float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsig
     SMILTime activeTime = elapsed - m_intervalBegin;
     SMILTime repeatingDuration = this->repeatingDuration();
     if (elapsed >= m_intervalEnd || activeTime > repeatingDuration) {
-        repeat = static_cast<unsigned>(repeatingDuration.value() / simpleDuration.value());
-        if (fmod(repeatingDuration.value(), !simpleDuration.value()))
-            repeat--;
-
-        SMILTime simpleEndTime = fmod(m_intervalEnd.value() - m_intervalBegin.value(), simpleDuration.value());
-        if (simpleEndTime.value())
-            return narrowPrecisionToFloat(simpleEndTime.value() / simpleDuration.value());
+        repeat = static_cast<unsigned>(repeatingDuration.value() / simpleDuration.value()) - 1;
 
-        return 1.f;
+        double percent = (m_intervalEnd.value() - m_intervalBegin.value()) / simpleDuration.value();
+        percent = percent - floor(percent);
+        if (percent < std::numeric_limits<float>::epsilon() || 1 - percent < std::numeric_limits<float>::epsilon())
+            return 1.0f;
+        return narrowPrecisionToFloat(percent);
     }
     repeat = static_cast<unsigned>(activeTime.value() / simpleDuration.value());
     SMILTime simpleTime = fmod(activeTime.value(), simpleDuration.value());
@@ -915,6 +1035,7 @@ float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsig
     
 SMILTime SVGSMILElement::calculateNextProgressTime(SMILTime elapsed) const
 {
+    ASSERT(m_timeContainer);
     if (m_activeState == Active) {
         // If duration is indefinite the value does not actually change over time. Same is true for <set>.
         SMILTime simpleDuration = this->simpleDuration();
@@ -926,7 +1047,7 @@ SMILTime SVGSMILElement::calculateNextProgressTime(SMILTime elapsed) const
                 return repeatingDurationEnd;
             return m_intervalEnd;
         } 
-        return elapsed + 0.025;
+        return elapsed + m_timeContainer->animationFrameDelay();
     }
     return m_intervalBegin >= elapsed ? m_intervalBegin : SMILTime::unresolved();
 }
@@ -936,10 +1057,7 @@ SVGSMILElement::ActiveState SVGSMILElement::determineActiveState(SMILTime elapse
     if (elapsed >= m_intervalBegin && elapsed < m_intervalEnd)
         return Active;
 
-    if (m_activeState == Active)
-        return fill() == FillFreeze ? Frozen : Inactive;
-
-    return m_activeState;
+    return fill() == FillFreeze ? Frozen : Inactive;
 }
     
 bool SVGSMILElement::isContributing(SMILTime elapsed) const 
@@ -948,80 +1066,105 @@ bool SVGSMILElement::isContributing(SMILTime elapsed) const
     return (m_activeState == Active && (fill() == FillFreeze || elapsed <= m_intervalBegin + repeatingDuration())) || m_activeState == Frozen;
 }
     
-void SVGSMILElement::progress(SMILTime elapsed, SVGSMILElement* resultElement)
+bool SVGSMILElement::progress(SMILTime elapsed, SVGSMILElement* resultElement, bool seekToTime)
 {
+    ASSERT(resultElement);
     ASSERT(m_timeContainer);
     ASSERT(m_isWaitingForFirstInterval || m_intervalBegin.isFinite());
-    
-    if (!m_conditionsConnected)
-        connectConditions();
-    
+
     if (!m_intervalBegin.isFinite()) {
         ASSERT(m_activeState == Inactive);
         m_nextProgressTime = SMILTime::unresolved();
-        return;
+        return false;
     }
-    
+
     if (elapsed < m_intervalBegin) {
         ASSERT(m_activeState != Active);
-        if (m_activeState == Frozen && resultElement)
+        if (m_activeState == Frozen) {
+            if (this == resultElement)
+                resetAnimatedType();
             updateAnimation(m_lastPercent, m_lastRepeat, resultElement);
+        }
         m_nextProgressTime = m_intervalBegin;
-        return;
+        return false;
     }
-    
+
     m_previousIntervalBegin = m_intervalBegin;
-    
-    if (m_activeState == Inactive) {
+
+    if (m_isWaitingForFirstInterval) {
         m_isWaitingForFirstInterval = false;
-        m_activeState = Active;
-        startedActiveInterval();
+        resolveFirstInterval();
+    }
+
+    // This call may obtain a new interval -- never call calculateAnimationPercentAndRepeat() before!
+    if (seekToTime) {
+        seekToIntervalCorrespondingToTime(elapsed);
+        if (elapsed < m_intervalBegin) {
+            // elapsed is not within an interval.
+            m_nextProgressTime = m_intervalBegin;
+            return false;
+        }
     }
-    
-    unsigned repeat;
-    float percent = calculateAnimationPercentAndRepeat(elapsed, repeat);
 
+    unsigned repeat = 0;
+    float percent = calculateAnimationPercentAndRepeat(elapsed, repeat);
     checkRestart(elapsed);
 
     ActiveState oldActiveState = m_activeState;
     m_activeState = determineActiveState(elapsed);
-    
-    if (isContributing(elapsed)) {
-        if (resultElement)
-            updateAnimation(percent, repeat, resultElement);
+    bool animationIsContributing = isContributing(elapsed);
+
+    // 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.
+    if (this == resultElement && animationIsContributing)
+        resetAnimatedType();
+
+    if (animationIsContributing) {
+        if (oldActiveState == Inactive)
+            startedActiveInterval();
+
+        updateAnimation(percent, repeat, resultElement);
         m_lastPercent = percent;
         m_lastRepeat = repeat;
     }
 
-    if (oldActiveState == Active && m_activeState != Active)
+    if (oldActiveState == Active && m_activeState != Active) {
+        smilEndEventSender().dispatchEventSoon(*this);
         endedActiveInterval();
+        if (m_activeState != Frozen)
+            clearAnimatedType(m_targetElement);
+    } else if (oldActiveState != Active && m_activeState == Active)
+        smilBeginEventSender().dispatchEventSoon(*this);
+
+    // Triggering all the pending events if the animation timeline is changed.
+    if (seekToTime) {
+        if (m_activeState == Inactive || m_activeState == Frozen)
+            smilEndEventSender().dispatchEventSoon(*this);
+    }
 
     m_nextProgressTime = calculateNextProgressTime(elapsed);
+    return animationIsContributing;
 }
     
 void SVGSMILElement::notifyDependentsIntervalChanged(NewOrExistingInterval newOrExisting)
 {
     ASSERT(m_intervalBegin.isFinite());
-    DEFINE_STATIC_LOCAL(HashSet<SVGSMILElement*>, loopBreaker, ());
-    if (loopBreaker.contains(this))
+    static NeverDestroyed<HashSet<SVGSMILElement*>> loopBreaker;
+    if (loopBreaker.get().contains(this))
         return;
-    loopBreaker.add(this);
+    loopBreaker.get().add(this);
     
-    TimeDependentSet::iterator end = m_timeDependents.end();
-    for (TimeDependentSet::iterator it = m_timeDependents.begin(); it != end; ++it) {
-        SVGSMILElement* dependent = *it;
+    for (auto& dependent : m_timeDependents) {
         dependent->createInstanceTimesFromSyncbase(this, newOrExisting);
     }
 
-    loopBreaker.remove(this);
+    loopBreaker.get().remove(this);
 }
     
 void SVGSMILElement::createInstanceTimesFromSyncbase(SVGSMILElement* syncbase, NewOrExistingInterval)
 {
     // FIXME: To be really correct, this should handle updating exising interval by changing 
     // the associated times instead of creating new ones.
-    for (unsigned n = 0; n < m_conditions.size(); ++n) {
-        Condition& condition = m_conditions[n];
+    for (auto& condition : m_conditions) {
         if (condition.m_type == Condition::Syncbase && condition.m_syncbase == syncbase) {
             ASSERT(condition.m_name == "begin" || condition.m_name == "end");
             // No nested time containers in SVG, no need for crazy time space conversions. Phew!
@@ -1030,7 +1173,8 @@ void SVGSMILElement::createInstanceTimesFromSyncbase(SVGSMILElement* syncbase, N
                 time = syncbase->m_intervalBegin + condition.m_offset;
             else
                 time = syncbase->m_intervalEnd + condition.m_offset;
-            ASSERT(time.isFinite());
+            if (!time.isFinite())
+                continue;
             if (condition.m_beginOrEnd == Begin)
                 addBeginTime(elapsed(), time);
             else
@@ -1072,6 +1216,11 @@ void SVGSMILElement::endedActiveInterval()
     clearTimesWithDynamicOrigins(m_endTimes);
 }
 
+void SVGSMILElement::dispatchPendingEvent(SMILEventSender* eventSender)
+{
+    ASSERT(eventSender == &smilBeginEventSender() || eventSender == &smilEndEventSender());
+    const AtomicString& eventType = eventSender->eventType();
+    dispatchEvent(Event::create(eventType, false, false));
 }
 
-#endif
+}