Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / svg / SVGAnimationElement.cpp
index ed86740..cac5d3d 100644 (file)
@@ -2,9 +2,10 @@
  * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
  * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
  * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
- * Copyright (C) 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2017 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  */
 
 #include "config.h"
-
-#if ENABLE(SVG_ANIMATION)
 #include "SVGAnimationElement.h"
 
-#include "Attribute.h"
 #include "CSSComputedStyleDeclaration.h"
-#include "CSSParser.h"
 #include "CSSPropertyNames.h"
-#include "Color.h"
+#include "CSSPropertyParser.h"
 #include "Document.h"
-#include "Event.h"
-#include "EventListener.h"
 #include "FloatConversion.h"
-#include "HTMLNames.h"
-#include "PlatformString.h"
-#include "SVGElementInstance.h"
+#include "RenderObject.h"
+#include "SVGAnimateColorElement.h"
+#include "SVGAnimateElement.h"
+#include "SVGElement.h"
 #include "SVGNames.h"
 #include "SVGParserUtilities.h"
-#include "SVGStyledElement.h"
-#include "SVGURIReference.h"
-#include "SVGUseElement.h"
-#include "XLinkNames.h"
-#include <wtf/StdLibExtras.h>
-
-using namespace std;
+#include "SVGStringList.h"
+#include <wtf/MathExtras.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringView.h>
 
 namespace WebCore {
 
 // Animated property definitions
 DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
 
-SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* document)
+BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGAnimationElement)
+    REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
+    REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
+END_REGISTER_ANIMATED_PROPERTIES
+
+SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document& document)
     : SVGSMILElement(tagName, document)
