<https://webkit.org/b/119996> Introduce toSVGAnimateElement(), and use it
[WebKit-https.git] / Source / WebCore / svg / SVGAnimateElement.cpp
1 /*
2  * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@kde.org>
4  * Copyright (C) 2008 Apple Inc. All rights reserved.
5  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24
25 #if ENABLE(SVG)
26 #include "SVGAnimateElement.h"
27
28 #include "CSSParser.h"
29 #include "CSSPropertyNames.h"
30 #include "QualifiedName.h"
31 #include "RenderObject.h"
32 #include "SVGAnimatorFactory.h"
33 #include "SVGNames.h"
34 #include "SVGStyledElement.h"
35 #include "StylePropertySet.h"
36
37 namespace WebCore {
38
39 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
40     : SVGAnimationElement(tagName, document)
41     , m_animatedPropertyType(AnimatedString)
42 {
43     ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag) || hasTagName(SVGNames::animateTransformTag));
44 }
45
46 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
47 {
48     return adoptRef(new SVGAnimateElement(tagName, document));
49 }
50
51 SVGAnimateElement::~SVGAnimateElement()
52 {
53 }
54
55 bool SVGAnimateElement::hasValidAttributeType()
56 {
57     SVGElement* targetElement = this->targetElement();
58     if (!targetElement)
59         return false;
60
61     return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType();
62 }
63
64 AnimatedPropertyType SVGAnimateElement::determineAnimatedPropertyType(SVGElement* targetElement) const
65 {
66     ASSERT(targetElement);
67
68     Vector<AnimatedPropertyType> propertyTypes;
69     targetElement->animatedPropertyTypeForAttribute(attributeName(), propertyTypes);
70     if (propertyTypes.isEmpty())
71         return AnimatedUnknown;
72
73     ASSERT(propertyTypes.size() <= 2);
74     AnimatedPropertyType type = propertyTypes[0];
75     if (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor)
76         return AnimatedUnknown;
77
78     // Animations of transform lists are not allowed for <animate> or <set>
79     // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
80     if (type == AnimatedTransformList && !hasTagName(SVGNames::animateTransformTag))
81         return AnimatedUnknown;
82
83     // Fortunately there's just one special case needed here: SVGMarkerElements orientAttr, which
84     // corresponds to SVGAnimatedAngle orientAngle and SVGAnimatedEnumeration orientType. We have to
85     // figure out whose value to change here.
86     if (targetElement->hasTagName(SVGNames::markerTag) && type == AnimatedAngle) {
87         ASSERT(propertyTypes.size() == 2);
88         ASSERT(propertyTypes[0] == AnimatedAngle);
89         ASSERT(propertyTypes[1] == AnimatedEnumeration);
90     } else if (propertyTypes.size() == 2)
91         ASSERT(propertyTypes[0] == propertyTypes[1]);
92
93     return type;
94 }
95
96 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement)
97 {
98     ASSERT(resultElement);
99     SVGElement* targetElement = this->targetElement();
100     if (!targetElement)
101         return;
102
103     ASSERT(m_animatedPropertyType == determineAnimatedPropertyType(targetElement));
104
105     ASSERT(percentage >= 0 && percentage <= 1);
106     ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
107     ASSERT(m_animatedPropertyType != AnimatedUnknown);
108     ASSERT(m_animator);
109     ASSERT(m_animator->type() == m_animatedPropertyType);
110     ASSERT(m_fromType);
111     ASSERT(m_fromType->type() == m_animatedPropertyType);
112     ASSERT(m_toType);
113
114     SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement);
115     ASSERT(resultAnimationElement->m_animatedType);
116     ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType);
117
118     if (hasTagName(SVGNames::setTag))
119         percentage = 1;
120
121     if (calcMode() == CalcModeDiscrete)
122         percentage = percentage < 0.5 ? 0 : 1;
123
124     // Target element might have changed.
125     m_animator->setContextElement(targetElement);
126
127     // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do
128     // if after calculateAnimatedValue() ran the cached pointers in the list propery tear
129     // offs would point nowhere, and we couldn't create copies of those values anymore,
130     // while detaching. This is covered by assertions, moving this down would fire them.
131     if (!m_animatedProperties.isEmpty())
132         m_animator->animValWillChange(m_animatedProperties);
133
134     // Values-animation accumulates using the last values entry corresponding to the end of duration time.
135     SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get();
136     m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get());
137 }
138
139 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
140 {
141     if (toAtEndOfDurationString.isEmpty())
142         return false;
143     m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
144     return true;
145 }
146
147 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
148 {
149     SVGElement* targetElement = this->targetElement();
150     if (!targetElement)
151         return false;
152
153     determinePropertyValueTypes(fromString, toString);
154     ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString);
155     ASSERT(m_animatedPropertyType == m_animator->type());
156     return true;
157 }
158
159 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
160 {
161     SVGElement* targetElement = this->targetElement();
162     if (!targetElement)
163         return false;
164
165     if (animationMode() == ByAnimation && !isAdditive())
166         return false;
167
168     // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes).
169     if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition())
170         return false;
171
172     ASSERT(!hasTagName(SVGNames::setTag));
173
174     determinePropertyValueTypes(fromString, byString);
175     ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString);
176     ASSERT(m_animatedPropertyType == m_animator->type());
177     return true;
178 }
179
180 #ifndef NDEBUG
181 static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes)
182 {
183     SVGElementAnimatedPropertyList::const_iterator end = animatedTypes.end();
184     for (SVGElementAnimatedPropertyList::const_iterator it = animatedTypes.begin(); it != end; ++it) {
185         for (size_t i = 0; i < it->properties.size(); ++i) {
186             if (expectedPropertyType != it->properties[i]->animatedPropertyType()) {
187                 // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute.
188                 if (expectedPropertyType == AnimatedAngle && it->properties[i]->animatedPropertyType() == AnimatedEnumeration)
189                     return true;
190                 return false;
191             }
192         }
193     }
194
195     return true;
196 }
197 #endif
198
199 void SVGAnimateElement::resetAnimatedType()
200 {
201     SVGAnimatedTypeAnimator* animator = ensureAnimator();
202     ASSERT(m_animatedPropertyType == animator->type());
203
204     SVGElement* targetElement = this->targetElement();
205     const QualifiedName& attributeName = this->attributeName();
206     ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);
207
208     if (shouldApply == DontApplyAnimation)
209         return;
210
211     if (shouldApply == ApplyXMLAnimation) {
212         // SVG DOM animVal animation code-path.
213         m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName);
214         ASSERT(!m_animatedProperties.isEmpty());
215
216         ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties));
217         if (!m_animatedType)
218             m_animatedType = animator->startAnimValAnimation(m_animatedProperties);
219         else {
220             animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get());
221             animator->animValDidChange(m_animatedProperties);
222         }
223         return;
224     }
225
226     // CSS properties animation code-path.
227     ASSERT(m_animatedProperties.isEmpty());
228     String baseValue;
229
230     if (shouldApply == ApplyCSSAnimation) {
231         ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
232         computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
233     }
234
235     if (!m_animatedType)
236         m_animatedType = animator->constructFromString(baseValue);
237     else
238         m_animatedType->setValueAsString(attributeName, baseValue);
239 }
240
241 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
242 {
243     ASSERT(!targetElement->m_deletionHasBegun);
244
245     if (!targetElement->ensureAnimatedSMILStyleProperties().setProperty(id, value, false, 0))
246         return;
247
248     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
249 }
250
251 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
252 {
253     ASSERT(!targetElement->m_deletionHasBegun);
254     targetElement->ensureAnimatedSMILStyleProperties().removeProperty(id);
255     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
256 }
257
258 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
259 {
260     ASSERT(targetElement);
261     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
262         return;
263
264     CSSPropertyID id = cssPropertyID(attributeName.localName());
265
266     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
267     applyCSSPropertyToTarget(targetElement, id, valueAsString);
268
269     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
270     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
271     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
272     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
273         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
274             applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
275     }
276 }
277
278 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
279 {
280     ASSERT(targetElement);
281     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
282         return;
283
284     CSSPropertyID id = cssPropertyID(attributeName.localName());
285
286     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
287     removeCSSPropertyFromTarget(targetElement, id);
288
289     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
290     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
291     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
292     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
293         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
294             removeCSSPropertyFromTarget(shadowTreeElement, id);
295     }
296 }
297
298 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
299 {
300     ASSERT(!targetElement->m_deletionHasBegun);
301     targetElement->svgAttributeChanged(attributeName);
302 }
303
304 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
305 {
306     ASSERT(targetElement);
307     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
308         return;
309
310     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
311     notifyTargetAboutAnimValChange(targetElement, attributeName);
312
313     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
314     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
315     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
316     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
317         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
318             notifyTargetAboutAnimValChange(shadowTreeElement, attributeName);
319     }
320 }
321
322 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
323 {
324     if (!m_animatedType)
325         return;
326
327     if (!targetElement) {
328         m_animatedType.clear();
329         return;
330     }
331
332     if (m_animatedProperties.isEmpty()) {
333         // CSS properties animation code-path.
334         removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
335         m_animatedType.clear();
336         return;
337     }
338
339     // SVG DOM animVal animation code-path.
340     if (m_animator) {
341         m_animator->stopAnimValAnimation(m_animatedProperties);
342         notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
343     }
344
345     m_animatedProperties.clear();
346     m_animatedType.clear();
347 }
348
349 void SVGAnimateElement::applyResultsToTarget()
350 {
351     ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
352     ASSERT(m_animatedPropertyType != AnimatedUnknown);
353     ASSERT(m_animator);
354
355     // Early exit if our animated type got destructed by a previous endedActiveInterval().
356     if (!m_animatedType)
357         return;
358
359     if (m_animatedProperties.isEmpty()) {
360         // CSS properties animation code-path.
361         // Convert the result of the animation to a String and apply it as CSS property on the target & all instances.
362         applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedType->valueAsString());
363         return;
364     }
365
366     // SVG DOM animVal animation code-path.
367     // At this point the SVG DOM values are already changed, unlike for CSS.
368     // We only have to trigger update notifications here.
369     m_animator->animValDidChange(m_animatedProperties);
370     notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
371 }
372
373 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() const
374 {
375     // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
376     switch (m_animatedPropertyType) {
377     case AnimatedBoolean:
378     case AnimatedEnumeration:
379     case AnimatedPreserveAspectRatio:
380     case AnimatedString:
381     case AnimatedUnknown:
382         return false;
383     case AnimatedAngle:
384     case AnimatedColor:
385     case AnimatedInteger:
386     case AnimatedIntegerOptionalInteger:
387     case AnimatedLength:
388     case AnimatedLengthList:
389     case AnimatedNumber:
390     case AnimatedNumberList:
391     case AnimatedNumberOptionalNumber:
392     case AnimatedPath:
393     case AnimatedPoints:
394     case AnimatedRect:
395     case AnimatedTransformList:
396         return true;
397     default:
398         RELEASE_ASSERT_NOT_REACHED();
399         return true;
400     }
401 }
402
403 bool SVGAnimateElement::isAdditive() const
404 {
405     if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
406         if (!animatedPropertyTypeSupportsAddition())
407             return false;
408
409     return SVGAnimationElement::isAdditive();
410 }
411
412 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
413 {
414     // FIXME: A return value of float is not enough to support paced animations on lists.
415     SVGElement* targetElement = this->targetElement();
416     if (!targetElement)
417         return -1;
418
419     return ensureAnimator()->calculateDistance(fromString, toString);
420 }
421
422 void SVGAnimateElement::setTargetElement(SVGElement* target)
423 {
424     SVGAnimationElement::setTargetElement(target);
425     resetAnimatedPropertyType();
426 }
427
428 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
429 {
430     SVGAnimationElement::setAttributeName(attributeName);
431     resetAnimatedPropertyType();
432 }
433
434 void SVGAnimateElement::resetAnimatedPropertyType()
435 {
436     ASSERT(!m_animatedType);
437     m_fromType.clear();
438     m_toType.clear();
439     m_toAtEndOfDurationType.clear();
440     m_animator.clear();
441     m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(targetElement()) : AnimatedString;
442 }
443
444 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
445 {
446     if (!m_animator)
447         m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType);
448     ASSERT(m_animatedPropertyType == m_animator->type());
449     return m_animator.get();
450 }
451
452 }
453
454 #endif // ENABLE(SVG)