Remove ENABLE_SVG_ANIMATION as all major ports have it on by default
[WebKit-https.git] / Source / WebCore / svg / SVGAnimationElement.cpp
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 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  *
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.
13  *
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.
18  *
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.
23  */
24
25 #include "config.h"
26
27 #if ENABLE(SVG)
28 #include "SVGAnimationElement.h"
29
30 #include "Attribute.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "CSSPropertyNames.h"
34 #include "Color.h"
35 #include "Document.h"
36 #include "Event.h"
37 #include "EventListener.h"
38 #include "FloatConversion.h"
39 #include "HTMLNames.h"
40 #include "PlatformString.h"
41 #include "SVGAnimateElement.h"
42 #include "SVGElementInstance.h"
43 #include "SVGNames.h"
44 #include "SVGParserUtilities.h"
45 #include "SVGStyledElement.h"
46 #include "SVGURIReference.h"
47 #include "SVGUseElement.h"
48 #include "XLinkNames.h"
49 #include <wtf/StdLibExtras.h>
50
51 using namespace std;
52
53 namespace WebCore {
54
55 // Animated property definitions
56 DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
57
58 BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGAnimationElement)
59     REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
60     REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
61 END_REGISTER_ANIMATED_PROPERTIES
62
63 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* document)
64     : SVGSMILElement(tagName, document)
65     , m_animationValid(false)
66 {
67     registerAnimatedPropertiesForSVGAnimationElement();
68 }
69
70 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
71 {
72     result.clear();
73     Vector<String> parseList;
74     parse.split(';', parseList);
75     for (unsigned n = 0; n < parseList.size(); ++n) {
76         String timeString = parseList[n];
77         bool ok;
78         float time = timeString.toFloat(&ok);
79         if (!ok || time < 0 || time > 1)
80             goto fail;
81         if (verifyOrder) {
82             if (!n) {
83                 if (time)
84                     goto fail;
85             } else if (time < result.last())
86                 goto fail;
87         }
88         result.append(time);
89     }
90     return;
91 fail:
92     result.clear();
93 }
94
95 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
96 {
97     result.clear();
98     if (parse.isEmpty())
99         return;
100     const UChar* cur = parse.characters();
101     const UChar* end = cur + parse.length();
102
103     skipOptionalSVGSpaces(cur, end);
104
105     bool delimParsed = false;
106     while (cur < end) {
107         delimParsed = false;
108         float posA = 0;
109         if (!parseNumber(cur, end, posA)) {
110             result.clear();
111             return;
112         }
113
114         float posB = 0;
115         if (!parseNumber(cur, end, posB)) {
116             result.clear();
117             return;
118         }
119
120         float posC = 0;
121         if (!parseNumber(cur, end, posC)) {
122             result.clear();
123             return;
124         }
125
126         float posD = 0;
127         if (!parseNumber(cur, end, posD, false)) {
128             result.clear();
129             return;
130         }
131
132         skipOptionalSVGSpaces(cur, end);
133
134         if (cur < end && *cur == ';') {
135             delimParsed = true;
136             cur++;
137         }
138         skipOptionalSVGSpaces(cur, end);
139
140         result.append(UnitBezier(posA, posB, posC, posD));
141     }
142     if (!(cur == end && !delimParsed))
143         result.clear();
144 }
145
146 bool SVGAnimationElement::isSupportedAttribute(const QualifiedName& attrName)
147 {
148     DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
149     if (supportedAttributes.isEmpty()) {
150         SVGTests::addSupportedAttributes(supportedAttributes);
151         SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
152         supportedAttributes.add(SVGNames::valuesAttr);
153         supportedAttributes.add(SVGNames::keyTimesAttr);
154         supportedAttributes.add(SVGNames::keyPointsAttr);
155         supportedAttributes.add(SVGNames::keySplinesAttr);
156     }
157     return supportedAttributes.contains(attrName);
158 }
159      
160 void SVGAnimationElement::parseMappedAttribute(Attribute* attr)
161 {
162     if (!isSupportedAttribute(attr->name())) {
163         SVGSMILElement::parseMappedAttribute(attr);
164         return;
165     }
166
167     if (attr->name() == SVGNames::valuesAttr) {
168         // Per the SMIL specification, leading and trailing white space,
169         // and white space before and after semicolon separators, is allowed and will be ignored.
170         // http://www.w3.org/TR/SVG11/animate.html#ValuesAttribute
171         attr->value().string().split(';', m_values);
172         for (unsigned i = 0; i < m_values.size(); ++i)
173             m_values[i] = m_values[i].stripWhiteSpace();
174         return;
175     }
176
177     if (attr->name() == SVGNames::keyTimesAttr) {
178         parseKeyTimes(attr->value(), m_keyTimes, true);
179         return;
180     }
181
182     if (attr->name() == SVGNames::keyPointsAttr) {
183         if (hasTagName(SVGNames::animateMotionTag)) {
184             // This is specified to be an animateMotion attribute only but it is simpler to put it here 
185             // where the other timing calculatations are.
186             parseKeyTimes(attr->value(), m_keyPoints, false);
187         }
188         return;
189     }
190
191     if (attr->name() == SVGNames::keySplinesAttr) {
192         parseKeySplines(attr->value(), m_keySplines);
193         return;
194     }
195
196     if (SVGTests::parseMappedAttribute(attr))
197         return;
198     if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
199         return;
200
201     ASSERT_NOT_REACHED();
202 }
203
204 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
205 {
206     // Assumptions may not hold after an attribute change.
207     m_animationValid = false;
208     setInactive();
209     SVGSMILElement::attributeChanged(attr, preserveDecls);
210 }
211
212 float SVGAnimationElement::getStartTime() const
213 {
214     return narrowPrecisionToFloat(intervalBegin().value());
215 }
216
217 float SVGAnimationElement::getCurrentTime() const
218 {
219     return narrowPrecisionToFloat(elapsed().value());
220 }
221
222 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
223 {
224     return narrowPrecisionToFloat(simpleDuration().value());
225 }    
226     
227 void SVGAnimationElement::beginElement()
228 {
229     beginElementAt(0);
230 }
231
232 void SVGAnimationElement::beginElementAt(float offset)
233 {
234     SMILTime elapsed = this->elapsed();
235     addBeginTime(elapsed, elapsed + offset);
236 }
237
238 void SVGAnimationElement::endElement()
239 {
240     endElementAt(0);
241 }
242
243 void SVGAnimationElement::endElementAt(float offset)
244 {
245     SMILTime elapsed = this->elapsed();
246     addEndTime(elapsed, elapsed + offset);
247 }
248
249 AnimationMode SVGAnimationElement::animationMode() const
250 {
251     // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
252     if (hasTagName(SVGNames::setTag))
253         return ToAnimation;
254     if (!animationPath().isEmpty())
255         return PathAnimation;
256     if (fastHasAttribute(SVGNames::valuesAttr))
257         return ValuesAnimation;
258     if (!toValue().isEmpty())
259         return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
260     if (!byValue().isEmpty())
261         return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
262     return NoAnimation;
263 }
264
265 CalcMode SVGAnimationElement::calcMode() const
266 {    
267     DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete"));
268     DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear"));
269     DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced"));
270     DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline"));
271     const AtomicString& value = fastGetAttribute(SVGNames::calcModeAttr);
272     if (value == discrete)
273         return CalcModeDiscrete;
274     if (value == linear)
275         return CalcModeLinear;
276     if (value == paced)
277         return CalcModePaced;
278     if (value == spline)
279         return CalcModeSpline;
280     return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
281 }
282
283 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
284 {    
285     DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS"));
286     DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML"));
287     const AtomicString& value = fastGetAttribute(SVGNames::attributeTypeAttr);
288     if (value == css)
289         return AttributeTypeCSS;
290     if (value == xml)
291         return AttributeTypeXML;
292     return AttributeTypeAuto;
293 }
294
295 String SVGAnimationElement::toValue() const
296 {    
297     return fastGetAttribute(SVGNames::toAttr);
298 }
299
300 String SVGAnimationElement::byValue() const
301 {    
302     return fastGetAttribute(SVGNames::byAttr);
303 }
304
305 String SVGAnimationElement::fromValue() const
306 {    
307     return fastGetAttribute(SVGNames::fromAttr);
308 }
309
310 bool SVGAnimationElement::isAdditive() const
311 {
312     DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
313     const AtomicString& value = fastGetAttribute(SVGNames::additiveAttr);
314     return value == sum || animationMode() == ByAnimation;
315 }
316
317 bool SVGAnimationElement::isAccumulated() const
318 {
319     DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
320     const AtomicString& value = fastGetAttribute(SVGNames::accumulateAttr);
321     return value == sum && animationMode() != ToAnimation;
322 }
323
324 bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
325 {
326     ASSERT(targetElement);
327     if (!targetElement->isStyled())
328         return false;
329
330     return SVGStyledElement::isAnimatableCSSProperty(attributeName);
331 }
332
333 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
334 {
335     if (!hasValidAttributeType())
336         return;
337     SVGElement* targetElement = this->targetElement();
338     QualifiedName attributeName = this->attributeName();
339     if (!targetElement || attributeName == anyQName() || value.isNull())
340         return;
341
342     // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
343     if (targetElement->isStyled())
344         static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(true);
345         
346     bool attributeIsCSSProperty = isTargetAttributeCSSProperty(targetElement, attributeName);
347     // Stop animation, if attributeType is set to CSS by the user, but the attribute itself is not a CSS property.
348     if (!attributeIsCSSProperty && attributeType() == AttributeTypeCSS)
349         return;
350
351     ExceptionCode ec;
352     if (attributeIsCSSProperty) {
353         // FIXME: This should set the override style, not the inline style.
354         // Sadly override styles are not yet implemented.
355         targetElement->style()->setProperty(attributeName.localName(), value, "", ec);
356     } else {
357         // FIXME: This should set the 'presentation' value, not the actual 
358         // attribute value. Whatever that means in practice.
359         targetElement->setAttribute(attributeName, value, ec);
360     }
361     
362     if (targetElement->isStyled())
363         static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(false);
364     
365     // If the target element is used in an <use> instance tree, update that as well.
366     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
367     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
368     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
369         SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
370         if (!shadowTreeElement)
371             continue;
372         if (attributeIsCSSProperty)
373             shadowTreeElement->style()->setProperty(attributeName.localName(), value, "", ec);
374         else
375             shadowTreeElement->setAttribute(attributeName, value, ec);
376         (*it)->correspondingUseElement()->setNeedsStyleRecalc();
377     }
378 }
379     
380 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
381 {
382     ASSERT(calcMode() == CalcModePaced);
383     ASSERT(animationMode() == ValuesAnimation);
384
385     unsigned valuesCount = m_values.size();
386     ASSERT(valuesCount > 1);
387     Vector<float> keyTimesForPaced;
388     float totalDistance = 0;
389     keyTimesForPaced.append(0);
390     for (unsigned n = 0; n < valuesCount - 1; ++n) {
391         // Distance in any units
392         float distance = calculateDistance(m_values[n], m_values[n + 1]);
393         if (distance < 0)
394             return;
395         totalDistance += distance;
396         keyTimesForPaced.append(distance);
397     }
398     if (!totalDistance)
399         return;
400
401     // Normalize.
402     for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
403         keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
404     keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
405
406     // Use key times calculated based on pacing instead of the user provided ones.
407     m_keyTimes.swap(keyTimesForPaced);
408 }
409
410 static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
411
412 unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
413 {
414     unsigned index;
415     unsigned keyTimesCount = m_keyTimes.size();
416     // Compare index + 1 to keyTimesCount because the last keyTimes entry is
417     // required to be 1, and percent can never exceed 1; i.e., the second last
418     // keyTimes entry defines the beginning of the final interval
419     for (index = 1; index + 1 < keyTimesCount; ++index) {
420         if (m_keyTimes[index] > percent)
421             break;
422     }
423     return --index;
424 }
425
426 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
427 {
428     ASSERT(calcMode() == CalcModeSpline);
429     ASSERT(splineIndex < m_keySplines.size());
430     UnitBezier bezier = m_keySplines[splineIndex];
431     SMILTime duration = simpleDuration();
432     if (!duration.isFinite())
433         duration = 100.0;
434     return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
435 }
436
437 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
438 {
439     ASSERT(!m_keyPoints.isEmpty());
440     ASSERT(calcMode() != CalcModePaced);
441     ASSERT(m_keyTimes.size() > 1);
442     ASSERT(m_keyPoints.size() == m_keyTimes.size());
443
444     if (percent == 1)
445         return m_keyPoints[m_keyPoints.size() - 1];
446
447     unsigned index = calculateKeyTimesIndex(percent);
448     float fromPercent = m_keyTimes[index];
449     float toPercent = m_keyTimes[index + 1];
450     float fromKeyPoint = m_keyPoints[index];
451     float toKeyPoint = m_keyPoints[index + 1];
452     
453     if (calcMode() == CalcModeDiscrete)
454         return fromKeyPoint;
455     
456     float keyPointPercent = (percent - fromPercent) / (toPercent - fromPercent);
457     
458     if (calcMode() == CalcModeSpline) {
459         ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
460         keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
461     }
462     return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
463 }
464     
465 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
466 {
467     ASSERT(!m_keyPoints.isEmpty());
468     ASSERT(m_keyPoints.size() == m_keyTimes.size());
469     ASSERT(calcMode() != CalcModePaced);
470     effectivePercent = calculatePercentFromKeyPoints(percent);
471     unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
472     from = m_values[index];
473     to = m_values[index + 1];
474 }
475     
476 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to)
477 {
478     unsigned valuesCount = m_values.size();
479     ASSERT(m_animationValid);
480     ASSERT(valuesCount > 1);
481     
482     if (percent == 1) {
483         from = m_values[valuesCount - 1];
484         to = m_values[valuesCount - 1];
485         effectivePercent = 1;
486         return;
487     }
488
489     CalcMode calcMode = this->calcMode();
490     if (hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::animateColorTag)) {
491         SVGAnimateElement* animateElement = static_cast<SVGAnimateElement*>(this);
492         AnimatedPropertyType attributeType = animateElement->determineAnimatedPropertyType(targetElement());
493         // Fall back to discrete animations for Strings.
494         if (attributeType == AnimatedBoolean
495             || attributeType == AnimatedEnumeration
496             || attributeType == AnimatedPreserveAspectRatio
497             || attributeType == AnimatedString)
498             calcMode = CalcModeDiscrete;
499     }
500     if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
501         return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
502     
503     unsigned keyTimesCount = m_keyTimes.size();
504     ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
505     ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
506
507     unsigned index = calculateKeyTimesIndex(percent);
508     if (calcMode == CalcModeDiscrete) {
509         if (!keyTimesCount) 
510             index = static_cast<unsigned>(percent * valuesCount);
511         from = m_values[index];
512         to = m_values[index];
513         effectivePercent = 0;
514         return;
515     }
516     
517     float fromPercent;
518     float toPercent;
519     if (keyTimesCount) {
520         fromPercent = m_keyTimes[index];
521         toPercent = m_keyTimes[index + 1];
522     } else {        
523         index = static_cast<unsigned>(percent * (valuesCount - 1));
524         fromPercent =  static_cast<float>(index) / (valuesCount - 1);
525         toPercent =  static_cast<float>(index + 1) / (valuesCount - 1);
526     }
527     
528     if (index == valuesCount - 1)
529         --index;
530     from = m_values[index];
531     to = m_values[index + 1];
532     ASSERT(toPercent > fromPercent);
533     effectivePercent = (percent - fromPercent) / (toPercent - fromPercent);
534     
535     if (calcMode == CalcModeSpline) {
536         ASSERT(m_keySplines.size() == m_values.size() - 1);
537         effectivePercent = calculatePercentForSpline(effectivePercent, index);
538     }
539 }
540     
541 void SVGAnimationElement::startedActiveInterval()
542 {
543     m_animationValid = false;
544
545     if (!hasValidAttributeType())
546         return;
547
548     // These validations are appropriate for all animation modes.
549     if (fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
550         return;
551
552     AnimationMode animationMode = this->animationMode();
553     CalcMode calcMode = this->calcMode();
554     if (calcMode == CalcModeSpline) {
555         unsigned splinesCount = m_keySplines.size() + 1;
556         if ((fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != splinesCount)
557             || (animationMode == ValuesAnimation && m_values.size() != splinesCount)
558             || (fastHasAttribute(SVGNames::keyTimesAttr) && m_keyTimes.size() != splinesCount))
559             return;
560     }
561
562     String from = fromValue();
563     String to = toValue();
564     String by = byValue();
565     if (animationMode == NoAnimation)
566         return;
567     if (animationMode == FromToAnimation)
568         m_animationValid = calculateFromAndToValues(from, to);
569     else if (animationMode == ToAnimation) {
570         // For to-animations the from value is the current accumulated value from lower priority animations.
571         // The value is not static and is determined during the animation.
572         m_animationValid = calculateFromAndToValues(String(), to);
573     } else if (animationMode == FromByAnimation)
574         m_animationValid = calculateFromAndByValues(from, by);
575     else if (animationMode == ByAnimation)
576         m_animationValid = calculateFromAndByValues(String(), by);
577     else if (animationMode == ValuesAnimation) {
578         m_animationValid = m_values.size() > 1
579             && (calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyTimesAttr) || fastHasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
580             && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
581             && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
582             && (!fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
583         if (calcMode == CalcModePaced && m_animationValid)
584             calculateKeyTimesForCalcModePaced();
585     } else if (animationMode == PathAnimation)
586         m_animationValid = calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
587 }
588     
589 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
590 {    
591     if (!m_animationValid)
592         return;
593     
594     float effectivePercent;
595     CalcMode mode = calcMode();
596     if (animationMode() == ValuesAnimation) {
597         String from;
598         String to;
599         currentValuesForValuesAnimation(percent, effectivePercent, from, to);
600         if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
601             m_animationValid = calculateFromAndToValues(from, to);
602             if (!m_animationValid)
603                 return;
604             m_lastValuesAnimationFrom = from;
605             m_lastValuesAnimationTo = to;
606         }
607     } else if (!m_keyPoints.isEmpty() && mode != CalcModePaced)
608         effectivePercent = calculatePercentFromKeyPoints(percent);
609     else if (m_keyPoints.isEmpty() && mode == CalcModeSpline && m_keyTimes.size() > 1)
610         effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
611     else
612         effectivePercent = percent;
613
614     calculateAnimatedValue(effectivePercent, repeat, resultElement);
615 }
616
617 void SVGAnimationElement::endedActiveInterval()
618 {
619 }
620
621 }
622 #endif // ENABLE(SVG)
623