-    , m_animationValid(false)
 {
+    registerAnimatedPropertiesForSVGAnimationElement();
 }
 
 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
 {
     result.clear();
-    Vector<String> parseList;
-    parse.split(';', parseList);
-    for (unsigned n = 0; n < parseList.size(); ++n) {
-        String timeString = parseList[n];
+    bool isFirst = true;
+    for (StringView timeString : StringView(parse).split(';')) {
         bool ok;
-        float time = timeString.toFloat(&ok);
+        float time = timeString.toFloat(ok);
         if (!ok || time < 0 || time > 1)
             goto fail;
         if (verifyOrder) {
-            if (!n) {
+            if (isFirst) {
                 if (time)
                     goto fail;
+                isFirst = false;
             } else if (time < result.last())
                 goto fail;
         }
@@ -90,10 +87,12 @@ static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
     result.clear();
     if (parse.isEmpty())
         return;
-    const UChar* cur = parse.characters();
+
+    auto upconvertedCharacters = StringView(parse).upconvertedCharacters();
+    const UChar* cur = upconvertedCharacters;
     const UChar* end = cur + parse.length();
 
-    skipOptionalSpaces(cur, end);
+    skipOptionalSVGSpaces(cur, end);
 
     bool delimParsed = false;
     while (cur < end) {
@@ -122,13 +121,13 @@ static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
             return;
         }
 
-        skipOptionalSpaces(cur, end);
+        skipOptionalSVGSpaces(cur, end);
 
         if (cur < end && *cur == ';') {
             delimParsed = true;
             cur++;
         }
-        skipOptionalSpaces(cur, end);
+        skipOptionalSVGSpaces(cur, end);
 
         result.append(UnitBezier(posA, posB, posC, posD));
     }
@@ -138,84 +137,94 @@ static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
 
 bool SVGAnimationElement::isSupportedAttribute(const QualifiedName& attrName)
 {
-    DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
-    if (supportedAttributes.isEmpty()) {
-        SVGTests::addSupportedAttributes(supportedAttributes);
-        SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
-        supportedAttributes.add(SVGNames::valuesAttr);
-        supportedAttributes.add(SVGNames::keyTimesAttr);
-        supportedAttributes.add(SVGNames::keyPointsAttr);
-        supportedAttributes.add(SVGNames::keySplinesAttr);
-    }
-    return supportedAttributes.contains(attrName);
-}
-     
-void SVGAnimationElement::parseMappedAttribute(Attribute* attr)
+    static const auto supportedAttributes = makeNeverDestroyed([] {
+        HashSet<QualifiedName> set;
+        SVGTests::addSupportedAttributes(set);
+        SVGExternalResourcesRequired::addSupportedAttributes(set);
+        set.add({
+            SVGNames::valuesAttr,
+            SVGNames::keyTimesAttr,
+            SVGNames::keyPointsAttr,
+            SVGNames::keySplinesAttr,
+            SVGNames::attributeTypeAttr,
+            SVGNames::calcModeAttr,
+            SVGNames::fromAttr,
+            SVGNames::toAttr,
+            SVGNames::byAttr,
+        });
+        return set;
+    }());
+    return supportedAttributes.get().contains<SVGAttributeHashTranslator>(attrName);
+}
+
+void SVGAnimationElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
-    if (!isSupportedAttribute(attr->name())) {
-        SVGSMILElement::parseMappedAttribute(attr);
-        return;
-    }
-
-    if (attr->name() == SVGNames::valuesAttr) {
+    if (name == SVGNames::valuesAttr) {
         // Per the SMIL specification, leading and trailing white space,
         // and white space before and after semicolon separators, is allowed and will be ignored.
         // http://www.w3.org/TR/SVG11/animate.html#ValuesAttribute
-        attr->value().string().split(';', m_values);
-        for (unsigned i = 0; i < m_values.size(); ++i)
-            m_values[i] = m_values[i].stripWhiteSpace();
+        value.string().split(';', m_values);
+        for (auto& value : m_values)
+            value = value.stripWhiteSpace();
+
+        updateAnimationMode();
         return;
     }
 
-    if (attr->name() == SVGNames::keyTimesAttr) {
-        parseKeyTimes(attr->value(), m_keyTimes, true);
+    if (name == SVGNames::keyTimesAttr) {
+        parseKeyTimes(value, m_keyTimes, true);
         return;
     }
 
-    if (attr->name() == SVGNames::keyPointsAttr) {
+    if (name == SVGNames::keyPointsAttr) {
         if (hasTagName(SVGNames::animateMotionTag)) {
             // This is specified to be an animateMotion attribute only but it is simpler to put it here 
             // where the other timing calculatations are.
-            parseKeyTimes(attr->value(), m_keyPoints, false);
+            parseKeyTimes(value, m_keyPoints, false);
         }
         return;
     }
 
-    if (attr->name() == SVGNames::keySplinesAttr) {
-        parseKeySplines(attr->value(), m_keySplines);
+    if (name == SVGNames::keySplinesAttr) {
+        parseKeySplines(value, m_keySplines);
         return;
     }
 
-    if (SVGTests::parseMappedAttribute(attr))
+    if (name == SVGNames::attributeTypeAttr) {
+        setAttributeType(value);
         return;
-    if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
+    }
+
+    if (name == SVGNames::calcModeAttr) {
+        setCalcMode(value);
         return;
+    }
 
-    ASSERT_NOT_REACHED();
-}
+    if (name == SVGNames::fromAttr || name == SVGNames::toAttr || name == SVGNames::byAttr) {
+        updateAnimationMode();
+        return;
+    }
 
-void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
-{
-    // Assumptions may not hold after an attribute change.
-    m_animationValid = false;
-    setInactive();
-    SVGSMILElement::attributeChanged(attr, preserveDecls);
+    SVGSMILElement::parseAttribute(name, value);
+    SVGTests::parseAttribute(name, value);
+    SVGExternalResourcesRequired::parseAttribute(name, value);
 }
 
-void SVGAnimationElement::synchronizeProperty(const QualifiedName& attrName)
+void SVGAnimationElement::svgAttributeChanged(const QualifiedName& attrName)
 {
-    SVGSMILElement::synchronizeProperty(attrName);
-
-    if (attrName == anyQName()) {
-        synchronizeExternalResourcesRequired();
-        SVGTests::synchronizeProperties(this, attrName);
+    if (!isSupportedAttribute(attrName)) {
+        SVGSMILElement::svgAttributeChanged(attrName);
         return;
     }
 
-    if (SVGExternalResourcesRequired::isKnownAttribute(attrName))
-        synchronizeExternalResourcesRequired();
-    else if (SVGTests::isKnownAttribute(attrName))
-        SVGTests::synchronizeProperties(this, attrName);
+    animationAttributeChanged();
+}
+
+void SVGAnimationElement::animationAttributeChanged()
+{
+    // Assumptions may not hold after an attribute change.
+    m_animationValid = false;
+    setInactive();
 }
 
 float SVGAnimationElement::getStartTime() const
@@ -228,7 +237,7 @@ float SVGAnimationElement::getCurrentTime() const
     return narrowPrecisionToFloat(elapsed().value());
 }
 
-float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
+float SVGAnimationElement::getSimpleDuration() const
 {
     return narrowPrecisionToFloat(simpleDuration().value());
 }    
@@ -240,7 +249,10 @@ void SVGAnimationElement::beginElement()
 
 void SVGAnimationElement::beginElementAt(float offset)
 {
-    addBeginTime(elapsed() + offset);
+    if (std::isnan(offset))
+        return;
+    SMILTime elapsed = this->elapsed();
+    addBeginTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
 }
 
 void SVGAnimationElement::endElement()
@@ -250,147 +262,127 @@ void SVGAnimationElement::endElement()
 
 void SVGAnimationElement::endElementAt(float offset)
 {
-    addEndTime(elapsed() + offset);
+    if (std::isnan(offset))
+        return;
+    SMILTime elapsed = this->elapsed();
+    addEndTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
 }
 
-AnimationMode SVGAnimationElement::animationMode() const
+void SVGAnimationElement::updateAnimationMode()
 {
     // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
-    if (hasTagName(SVGNames::setTag))
-        return ToAnimation;
-    if (!animationPath().isEmpty())
-        return PathAnimation;
     if (hasAttribute(SVGNames::valuesAttr))
-        return ValuesAnimation;
-    if (!toValue().isEmpty())
-        return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
-    if (!byValue().isEmpty())
-        return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
-    return NoAnimation;
+        setAnimationMode(ValuesAnimation);
+    else if (!toValue().isEmpty())
+        setAnimationMode(fromValue().isEmpty() ? ToAnimation : FromToAnimation);
+    else if (!byValue().isEmpty())
+        setAnimationMode(fromValue().isEmpty() ? ByAnimation : FromByAnimation);
+    else
+        setAnimationMode(NoAnimation);
 }
 
-CalcMode SVGAnimationElement::calcMode() const
-{    
-    DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete"));
-    DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear"));
-    DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced"));
-    DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline"));
-    const AtomicString& value = getAttribute(SVGNames::calcModeAttr);
-    if (value == discrete)
-        return CalcModeDiscrete;
-    if (value == linear)
-        return CalcModeLinear;
-    if (value == paced)
-        return CalcModePaced;
-    if (value == spline)
-        return CalcModeSpline;
-    return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
-}
-
-SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
-{    
-    DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS"));
-    DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML"));
-    const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr);
-    if (value == css)
-        return AttributeTypeCSS;
-    if (value == xml)
-        return AttributeTypeXML;
-    return AttributeTypeAuto;
+void SVGAnimationElement::setCalcMode(const AtomicString& calcMode)
+{
+    static NeverDestroyed<const AtomicString> discrete("discrete", AtomicString::ConstructFromLiteral);
+    static NeverDestroyed<const AtomicString> linear("linear", AtomicString::ConstructFromLiteral);
+    static NeverDestroyed<const AtomicString> paced("paced", AtomicString::ConstructFromLiteral);
+    static NeverDestroyed<const AtomicString> spline("spline", AtomicString::ConstructFromLiteral);
+    if (calcMode == discrete)
+        setCalcMode(CalcMode::Discrete);
+    else if (calcMode == linear)
+        setCalcMode(CalcMode::Linear);
+    else if (calcMode == paced)
+        setCalcMode(CalcMode::Paced);
+    else if (calcMode == spline)
+        setCalcMode(CalcMode::Spline);
+    else
+        setCalcMode(hasTagName(SVGNames::animateMotionTag) ? CalcMode::Paced : CalcMode::Linear);
+}
+
+void SVGAnimationElement::setAttributeType(const AtomicString& attributeType)
+{
+    static NeverDestroyed<const AtomicString> css("CSS", AtomicString::ConstructFromLiteral);
+    static NeverDestroyed<const AtomicString> xml("XML", AtomicString::ConstructFromLiteral);
+    if (attributeType == css)
+        m_attributeType = AttributeType::CSS;
+    else if (attributeType == xml)
+        m_attributeType = AttributeType::XML;
+    else
+        m_attributeType = AttributeType::Auto;
+    checkInvalidCSSAttributeType(targetElement());
 }
 
 String SVGAnimationElement::toValue() const
 {    
-    return getAttribute(SVGNames::toAttr);
+    return attributeWithoutSynchronization(SVGNames::toAttr);
 }
 
 String SVGAnimationElement::byValue() const
 {    
-    return getAttribute(SVGNames::byAttr);
+    return attributeWithoutSynchronization(SVGNames::byAttr);
 }
 
 String SVGAnimationElement::fromValue() const
 {    
-    return getAttribute(SVGNames::fromAttr);
+    return attributeWithoutSynchronization(SVGNames::fromAttr);
 }
 
 bool SVGAnimationElement::isAdditive() const
 {
-    DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
-    const AtomicString& value = getAttribute(SVGNames::additiveAttr);
+    static NeverDestroyed<const AtomicString> sum("sum", AtomicString::ConstructFromLiteral);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::additiveAttr);
     return value == sum || animationMode() == ByAnimation;
 }
 
 bool SVGAnimationElement::isAccumulated() const
 {
-    DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
-    const AtomicString& value = getAttribute(SVGNames::accumulateAttr);
+    static NeverDestroyed<const AtomicString> sum("sum", AtomicString::ConstructFromLiteral);
+    const AtomicString& value = attributeWithoutSynchronization(SVGNames::accumulateAttr);
     return value == sum && animationMode() != ToAnimation;
 }
 
-bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
+bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* element, const QualifiedName& attributeName)
 {
-    ASSERT(targetElement);
-    if (!targetElement->isStyled())
+    if (element->isTextContent()
+        && (attributeName == SVGNames::xAttr || attributeName == SVGNames::yAttr))
         return false;
 
-    return SVGStyledElement::isAnimatableCSSProperty(attributeName);
+    return SVGElement::isAnimatableCSSProperty(attributeName);
 }
 
-void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
+SVGAnimationElement::ShouldApplyAnimation SVGAnimationElement::shouldApplyAnimation(SVGElement* targetElement, const QualifiedName& attributeName)
 {
-    if (!hasValidAttributeType())
-        return;
-    SVGElement* targetElement = this->targetElement();
-    QualifiedName attributeName = this->attributeName();
-    if (!targetElement || attributeName == anyQName() || value.isNull())
-        return;
+    if (!hasValidAttributeType() || !targetElement || attributeName == anyQName())
+        return DontApplyAnimation;
+
+    // Always animate CSS properties, using the ApplyCSSAnimation code path, regardless of the attributeType value.
+    if (isTargetAttributeCSSProperty(targetElement, attributeName)) {
+        if (targetElement->isPresentationAttributeWithSVGDOM(attributeName))
+            return ApplyXMLandCSSAnimation;
+        return ApplyCSSAnimation;
+    }
 
-    // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
-    if (targetElement->isStyled())
-        static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(true);
-        
-    bool attributeIsCSSProperty = isTargetAttributeCSSProperty(targetElement, attributeName);
-    // Stop animation, if attributeType is set to CSS by the user, but the attribute itself is not a CSS property.
-    if (!attributeIsCSSProperty && attributeType() == AttributeTypeCSS)
-        return;
 
-    ExceptionCode ec;
-    if (attributeIsCSSProperty) {
-        // FIXME: This should set the override style, not the inline style.
-        // Sadly override styles are not yet implemented.
-        targetElement->style()->setProperty(attributeName.localName(), value, "", ec);
-    } else {
-        // FIXME: This should set the 'presentation' value, not the actual 
-        // attribute value. Whatever that means in practice.
-        targetElement->setAttribute(attributeName, value, ec);
-    }
-    
-    if (targetElement->isStyled())
-        static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(false);
-    
-    // If the target element is used in an <use> instance tree, update that as well.
-    const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
-    const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
-    for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
-        SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
-        if (!shadowTreeElement)
-            continue;
-        if (attributeIsCSSProperty)
-            shadowTreeElement->style()->setProperty(attributeName.localName(), value, "", ec);
-        else
-            shadowTreeElement->setAttribute(attributeName, value, ec);
-        (*it)->correspondingUseElement()->setNeedsStyleRecalc();
-    }
+    // If attributeType="CSS" and attributeName doesn't point to a CSS property, ignore the animation.
+    if (attributeType() == AttributeType::CSS)
+        return DontApplyAnimation;
+
+    return ApplyXMLAnimation;
 }
-    
+
 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
 {
-    ASSERT(calcMode() == CalcModePaced);
+    ASSERT(calcMode() == CalcMode::Paced);
     ASSERT(animationMode() == ValuesAnimation);
 
     unsigned valuesCount = m_values.size();
-    ASSERT(valuesCount > 1);
+    ASSERT(valuesCount >= 1);
+    if (valuesCount == 1)
+        return;
+
+    // FIXME, webkit.org/b/109010: m_keyTimes should not be modified in this function.
+    m_keyTimes.clear();
+
     Vector<float> keyTimesForPaced;
     float totalDistance = 0;
     keyTimesForPaced.append(0);
@@ -411,7 +403,7 @@ void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
     keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
 
     // Use key times calculated based on pacing instead of the user provided ones.
-    m_keyTimes.swap(keyTimesForPaced);
+    m_keyTimes = keyTimesForPaced;
 }
 
 static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
@@ -420,8 +412,11 @@ unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
 {
     unsigned index;
     unsigned keyTimesCount = m_keyTimes.size();
-    for (index = 1; index < keyTimesCount; ++index) {
-        if (m_keyTimes[index] >= percent)
+    // Compare index + 1 to keyTimesCount because the last keyTimes entry is
+    // required to be 1, and percent can never exceed 1; i.e., the second last
+    // keyTimes entry defines the beginning of the final interval
+    for (index = 1; index + 1 < keyTimesCount; ++index) {
+        if (m_keyTimes[index] > percent)
             break;
     }
     return --index;
@@ -429,8 +424,8 @@ unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
 
 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
 {
-    ASSERT(calcMode() == CalcModeSpline);
-    ASSERT(splineIndex < m_keySplines.size());
+    ASSERT(calcMode() == CalcMode::Spline);
+    ASSERT_WITH_SECURITY_IMPLICATION(splineIndex < m_keySplines.size());
     UnitBezier bezier = m_keySplines[splineIndex];
     SMILTime duration = simpleDuration();
     if (!duration.isFinite())
@@ -441,47 +436,71 @@ float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned spl
 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
 {
     ASSERT(!m_keyPoints.isEmpty());
-    ASSERT(calcMode() != CalcModePaced);
+    ASSERT(calcMode() != CalcMode::Paced);
     ASSERT(m_keyTimes.size() > 1);
     ASSERT(m_keyPoints.size() == m_keyTimes.size());
 
+    if (percent == 1)
+        return m_keyPoints[m_keyPoints.size() - 1];
+
     unsigned index = calculateKeyTimesIndex(percent);
     float fromPercent = m_keyTimes[index];
     float toPercent = m_keyTimes[index + 1];
     float fromKeyPoint = m_keyPoints[index];
     float toKeyPoint = m_keyPoints[index + 1];
     
-    if (calcMode() == CalcModeDiscrete)
-        return percent == 1 ? toKeyPoint : fromKeyPoint;
+    if (calcMode() == CalcMode::Discrete)
+        return fromKeyPoint;
     
-    float keyPointPercent = percent == 1 ? 1 : (percent - fromPercent) / (toPercent - fromPercent);
+    float keyPointPercent = (percent - fromPercent) / (toPercent - fromPercent);
     
-    if (calcMode() == CalcModeSpline) {
+    if (calcMode() == CalcMode::Spline) {
         ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
         keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
     }
     return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
 }
+
+float SVGAnimationElement::calculatePercentForFromTo(float percent) const
+{
+    if (calcMode() == CalcMode::Discrete && m_keyTimes.size() == 2)
+        return percent > m_keyTimes[1] ? 1 : 0;
+
+    return percent;
+}
     
 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
 {
     ASSERT(!m_keyPoints.isEmpty());
     ASSERT(m_keyPoints.size() == m_keyTimes.size());
-    ASSERT(calcMode() != CalcModePaced);
+    ASSERT(calcMode() != CalcMode::Paced);
     effectivePercent = calculatePercentFromKeyPoints(percent);
     unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
     from = m_values[index];
     to = m_values[index + 1];
 }
     
-void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const
+void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to)
 {
     unsigned valuesCount = m_values.size();
     ASSERT(m_animationValid);
-    ASSERT(valuesCount > 1);
-    
+    ASSERT(valuesCount >= 1);
+
+    if (percent == 1 || valuesCount == 1) {
+        from = m_values[valuesCount - 1];
+        to = m_values[valuesCount - 1];
+        effectivePercent = 1;
+        return;
+    }
+
     CalcMode calcMode = this->calcMode();
-    if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
+    if (is<SVGAnimateElement>(*this) || is<SVGAnimateColorElement>(*this)) {
+        ASSERT(targetElement());
+        AnimatedPropertyType type = downcast<SVGAnimateElementBase>(*this).determineAnimatedPropertyType(*targetElement());
+        if (type == AnimatedBoolean || type == AnimatedEnumeration || type == AnimatedPreserveAspectRatio || type == AnimatedString)
+            calcMode = CalcMode::Discrete;
+    }
+    if (!m_keyPoints.isEmpty() && calcMode != CalcMode::Paced)
         return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
     
     unsigned keyTimesCount = m_keyTimes.size();
@@ -489,9 +508,9 @@ void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float&
     ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
 
     unsigned index = calculateKeyTimesIndex(percent);
-    if (calcMode == CalcModeDiscrete) {
+    if (calcMode == CalcMode::Discrete) {
         if (!keyTimesCount) 
-            index = percent == 1 ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
+            index = static_cast<unsigned>(percent * valuesCount);
         from = m_values[index];
         to = m_values[index];
         effectivePercent = 0;
@@ -504,7 +523,7 @@ void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float&
         fromPercent = m_keyTimes[index];
         toPercent = m_keyTimes[index + 1];
     } else {        
-        index = static_cast<unsigned>(percent * (valuesCount - 1));
+        index = static_cast<unsigned>(floorf(percent * (valuesCount - 1)));
         fromPercent =  static_cast<float>(index) / (valuesCount - 1);
         toPercent =  static_cast<float>(index + 1) / (valuesCount - 1);
     }
@@ -513,10 +532,10 @@ void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float&
         --index;
     from = m_values[index];
     to = m_values[index + 1];
-    ASSERT(toPercent > fromPercent);
-    effectivePercent = percent == 1 ? 1 : (percent - fromPercent) / (toPercent - fromPercent);
-    
-    if (calcMode == CalcModeSpline) {
+    ASSERT_WITH_SECURITY_IMPLICATION(toPercent > fromPercent);
+    effectivePercent = (percent - fromPercent) / (toPercent - fromPercent);
+
+    if (calcMode == CalcMode::Spline) {
         ASSERT(m_keySplines.size() == m_values.size() - 1);
         effectivePercent = calculatePercentForSpline(effectivePercent, index);
     }
@@ -530,15 +549,17 @@ void SVGAnimationElement::startedActiveInterval()
         return;
 
     // These validations are appropriate for all animation modes.
-    if (hasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
+    if (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
         return;
 
     AnimationMode animationMode = this->animationMode();
     CalcMode calcMode = this->calcMode();
-    if (calcMode == CalcModeSpline) {
-        unsigned splinesCount = m_keySplines.size() + 1;
-        if ((hasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != splinesCount)
-            || (animationMode == ValuesAnimation && m_values.size() != splinesCount))
+    if (calcMode == CalcMode::Spline) {
+        unsigned splinesCount = m_keySplines.size();
+        if (!splinesCount
+            || (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && m_keyPoints.size() - 1 != splinesCount)
+            || (animationMode == ValuesAnimation && m_values.size() - 1 != splinesCount)
+            || (hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && m_keyTimes.size() - 1 != splinesCount))
             return;
     }
 
@@ -547,36 +568,42 @@ void SVGAnimationElement::startedActiveInterval()
     String by = byValue();
     if (animationMode == NoAnimation)
         return;
+    if ((animationMode == FromToAnimation || animationMode == FromByAnimation || animationMode == ToAnimation || animationMode == ByAnimation)
+        && (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && (m_keyTimes.size() < 2 || m_keyTimes.size() != m_keyPoints.size())))
+        return;
     if (animationMode == FromToAnimation)
         m_animationValid = calculateFromAndToValues(from, to);
     else if (animationMode == ToAnimation) {
         // For to-animations the from value is the current accumulated value from lower priority animations.
         // The value is not static and is determined during the animation.
-        m_animationValid = calculateFromAndToValues(String(), to);
+        m_animationValid = calculateFromAndToValues(emptyString(), to);
     } else if (animationMode == FromByAnimation)
         m_animationValid = calculateFromAndByValues(from, by);
     else if (animationMode == ByAnimation)
-        m_animationValid = calculateFromAndByValues(String(), by);
+        m_animationValid = calculateFromAndByValues(emptyString(), by);
     else if (animationMode == ValuesAnimation) {
-        m_animationValid = m_values.size() > 1
-            && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
-            && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
-            && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
-            && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
-        if (calcMode == CalcModePaced && m_animationValid)
+        m_animationValid = m_values.size() >= 1
+            && (calcMode == CalcMode::Paced || !hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) || hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
+            && (calcMode == CalcMode::Discrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
+            && (calcMode != CalcMode::Spline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
+            && (!hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
+        if (m_animationValid)
+            m_animationValid = calculateToAtEndOfDurationValue(m_values.last());
+        if (calcMode == CalcMode::Paced && m_animationValid)
             calculateKeyTimesForCalcModePaced();
     } else if (animationMode == PathAnimation)
-        m_animationValid = calcMode == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
+        m_animationValid = calcMode == CalcMode::Paced || !hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
 }
-    
-void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
+
+void SVGAnimationElement::updateAnimation(float percent, unsigned repeatCount, SVGSMILElement* resultElement)
 {    
     if (!m_animationValid)
         return;
-    
+
     float effectivePercent;
-    CalcMode mode = calcMode();
-    if (animationMode() == ValuesAnimation) {
+    CalcMode calcMode = this->calcMode();
+    AnimationMode animationMode = this->animationMode();
+    if (animationMode == ValuesAnimation) {
         String from;
         String to;
         currentValuesForValuesAnimation(percent, effectivePercent, from, to);
@@ -587,20 +614,93 @@ void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMI
             m_lastValuesAnimationFrom = from;
             m_lastValuesAnimationTo = to;
         }
-    } else if (!m_keyPoints.isEmpty() && mode != CalcModePaced)
+    } else if (!m_keyPoints.isEmpty() && calcMode != CalcMode::Paced)
         effectivePercent = calculatePercentFromKeyPoints(percent);
-    else if (m_keyPoints.isEmpty() && mode == CalcModeSpline && m_keyTimes.size() > 1)
+    else if (m_keyPoints.isEmpty() && calcMode == CalcMode::Spline && m_keyTimes.size() > 1)
         effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
+    else if (animationMode == FromToAnimation || animationMode == ToAnimation)
+        effectivePercent = calculatePercentForFromTo(percent);
     else
         effectivePercent = percent;
 
-    calculateAnimatedValue(effectivePercent, repeat, resultElement);
+    calculateAnimatedValue(effectivePercent, repeatCount, resultElement);
+}
+
+void SVGAnimationElement::computeCSSPropertyValue(SVGElement* element, CSSPropertyID id, String& valueString)
+{
+    ASSERT(element);
+
+    // Don't include any properties resulting from CSS Transitions/Animations or SMIL animations, as we want to retrieve the "base value".
+    element->setUseOverrideComputedStyle(true);
+    RefPtr<CSSValue> value = ComputedStyleExtractor(element).propertyValue(id);
+    valueString = value ? value->cssText() : String();
+    element->setUseOverrideComputedStyle(false);
+}
+
+void SVGAnimationElement::adjustForInheritance(SVGElement* targetElement, const QualifiedName& attributeName, String& value)
+{
+    // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again.
+    // In the future we might want to work with the value type directly to avoid the String parsing.
+    ASSERT(targetElement);
+
+    Element* parent = targetElement->parentElement();
+    if (!parent || !parent->isSVGElement())
+        return;
+
+    SVGElement& svgParent = downcast<SVGElement>(*parent);
+    computeCSSPropertyValue(&svgParent, cssPropertyID(attributeName.localName()), value);
 }
 
-void SVGAnimationElement::endedActiveInterval()
+static bool inheritsFromProperty(SVGElement*, const QualifiedName& attributeName, const String& value)
 {
+    static NeverDestroyed<const AtomicString> inherit("inherit", AtomicString::ConstructFromLiteral);
+    
+    if (value.isEmpty() || value != inherit)
+        return false;
+    return SVGElement::isAnimatableCSSProperty(attributeName);
 }
 
+void SVGAnimationElement::determinePropertyValueTypes(const String& from, const String& to)
+{
+    SVGElement* targetElement = this->targetElement();
+    ASSERT(targetElement);
+
+    const QualifiedName& attributeName = this->attributeName();
+    if (inheritsFromProperty(targetElement, attributeName, from))
+        m_fromPropertyValueType = InheritValue;
+    if (inheritsFromProperty(targetElement, attributeName, to))
+        m_toPropertyValueType = InheritValue;
+}
+void SVGAnimationElement::resetAnimatedPropertyType()
+{
+    m_lastValuesAnimationFrom = String();
+    m_lastValuesAnimationTo = String();
 }
-#endif // ENABLE(SVG_ANIMATION)
 
+void SVGAnimationElement::setTargetElement(SVGElement* target)
+{
+    SVGSMILElement::setTargetElement(target);
+    checkInvalidCSSAttributeType(target);
+}
+
+void SVGAnimationElement::checkInvalidCSSAttributeType(SVGElement* target)
+{
+    m_hasInvalidCSSAttributeType = target && hasValidAttributeName() && attributeType() == AttributeType::CSS && !isTargetAttributeCSSProperty(target, attributeName());
+}
+
+Ref<SVGStringList> SVGAnimationElement::requiredFeatures()
+{
+    return SVGTests::requiredFeatures(*this);
+}
+
+Ref<SVGStringList> SVGAnimationElement::requiredExtensions()
+{ 
+    return SVGTests::requiredExtensions(*this);
+}
+
+Ref<SVGStringList> SVGAnimationElement::systemLanguage()
+{
+    return SVGTests::systemLanguage(*this);
+}
+
+}