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