1/*
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, 2017 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.
8 * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26#include "config.h"
27#include "SVGAnimationElement.h"
28
29#include "CSSComputedStyleDeclaration.h"
30#include "CSSPropertyNames.h"
31#include "CSSPropertyParser.h"
32#include "Document.h"
33#include "FloatConversion.h"
34#include "RenderObject.h"
35#include "SVGAnimateColorElement.h"
36#include "SVGAnimateElement.h"
37#include "SVGElement.h"
38#include "SVGNames.h"
39#include "SVGParserUtilities.h"
40#include "SVGStringList.h"
41#include <wtf/IsoMallocInlines.h>
42#include <wtf/MathExtras.h>
43#include <wtf/NeverDestroyed.h>
44#include <wtf/text/StringView.h>
45
46namespace WebCore {
47
48WTF_MAKE_ISO_ALLOCATED_IMPL(SVGAnimationElement);
49
50SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document& document)
51 : SVGSMILElement(tagName, document)
52 , SVGExternalResourcesRequired(this)
53 , SVGTests(this)
54{
55}
56
57static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
58{
59 result.clear();
60 bool isFirst = true;
61 for (StringView timeString : StringView(parse).split(';')) {
62 bool ok;
63 float time = timeString.toFloat(ok);
64 if (!ok || time < 0 || time > 1)
65 goto fail;
66 if (verifyOrder) {
67 if (isFirst) {
68 if (time)
69 goto fail;
70 isFirst = false;
71 } else if (time < result.last())
72 goto fail;
73 }
74 result.append(time);
75 }
76 return;
77fail:
78 result.clear();
79}
80
81static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
82{
83 result.clear();
84 if (parse.isEmpty())
85 return;
86
87 auto upconvertedCharacters = StringView(parse).upconvertedCharacters();
88 const UChar* cur = upconvertedCharacters;
89 const UChar* end = cur + parse.length();
90
91 skipOptionalSVGSpaces(cur, end);
92
93 bool delimParsed = false;
94 while (cur < end) {
95 delimParsed = false;
96 float posA = 0;
97 if (!parseNumber(cur, end, posA)) {
98 result.clear();
99 return;
100 }
101
102 float posB = 0;
103 if (!parseNumber(cur, end, posB)) {
104 result.clear();
105 return;
106 }
107
108 float posC = 0;
109 if (!parseNumber(cur, end, posC)) {
110 result.clear();
111 return;
112 }
113
114 float posD = 0;
115 if (!parseNumber(cur, end, posD, false)) {
116 result.clear();
117 return;
118 }
119
120 skipOptionalSVGSpaces(cur, end);
121
122 if (cur < end && *cur == ';') {
123 delimParsed = true;
124 cur++;
125 }
126 skipOptionalSVGSpaces(cur, end);
127
128 result.append(UnitBezier(posA, posB, posC, posD));
129 }
130 if (!(cur == end && !delimParsed))
131 result.clear();
132}
133
134bool SVGAnimationElement::isSupportedAttribute(const QualifiedName& attrName)
135{
136 static const auto supportedAttributes = makeNeverDestroyed([] {
137 HashSet<QualifiedName> set;
138 SVGTests::addSupportedAttributes(set);
139 SVGExternalResourcesRequired::addSupportedAttributes(set);
140 set.add({
141 SVGNames::valuesAttr.get(),
142 SVGNames::keyTimesAttr.get(),
143 SVGNames::keyPointsAttr.get(),
144 SVGNames::keySplinesAttr.get(),
145 SVGNames::attributeTypeAttr.get(),
146 SVGNames::calcModeAttr.get(),
147 SVGNames::fromAttr.get(),
148 SVGNames::toAttr.get(),
149 SVGNames::byAttr.get(),
150 });
151 return set;
152 }());
153 return supportedAttributes.get().contains<SVGAttributeHashTranslator>(attrName);
154}
155
156void SVGAnimationElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
157{
158 if (name == SVGNames::valuesAttr) {
159 // Per the SMIL specification, leading and trailing white space,
160 // and white space before and after semicolon separators, is allowed and will be ignored.
161 // http://www.w3.org/TR/SVG11/animate.html#ValuesAttribute
162 m_values = value.string().split(';');
163 for (auto& value : m_values)
164 value = value.stripWhiteSpace();
165
166 updateAnimationMode();
167 return;
168 }
169
170 if (name == SVGNames::keyTimesAttr) {
171 parseKeyTimes(value, m_keyTimes, true);
172 return;
173 }
174
175 if (name == SVGNames::keyPointsAttr) {
176 if (hasTagName(SVGNames::animateMotionTag)) {
177 // This is specified to be an animateMotion attribute only but it is simpler to put it here
178 // where the other timing calculatations are.
179 parseKeyTimes(value, m_keyPoints, false);
180 }
181 return;
182 }
183
184 if (name == SVGNames::keySplinesAttr) {
185 parseKeySplines(value, m_keySplines);
186 return;
187 }
188
189 if (name == SVGNames::attributeTypeAttr) {
190 setAttributeType(value);
191 return;
192 }
193
194 if (name == SVGNames::calcModeAttr) {
195 setCalcMode(value);
196 return;
197 }
198
199 if (name == SVGNames::fromAttr || name == SVGNames::toAttr || name == SVGNames::byAttr) {
200 updateAnimationMode();
201 return;
202 }
203
204 SVGSMILElement::parseAttribute(name, value);
205 SVGTests::parseAttribute(name, value);
206 SVGExternalResourcesRequired::parseAttribute(name, value);
207}
208
209void SVGAnimationElement::svgAttributeChanged(const QualifiedName& attrName)
210{
211 if (!isSupportedAttribute(attrName)) {
212 SVGSMILElement::svgAttributeChanged(attrName);
213 return;
214 }
215
216 animationAttributeChanged();
217}
218
219void SVGAnimationElement::animationAttributeChanged()
220{
221 // Assumptions may not hold after an attribute change.
222 m_animationValid = false;
223 setInactive();
224}
225
226float SVGAnimationElement::getStartTime() const
227{
228 return narrowPrecisionToFloat(intervalBegin().value());
229}
230
231float SVGAnimationElement::getCurrentTime() const
232{
233 return narrowPrecisionToFloat(elapsed().value());
234}
235
236float SVGAnimationElement::getSimpleDuration() const
237{
238 return narrowPrecisionToFloat(simpleDuration().value());
239}
240
241void SVGAnimationElement::beginElement()
242{
243 beginElementAt(0);
244}
245
246void SVGAnimationElement::beginElementAt(float offset)
247{
248 if (std::isnan(offset))
249 return;
250 SMILTime elapsed = this->elapsed();
251 addBeginTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
252}
253
254void SVGAnimationElement::endElement()
255{
256 endElementAt(0);
257}
258
259void SVGAnimationElement::endElementAt(float offset)
260{
261 if (std::isnan(offset))
262 return;
263 SMILTime elapsed = this->elapsed();
264 addEndTime(elapsed, elapsed + offset, SMILTimeWithOrigin::ScriptOrigin);
265}
266
267void SVGAnimationElement::updateAnimationMode()
268{
269 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
270 if (hasAttribute(SVGNames::valuesAttr))
271 setAnimationMode(AnimationMode::Values);
272 else if (!toValue().isEmpty())
273 setAnimationMode(fromValue().isEmpty() ? AnimationMode::To : AnimationMode::FromTo);
274 else if (!byValue().isEmpty())
275 setAnimationMode(fromValue().isEmpty() ? AnimationMode::By : AnimationMode::FromBy);
276 else
277 setAnimationMode(AnimationMode::None);
278}
279
280void SVGAnimationElement::setCalcMode(const AtomicString& calcMode)
281{
282 static NeverDestroyed<const AtomicString> discrete("discrete", AtomicString::ConstructFromLiteral);
283 static NeverDestroyed<const AtomicString> linear("linear", AtomicString::ConstructFromLiteral);
284 static NeverDestroyed<const AtomicString> paced("paced", AtomicString::ConstructFromLiteral);
285 static NeverDestroyed<const AtomicString> spline("spline", AtomicString::ConstructFromLiteral);
286 if (calcMode == discrete)
287 setCalcMode(CalcMode::Discrete);
288 else if (calcMode == linear)
289 setCalcMode(CalcMode::Linear);
290 else if (calcMode == paced)
291 setCalcMode(CalcMode::Paced);
292 else if (calcMode == spline)
293 setCalcMode(CalcMode::Spline);
294 else
295 setCalcMode(hasTagName(SVGNames::animateMotionTag) ? CalcMode::Paced : CalcMode::Linear);
296}
297
298void SVGAnimationElement::setAttributeType(const AtomicString& attributeType)
299{
300 static NeverDestroyed<const AtomicString> css("CSS", AtomicString::ConstructFromLiteral);
301 static NeverDestroyed<const AtomicString> xml("XML", AtomicString::ConstructFromLiteral);
302 if (attributeType == css)
303 m_attributeType = AttributeType::CSS;
304 else if (attributeType == xml)
305 m_attributeType = AttributeType::XML;
306 else
307 m_attributeType = AttributeType::Auto;
308}
309
310String SVGAnimationElement::toValue() const
311{
312 return attributeWithoutSynchronization(SVGNames::toAttr);
313}
314
315String SVGAnimationElement::byValue() const
316{
317 return attributeWithoutSynchronization(SVGNames::byAttr);
318}
319
320String SVGAnimationElement::fromValue() const
321{
322 return attributeWithoutSynchronization(SVGNames::fromAttr);
323}
324
325bool SVGAnimationElement::isAdditive() const
326{
327 static NeverDestroyed<const AtomicString> sum("sum", AtomicString::ConstructFromLiteral);
328 const AtomicString& value = attributeWithoutSynchronization(SVGNames::additiveAttr);
329 return value == sum || animationMode() == AnimationMode::By;
330}
331
332bool SVGAnimationElement::isAccumulated() const
333{
334 static NeverDestroyed<const AtomicString> sum("sum", AtomicString::ConstructFromLiteral);
335 const AtomicString& value = attributeWithoutSynchronization(SVGNames::accumulateAttr);
336 return value == sum && animationMode() != AnimationMode::To;
337}
338
339bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
340{
341 return targetElement->isAnimatedStyleAttribute(attributeName);
342}
343
344void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
345{
346 ASSERT(calcMode() == CalcMode::Paced);
347 ASSERT(animationMode() == AnimationMode::Values);
348
349 unsigned valuesCount = m_values.size();
350 ASSERT(valuesCount >= 1);
351 if (valuesCount == 1)
352 return;
353
354 // FIXME, webkit.org/b/109010: m_keyTimes should not be modified in this function.
355 m_keyTimes.clear();
356
357 Vector<float> keyTimesForPaced;
358 float totalDistance = 0;
359 keyTimesForPaced.append(0);
360 for (unsigned n = 0; n < valuesCount - 1; ++n) {
361 // Distance in any units
362 auto distance = calculateDistance(m_values[n], m_values[n + 1]);
363 if (!distance)
364 return;
365 totalDistance += *distance;
366 keyTimesForPaced.append(*distance);
367 }
368 if (!totalDistance)
369 return;
370
371 // Normalize.
372 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
373 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
374 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
375
376 // Use key times calculated based on pacing instead of the user provided ones.
377 m_keyTimes = keyTimesForPaced;
378}
379
380static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
381
382unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
383{
384 unsigned index;
385 unsigned keyTimesCount = m_keyTimes.size();
386 // Compare index + 1 to keyTimesCount because the last keyTimes entry is
387 // required to be 1, and percent can never exceed 1; i.e., the second last
388 // keyTimes entry defines the beginning of the final interval
389 for (index = 1; index + 1 < keyTimesCount; ++index) {
390 if (m_keyTimes[index] > percent)
391 break;
392 }
393 return --index;
394}
395
396float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
397{
398 ASSERT(calcMode() == CalcMode::Spline);
399 ASSERT_WITH_SECURITY_IMPLICATION(splineIndex < m_keySplines.size());
400 UnitBezier bezier = m_keySplines[splineIndex];
401 SMILTime duration = simpleDuration();
402 if (!duration.isFinite())
403 duration = 100.0;
404 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
405}
406
407float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
408{
409 ASSERT(!m_keyPoints.isEmpty());
410 ASSERT(calcMode() != CalcMode::Paced);
411 ASSERT(m_keyTimes.size() > 1);
412 ASSERT(m_keyPoints.size() == m_keyTimes.size());
413
414 if (percent == 1)
415 return m_keyPoints[m_keyPoints.size() - 1];
416
417 unsigned index = calculateKeyTimesIndex(percent);
418 float fromPercent = m_keyTimes[index];
419 float toPercent = m_keyTimes[index + 1];
420 float fromKeyPoint = m_keyPoints[index];
421 float toKeyPoint = m_keyPoints[index + 1];
422
423 if (calcMode() == CalcMode::Discrete)
424 return fromKeyPoint;
425
426 float keyPointPercent = (percent - fromPercent) / (toPercent - fromPercent);
427
428 if (calcMode() == CalcMode::Spline) {
429 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
430 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
431 }
432 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
433}
434
435float SVGAnimationElement::calculatePercentForFromTo(float percent) const
436{
437 if (calcMode() == CalcMode::Discrete && m_keyTimes.size() == 2)
438 return percent > m_keyTimes[1] ? 1 : 0;
439
440 return percent;
441}
442
443void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
444{
445 ASSERT(!m_keyPoints.isEmpty());
446 ASSERT(m_keyPoints.size() == m_keyTimes.size());
447 ASSERT(calcMode() != CalcMode::Paced);
448 effectivePercent = calculatePercentFromKeyPoints(percent);
449 unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
450 from = m_values[index];
451 to = m_values[index + 1];
452}
453
454void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to)
455{
456 unsigned valuesCount = m_values.size();
457 ASSERT(m_animationValid);
458 ASSERT(valuesCount >= 1);
459
460 if (percent == 1 || valuesCount == 1) {
461 from = m_values[valuesCount - 1];
462 to = m_values[valuesCount - 1];
463 effectivePercent = 1;
464 return;
465 }
466
467 CalcMode calcMode = this->calcMode();
468 if (is<SVGAnimateElement>(*this) || is<SVGAnimateColorElement>(*this)) {
469 ASSERT(targetElement());
470 if (downcast<SVGAnimateElementBase>(*this).isDiscreteAnimator())
471 calcMode = CalcMode::Discrete;
472 }
473 if (!m_keyPoints.isEmpty() && calcMode != CalcMode::Paced)
474 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
475
476 unsigned keyTimesCount = m_keyTimes.size();
477 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
478 ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
479
480 unsigned index = calculateKeyTimesIndex(percent);
481 if (calcMode == CalcMode::Discrete) {
482 if (!keyTimesCount)
483 index = static_cast<unsigned>(percent * valuesCount);
484 from = m_values[index];
485 to = m_values[index];
486 effectivePercent = 0;
487 return;
488 }
489
490 float fromPercent;
491 float toPercent;
492 if (keyTimesCount) {
493 fromPercent = m_keyTimes[index];
494 toPercent = m_keyTimes[index + 1];
495 } else {
496 index = static_cast<unsigned>(floorf(percent * (valuesCount - 1)));
497 fromPercent = static_cast<float>(index) / (valuesCount - 1);
498 toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
499 }
500
501 if (index == valuesCount - 1)
502 --index;
503 from = m_values[index];
504 to = m_values[index + 1];
505 ASSERT_WITH_SECURITY_IMPLICATION(toPercent > fromPercent);
506 effectivePercent = (percent - fromPercent) / (toPercent - fromPercent);
507
508 if (calcMode == CalcMode::Spline) {
509 ASSERT(m_keySplines.size() == m_values.size() - 1);
510 effectivePercent = calculatePercentForSpline(effectivePercent, index);
511 }
512}
513
514void SVGAnimationElement::startedActiveInterval()
515{
516 m_animationValid = false;
517
518 if (!hasValidAttributeType())
519 return;
520
521 // These validations are appropriate for all animation modes.
522 if (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
523 return;
524
525 AnimationMode animationMode = this->animationMode();
526 CalcMode calcMode = this->calcMode();
527 if (calcMode == CalcMode::Spline) {
528 unsigned splinesCount = m_keySplines.size();
529 if (!splinesCount
530 || (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && m_keyPoints.size() - 1 != splinesCount)
531 || (animationMode == AnimationMode::Values && m_values.size() - 1 != splinesCount)
532 || (hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && m_keyTimes.size() - 1 != splinesCount))
533 return;
534 }
535
536 String from = fromValue();
537 String to = toValue();
538 String by = byValue();
539 if (animationMode == AnimationMode::None)
540 return;
541 if ((animationMode == AnimationMode::FromTo || animationMode == AnimationMode::FromBy || animationMode == AnimationMode::To || animationMode == AnimationMode::By)
542 && (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && (m_keyTimes.size() < 2 || m_keyTimes.size() != m_keyPoints.size())))
543 return;
544 if (animationMode == AnimationMode::FromTo)
545 m_animationValid = calculateFromAndToValues(from, to);
546 else if (animationMode == AnimationMode::To) {
547 // For to-animations the from value is the current accumulated value from lower priority animations.
548 // The value is not static and is determined during the animation.
549 m_animationValid = calculateFromAndToValues(emptyString(), to);
550 } else if (animationMode == AnimationMode::FromBy)
551 m_animationValid = calculateFromAndByValues(from, by);
552 else if (animationMode == AnimationMode::By)
553 m_animationValid = calculateFromAndByValues(emptyString(), by);
554 else if (animationMode == AnimationMode::Values) {
555 m_animationValid = m_values.size() >= 1
556 && (calcMode == CalcMode::Paced || !hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) || hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
557 && (calcMode == CalcMode::Discrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
558 && (calcMode != CalcMode::Spline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
559 && (!hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
560 if (m_animationValid)
561 m_animationValid = calculateToAtEndOfDurationValue(m_values.last());
562 if (calcMode == CalcMode::Paced && m_animationValid)
563 calculateKeyTimesForCalcModePaced();
564 } else if (animationMode == AnimationMode::Path)
565 m_animationValid = calcMode == CalcMode::Paced || !hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
566}
567
568void SVGAnimationElement::updateAnimation(float percent, unsigned repeatCount, SVGSMILElement* resultElement)
569{
570 if (!m_animationValid)
571 return;
572
573 float effectivePercent;
574 CalcMode calcMode = this->calcMode();
575 AnimationMode animationMode = this->animationMode();
576 if (animationMode == AnimationMode::Values) {
577 String from;
578 String to;
579 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
580 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
581 m_animationValid = calculateFromAndToValues(from, to);
582 if (!m_animationValid)
583 return;
584 m_lastValuesAnimationFrom = from;
585 m_lastValuesAnimationTo = to;
586 }
587 } else if (!m_keyPoints.isEmpty() && calcMode != CalcMode::Paced)
588 effectivePercent = calculatePercentFromKeyPoints(percent);
589 else if (m_keyPoints.isEmpty() && calcMode == CalcMode::Spline && m_keyTimes.size() > 1)
590 effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
591 else if (animationMode == AnimationMode::FromTo || animationMode == AnimationMode::To)
592 effectivePercent = calculatePercentForFromTo(percent);
593 else
594 effectivePercent = percent;
595
596 calculateAnimatedValue(effectivePercent, repeatCount, resultElement);
597}
598
599void SVGAnimationElement::computeCSSPropertyValue(SVGElement* element, CSSPropertyID id, String& valueString)
600{
601 ASSERT(element);
602
603 // Don't include any properties resulting from CSS Transitions/Animations or SMIL animations, as we want to retrieve the "base value".
604 element->setUseOverrideComputedStyle(true);
605 RefPtr<CSSValue> value = ComputedStyleExtractor(element).propertyValue(id);
606 valueString = value ? value->cssText() : String();
607 element->setUseOverrideComputedStyle(false);
608}
609
610static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value)
611{
612 static NeverDestroyed<const AtomicString> inherit("inherit", AtomicString::ConstructFromLiteral);
613
614 if (value.isEmpty() || value != inherit)
615 return false;
616 return targetElement->isAnimatedStyleAttribute(attributeName);
617}
618
619void SVGAnimationElement::determinePropertyValueTypes(const String& from, const String& to)
620{
621 auto targetElement = makeRefPtr(this->targetElement());
622 ASSERT(targetElement);
623
624 const QualifiedName& attributeName = this->attributeName();
625 if (inheritsFromProperty(targetElement.get(), attributeName, from))
626 m_fromPropertyValueType = InheritValue;
627 if (inheritsFromProperty(targetElement.get(), attributeName, to))
628 m_toPropertyValueType = InheritValue;
629}
630void SVGAnimationElement::resetAnimation()
631{
632 m_lastValuesAnimationFrom = String();
633 m_lastValuesAnimationTo = String();
634}
635
636}
637