2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2008 Apple Inc. All rights reserved.
6 * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
7 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
28 #include "SVGAnimationElement.h"
30 #include "Attribute.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "CSSPropertyNames.h"
35 #include "FloatConversion.h"
36 #include "RenderObject.h"
37 #include "SVGAnimateElement.h"
38 #include "SVGElementInstance.h"
40 #include "SVGParserUtilities.h"
41 #include "SVGStyledElement.h"
42 #include <wtf/MathExtras.h>
46 // Animated property definitions
47 DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
49 BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGAnimationElement)
50 REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
51 REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
52 END_REGISTER_ANIMATED_PROPERTIES
54 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* document)
55 : SVGSMILElement(tagName, document)
56 , m_fromPropertyValueType(RegularPropertyValue)
57 , m_toPropertyValueType(RegularPropertyValue)
58 , m_animationValid(false)
59 , m_attributeType(AttributeTypeAuto)
60 , m_hasInvalidCSSAttributeType(false)
61 , m_calcMode(CalcModeLinear)
62 , m_animationMode(NoAnimation)
64 registerAnimatedPropertiesForSVGAnimationElement();
67 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
70 Vector<String> parseList;
71 parse.split(';', parseList);
72 for (unsigned n = 0; n < parseList.size(); ++n) {
73 String timeString = parseList[n];
75 float time = timeString.toFloat(&ok);
76 if (!ok || time < 0 || time > 1)
82 } else if (time < result.last())
92 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
97 const UChar* cur = parse.characters();
98 const UChar* end = cur + parse.length();
100 skipOptionalSVGSpaces(cur, end);
102 bool delimParsed = false;
106 if (!parseNumber(cur, end, posA)) {
112 if (!parseNumber(cur, end, posB)) {
118 if (!parseNumber(cur, end, posC)) {
124 if (!parseNumber(cur, end, posD, false)) {
129 skipOptionalSVGSpaces(cur, end);
131 if (cur < end && *cur == ';') {
135 skipOptionalSVGSpaces(cur, end);
137 result.append(UnitBezier(posA, posB, posC, posD));
139 if (!(cur == end && !delimParsed))
143 bool SVGAnimationElement::isSupportedAttribute(const QualifiedName& attrName)
145 DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
146 if (supportedAttributes.isEmpty()) {
147 SVGTests::addSupportedAttributes(supportedAttributes);
148 SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
149 supportedAttributes.add(SVGNames::valuesAttr);
150 supportedAttributes.add(SVGNames::keyTimesAttr);
151 supportedAttributes.add(SVGNames::keyPointsAttr);
152 supportedAttributes.add(SVGNames::keySplinesAttr);
153 supportedAttributes.add(SVGNames::attributeTypeAttr);
154 supportedAttributes.add(SVGNames::calcModeAttr);
155 supportedAttributes.add(SVGNames::fromAttr);
156 supportedAttributes.add(SVGNames::toAttr);
157 supportedAttributes.add(SVGNames::byAttr);
159 return supportedAttributes.contains<QualifiedName, SVGAttributeHashTranslator>(attrName);
162 void SVGAnimationElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
164 if (!isSupportedAttribute(name)) {
165 SVGSMILElement::parseAttribute(name, value);
169 if (name == SVGNames::valuesAttr) {
170 // Per the SMIL specification, leading and trailing white space,
171 // and white space before and after semicolon separators, is allowed and will be ignored.
172 // http://www.w3.org/TR/SVG11/animate.html#ValuesAttribute
173 value.string().split(';', m_values);
174 for (unsigned i = 0; i < m_values.size(); ++i)
175 m_values[i] = m_values[i].stripWhiteSpace();
177 updateAnimationMode();
181 if (name == SVGNames::keyTimesAttr) {
182 parseKeyTimes(value, m_keyTimes, true);
186 if (name == SVGNames::keyPointsAttr) {
187 if (hasTagName(SVGNames::animateMotionTag)) {
188 // This is specified to be an animateMotion attribute only but it is simpler to put it here
189 // where the other timing calculatations are.
190 parseKeyTimes(value, m_keyPoints, false);
195 if (name == SVGNames::keySplinesAttr) {
196 parseKeySplines(value, m_keySplines);
200 if (name == SVGNames::attributeTypeAttr) {
201 setAttributeType(value);
205 if (name == SVGNames::calcModeAttr) {
210 if (name == SVGNames::fromAttr || name == SVGNames::toAttr || name == SVGNames::byAttr) {
211 updateAnimationMode();
215 if (SVGTests::parseAttribute(name, value))
217 if (SVGExternalResourcesRequired::parseAttribute(name, value))
220 ASSERT_NOT_REACHED();
223 void SVGAnimationElement::svgAttributeChanged(const QualifiedName& attrName)
225 if (!isSupportedAttribute(attrName)) {
226 SVGSMILElement::svgAttributeChanged(attrName);
230 animationAttributeChanged();
233 void SVGAnimationElement::animationAttributeChanged()
235 // Assumptions may not hold after an attribute change.
236 m_animationValid = false;
240 float SVGAnimationElement::getStartTime() const
242 return narrowPrecisionToFloat(intervalBegin().value());
245 float SVGAnimationElement::getCurrentTime() const
247 return narrowPrecisionToFloat(elapsed().value());
250 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
252 return narrowPrecisionToFloat(simpleDuration().value());
255 void SVGAnimationElement::beginElement()
260 void SVGAnimationElement::beginElementAt(float offset)
264 SMILTime elapsed = this->elapsed();
265 addBeginTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
268 void SVGAnimationElement::endElement()
273 void SVGAnimationElement::endElementAt(float offset)
277 SMILTime elapsed = this->elapsed();
278 addEndTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
281 void SVGAnimationElement::updateAnimationMode()
283 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
284 if (hasAttribute(SVGNames::valuesAttr))
285 setAnimationMode(ValuesAnimation);
286 else if (!toValue().isEmpty())
287 setAnimationMode(fromValue().isEmpty() ? ToAnimation : FromToAnimation);
288 else if (!byValue().isEmpty())
289 setAnimationMode(fromValue().isEmpty() ? ByAnimation : FromByAnimation);
291 setAnimationMode(NoAnimation);
294 void SVGAnimationElement::setCalcMode(const AtomicString& calcMode)
296 DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete", AtomicString::ConstructFromLiteral));
297 DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear", AtomicString::ConstructFromLiteral));
298 DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced", AtomicString::ConstructFromLiteral));
299 DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline", AtomicString::ConstructFromLiteral));
300 if (calcMode == discrete)
301 setCalcMode(CalcModeDiscrete);
302 else if (calcMode == linear)
303 setCalcMode(CalcModeLinear);
304 else if (calcMode == paced)
305 setCalcMode(CalcModePaced);
306 else if (calcMode == spline)
307 setCalcMode(CalcModeSpline);
309 setCalcMode(hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear);
312 void SVGAnimationElement::setAttributeType(const AtomicString& attributeType)
314 DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS", AtomicString::ConstructFromLiteral));
315 DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML", AtomicString::ConstructFromLiteral));
316 if (attributeType == css)
317 m_attributeType = AttributeTypeCSS;
318 else if (attributeType == xml)
319 m_attributeType = AttributeTypeXML;
321 m_attributeType = AttributeTypeAuto;
322 checkInvalidCSSAttributeType(targetElement());
325 String SVGAnimationElement::toValue() const
327 return fastGetAttribute(SVGNames::toAttr);
330 String SVGAnimationElement::byValue() const
332 return fastGetAttribute(SVGNames::byAttr);
335 String SVGAnimationElement::fromValue() const
337 return fastGetAttribute(SVGNames::fromAttr);
340 bool SVGAnimationElement::isAdditive() const
342 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum", AtomicString::ConstructFromLiteral));
343 const AtomicString& value = fastGetAttribute(SVGNames::additiveAttr);
344 return value == sum || animationMode() == ByAnimation;
347 bool SVGAnimationElement::isAccumulated() const
349 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum", AtomicString::ConstructFromLiteral));
350 const AtomicString& value = fastGetAttribute(SVGNames::accumulateAttr);
351 return value == sum && animationMode() != ToAnimation;
354 bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
356 ASSERT(targetElement);
357 if (!targetElement->isStyled())
360 return SVGStyledElement::isAnimatableCSSProperty(attributeName);
363 SVGAnimationElement::ShouldApplyAnimation SVGAnimationElement::shouldApplyAnimation(SVGElement* targetElement, const QualifiedName& attributeName)
365 if (!hasValidAttributeType() || !targetElement || attributeName == anyQName())
366 return DontApplyAnimation;
368 // Always animate CSS properties, using the ApplyCSSAnimation code path, regardless of the attributeType value.
369 if (isTargetAttributeCSSProperty(targetElement, attributeName))
370 return ApplyCSSAnimation;
372 // If attributeType="CSS" and attributeName doesn't point to a CSS property, ignore the animation.
373 if (attributeType() == AttributeTypeCSS)
374 return DontApplyAnimation;
376 return ApplyXMLAnimation;
379 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
381 ASSERT(calcMode() == CalcModePaced);
382 ASSERT(animationMode() == ValuesAnimation);
384 unsigned valuesCount = m_values.size();
385 ASSERT(valuesCount >= 1);
386 if (valuesCount == 1)
388 Vector<float> keyTimesForPaced;
389 float totalDistance = 0;
390 keyTimesForPaced.append(0);
391 for (unsigned n = 0; n < valuesCount - 1; ++n) {
392 // Distance in any units
393 float distance = calculateDistance(m_values[n], m_values[n + 1]);
396 totalDistance += distance;
397 keyTimesForPaced.append(distance);
403 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
404 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
405 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
407 // Use key times calculated based on pacing instead of the user provided ones.
408 m_keyTimes.swap(keyTimesForPaced);
411 static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
413 unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
416 unsigned keyTimesCount = m_keyTimes.size();
417 // Compare index + 1 to keyTimesCount because the last keyTimes entry is
418 // required to be 1, and percent can never exceed 1; i.e., the second last
419 // keyTimes entry defines the beginning of the final interval
420 for (index = 1; index + 1 < keyTimesCount; ++index) {
421 if (m_keyTimes[index] > percent)
427 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
429 ASSERT(calcMode() == CalcModeSpline);
430 ASSERT(splineIndex < m_keySplines.size());
431 UnitBezier bezier = m_keySplines[splineIndex];
432 SMILTime duration = simpleDuration();
433 if (!duration.isFinite())
435 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
438 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
440 ASSERT(!m_keyPoints.isEmpty());
441 ASSERT(calcMode() != CalcModePaced);
442 ASSERT(m_keyTimes.size() > 1);
443 ASSERT(m_keyPoints.size() == m_keyTimes.size());
446 return m_keyPoints[m_keyPoints.size() - 1];
448 unsigned index = calculateKeyTimesIndex(percent);
449 float fromPercent = m_keyTimes[index];
450 float toPercent = m_keyTimes[index + 1];
451 float fromKeyPoint = m_keyPoints[index];
452 float toKeyPoint = m_keyPoints[index + 1];
454 if (calcMode() == CalcModeDiscrete)
457 float keyPointPercent = (percent - fromPercent) / (toPercent - fromPercent);
459 if (calcMode() == CalcModeSpline) {
460 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
461 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
463 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
466 float SVGAnimationElement::calculatePercentForFromTo(float percent) const
468 if (calcMode() == CalcModeDiscrete && m_keyTimes.size() == 2)
469 return percent > m_keyTimes[1] ? 1 : 0;
474 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
476 ASSERT(!m_keyPoints.isEmpty());
477 ASSERT(m_keyPoints.size() == m_keyTimes.size());
478 ASSERT(calcMode() != CalcModePaced);
479 effectivePercent = calculatePercentFromKeyPoints(percent);
480 unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
481 from = m_values[index];
482 to = m_values[index + 1];
485 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to)
487 unsigned valuesCount = m_values.size();
488 ASSERT(m_animationValid);
489 ASSERT(valuesCount >= 1);
491 if (percent == 1 || valuesCount == 1) {
492 from = m_values[valuesCount - 1];
493 to = m_values[valuesCount - 1];
494 effectivePercent = 1;
498 CalcMode calcMode = this->calcMode();
499 if (hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::animateColorTag)) {
500 SVGAnimateElement* animateElement = static_cast<SVGAnimateElement*>(this);
501 AnimatedPropertyType attributeType = animateElement->determineAnimatedPropertyType(targetElement());
502 // Fall back to discrete animations for Strings.
503 if (attributeType == AnimatedBoolean
504 || attributeType == AnimatedEnumeration
505 || attributeType == AnimatedPreserveAspectRatio
506 || attributeType == AnimatedString)
507 calcMode = CalcModeDiscrete;
509 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
510 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
512 unsigned keyTimesCount = m_keyTimes.size();
513 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
514 ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
516 unsigned index = calculateKeyTimesIndex(percent);
517 if (calcMode == CalcModeDiscrete) {
519 index = static_cast<unsigned>(percent * valuesCount);
520 from = m_values[index];
521 to = m_values[index];
522 effectivePercent = 0;
529 fromPercent = m_keyTimes[index];
530 toPercent = m_keyTimes[index + 1];
532 index = static_cast<unsigned>(floorf(percent * (valuesCount - 1)));
533 fromPercent = static_cast<float>(index) / (valuesCount - 1);
534 toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
537 if (index == valuesCount - 1)
539 from = m_values[index];
540 to = m_values[index + 1];
541 ASSERT(toPercent > fromPercent);
542 effectivePercent = (percent - fromPercent) / (toPercent - fromPercent);
544 if (calcMode == CalcModeSpline) {
545 ASSERT(m_keySplines.size() == m_values.size() - 1);
546 effectivePercent = calculatePercentForSpline(effectivePercent, index);
550 void SVGAnimationElement::startedActiveInterval()
552 m_animationValid = false;
554 if (!hasValidAttributeType())
557 // These validations are appropriate for all animation modes.
558 if (fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
561 AnimationMode animationMode = this->animationMode();
562 CalcMode calcMode = this->calcMode();
563 if (calcMode == CalcModeSpline) {
564 unsigned splinesCount = m_keySplines.size() + 1;
565 if ((fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != splinesCount)
566 || (animationMode == ValuesAnimation && m_values.size() != splinesCount)
567 || (fastHasAttribute(SVGNames::keyTimesAttr) && m_keyTimes.size() != splinesCount))
571 String from = fromValue();
572 String to = toValue();
573 String by = byValue();
574 if (animationMode == NoAnimation)
576 if (animationMode == FromToAnimation)
577 m_animationValid = calculateFromAndToValues(from, to);
578 else if (animationMode == ToAnimation) {
579 // For to-animations the from value is the current accumulated value from lower priority animations.
580 // The value is not static and is determined during the animation.
581 m_animationValid = calculateFromAndToValues(emptyString(), to);
582 } else if (animationMode == FromByAnimation)
583 m_animationValid = calculateFromAndByValues(from, by);
584 else if (animationMode == ByAnimation)
585 m_animationValid = calculateFromAndByValues(emptyString(), by);
586 else if (animationMode == ValuesAnimation) {
587 m_animationValid = m_values.size() >= 1
588 && (calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyTimesAttr) || fastHasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
589 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
590 && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
591 && (!fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
592 if (m_animationValid)
593 m_animationValid = calculateToAtEndOfDurationValue(m_values.last());
594 if (calcMode == CalcModePaced && m_animationValid)
595 calculateKeyTimesForCalcModePaced();
596 } else if (animationMode == PathAnimation)
597 m_animationValid = calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
600 void SVGAnimationElement::updateAnimation(float percent, unsigned repeatCount, SVGSMILElement* resultElement)
602 if (!m_animationValid)
605 float effectivePercent;
606 CalcMode calcMode = this->calcMode();
607 AnimationMode animationMode = this->animationMode();
608 if (animationMode == ValuesAnimation) {
611 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
612 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
613 m_animationValid = calculateFromAndToValues(from, to);
614 if (!m_animationValid)
616 m_lastValuesAnimationFrom = from;
617 m_lastValuesAnimationTo = to;
619 } else if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
620 effectivePercent = calculatePercentFromKeyPoints(percent);
621 else if (m_keyPoints.isEmpty() && calcMode == CalcModeSpline && m_keyTimes.size() > 1)
622 effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
623 else if (animationMode == FromToAnimation || animationMode == ToAnimation)
624 effectivePercent = calculatePercentForFromTo(percent);
626 effectivePercent = percent;
628 calculateAnimatedValue(effectivePercent, repeatCount, resultElement);
631 void SVGAnimationElement::computeCSSPropertyValue(SVGElement* element, CSSPropertyID id, String& value)
634 ASSERT(element->isStyled());
636 // Don't include any properties resulting from CSS Transitions/Animations or SMIL animations, as we want to retrieve the "base value".
637 element->setUseOverrideComputedStyle(true);
638 value = CSSComputedStyleDeclaration::create(element)->getPropertyValue(id);
639 element->setUseOverrideComputedStyle(false);
642 void SVGAnimationElement::adjustForInheritance(SVGElement* targetElement, const QualifiedName& attributeName, String& value)
644 // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again.
645 // In the future we might want to work with the value type directly to avoid the String parsing.
646 ASSERT(targetElement);
648 Element* parent = targetElement->parentElement();
649 if (!parent || !parent->isSVGElement())
652 SVGElement* svgParent = static_cast<SVGElement*>(parent);
653 if (!svgParent->isStyled())
655 computeCSSPropertyValue(svgParent, cssPropertyID(attributeName.localName()), value);
658 static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value)
660 ASSERT(targetElement);
661 DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit", AtomicString::ConstructFromLiteral));
663 if (value.isEmpty() || value != inherit || !targetElement->isStyled())
665 return SVGStyledElement::isAnimatableCSSProperty(attributeName);
668 void SVGAnimationElement::determinePropertyValueTypes(const String& from, const String& to)
670 SVGElement* targetElement = this->targetElement();
671 ASSERT(targetElement);
673 const QualifiedName& attributeName = this->attributeName();
674 if (inheritsFromProperty(targetElement, attributeName, from))
675 m_fromPropertyValueType = InheritValue;
676 if (inheritsFromProperty(targetElement, attributeName, to))
677 m_toPropertyValueType = InheritValue;
680 void SVGAnimationElement::setTargetElement(SVGElement* target)
682 SVGSMILElement::setTargetElement(target);
683 checkInvalidCSSAttributeType(target);
686 void SVGAnimationElement::setAttributeName(const QualifiedName& attributeName)
688 SVGSMILElement::setAttributeName(attributeName);
689 checkInvalidCSSAttributeType(targetElement());
692 void SVGAnimationElement::checkInvalidCSSAttributeType(SVGElement* target)
694 m_hasInvalidCSSAttributeType = target && hasValidAttributeName() && attributeType() == AttributeTypeCSS && !isTargetAttributeCSSProperty(target, attributeName());
699 #endif // ENABLE(SVG)