Prevent animation when CSS attributeType is invalid.
[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
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     ASSERT(resultElement->hasTagName(SVGNames::animateTag)
114         || resultElement->hasTagName(SVGNames::animateColorTag)
115         || resultElement->hasTagName(SVGNames::animateTransformTag)
116         || resultElement->hasTagName(SVGNames::setTag));
117
118     SVGAnimateElement* resultAnimationElement = static_cast<SVGAnimateElement*>(resultElement);
119     ASSERT(resultAnimationElement->m_animatedType);
120     ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType);
121
122     if (hasTagName(SVGNames::setTag))
123         percentage = 1;
124
125     if (calcMode() == CalcModeDiscrete)
126         percentage = percentage < 0.5 ? 0 : 1;
127
128     // Target element might have changed.
129     m_animator->setContextElement(targetElement);
130
131     // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do
132     // if after calculateAnimatedValue() ran the cached pointers in the list propery tear
133     // offs would point nowhere, and we couldn't create copies of those values anymore,
134     // while detaching. This is covered by assertions, moving this down would fire them.
135     if (!m_animatedProperties.isEmpty())
136         m_animator->animValWillChange(m_animatedProperties);
137
138     // Values-animation accumulates using the last values entry corresponding to the end of duration time.
139     SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get();
140     m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get());
141 }
142
143 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
144 {
145     if (toAtEndOfDurationString.isEmpty())
146         return false;
147     m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
148     return true;
149 }
150
151 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
152 {
153     SVGElement* targetElement = this->targetElement();
154     if (!targetElement)
155         return false;
156
157     determinePropertyValueTypes(fromString, toString);
158     ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString);
159     ASSERT(m_animatedPropertyType == m_animator->type());
160     return true;
161 }
162
163 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
164 {
165     SVGElement* targetElement = this->targetElement();
166     if (!targetElement)
167         return false;
168
169     if (animationMode() == ByAnimation && !isAdditive())
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     if (shouldApply == ApplyXMLAnimation) {
208         // SVG DOM animVal animation code-path.
209         m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName);
210         ASSERT(!m_animatedProperties.isEmpty());
211
212         ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties));
213         if (!m_animatedType)
214             m_animatedType = animator->startAnimValAnimation(m_animatedProperties);
215         else {
216             animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get());
217             animator->animValDidChange(m_animatedProperties);
218         }
219         return;
220     }
221
222     // CSS properties animation code-path.
223     ASSERT(m_animatedProperties.isEmpty());
224     String baseValue;
225
226     if (shouldApply == ApplyCSSAnimation) {
227         ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
228         computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
229     }
230
231     if (!m_animatedType)
232         m_animatedType = animator->constructFromString(baseValue);
233     else
234         m_animatedType->setValueAsString(attributeName, baseValue);
235 }
236
237 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
238 {
239     ASSERT(!targetElement->m_deletionHasBegun);
240
241     StylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties();
242     if (!propertySet->setProperty(id, value, false, 0))
243         return;
244
245     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
246 }
247
248 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
249 {
250     ASSERT(!targetElement->m_deletionHasBegun);
251     targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id);
252     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
253 }
254
255 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
256 {
257     ASSERT(targetElement);
258     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
259         return;
260
261     CSSPropertyID id = cssPropertyID(attributeName.localName());
262
263     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
264     applyCSSPropertyToTarget(targetElement, id, valueAsString);
265
266     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
267     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
268     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
269     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
270         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
271             applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
272     }
273 }
274
275 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
276 {
277     ASSERT(targetElement);
278     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
279         return;
280
281     CSSPropertyID id = cssPropertyID(attributeName.localName());
282
283     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
284     removeCSSPropertyFromTarget(targetElement, id);
285
286     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
287     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
288     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
289     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
290         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
291             removeCSSPropertyFromTarget(shadowTreeElement, id);
292     }
293 }
294
295 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
296 {
297     ASSERT(!targetElement->m_deletionHasBegun);
298     targetElement->svgAttributeChanged(attributeName);
299 }
300
301 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
302 {
303     ASSERT(targetElement);
304     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
305         return;
306
307     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
308     notifyTargetAboutAnimValChange(targetElement, attributeName);
309
310     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
311     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
312     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
313     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
314         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
315             notifyTargetAboutAnimValChange(shadowTreeElement, attributeName);
316     }
317 }
318
319 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
320 {
321     if (!m_animatedType)
322         return;
323
324     if (!targetElement) {
325         m_animatedType.clear();
326         return;
327     }
328
329     if (m_animatedProperties.isEmpty()) {
330         // CSS properties animation code-path.
331         removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
332         m_animatedType.clear();
333         return;
334     }
335
336     // SVG DOM animVal animation code-path.
337     if (m_animator) {
338         m_animator->stopAnimValAnimation(m_animatedProperties);
339         notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
340     }
341
342     m_animatedProperties.clear();
343     m_animatedType.clear();
344 }
345
346 void SVGAnimateElement::applyResultsToTarget()
347 {
348     ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
349     ASSERT(m_animatedPropertyType != AnimatedUnknown);
350     ASSERT(m_animator);
351
352     // Early exit if our animated type got destructed by a previous endedActiveInterval().
353     if (!m_animatedType)
354         return;
355
356     if (m_animatedProperties.isEmpty()) {
357         // CSS properties animation code-path.
358         // Convert the result of the animation to a String and apply it as CSS property on the target & all instances.
359         applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedType->valueAsString());
360         return;
361     }
362
363     // SVG DOM animVal animation code-path.
364     // At this point the SVG DOM values are already changed, unlike for CSS.
365     // We only have to trigger update notifications here.
366     m_animator->animValDidChange(m_animatedProperties);
367     notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
368 }
369
370 bool SVGAnimateElement::isAdditive() const
371 {
372     if (animationMode() == ByAnimation || animationMode() == FromByAnimation) {
373         // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
374         switch (m_animatedPropertyType) {
375         case AnimatedBoolean:
376         case AnimatedEnumeration:
377         case AnimatedPreserveAspectRatio:
378         case AnimatedString:
379         case AnimatedUnknown:
380             return false;
381         default:
382             break;
383         }
384     }
385
386     return SVGAnimationElement::isAdditive();
387 }
388
389 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
390 {
391     // FIXME: A return value of float is not enough to support paced animations on lists.
392     SVGElement* targetElement = this->targetElement();
393     if (!targetElement)
394         return -1;
395
396     return ensureAnimator()->calculateDistance(fromString, toString);
397 }
398
399 void SVGAnimateElement::targetElementWillChange(SVGElement* currentTarget, SVGElement* newTarget)
400 {
401     SVGAnimationElement::targetElementWillChange(currentTarget, newTarget);
402
403     ASSERT(!m_animatedType);
404     m_fromType.clear();
405     m_toType.clear();
406     m_toAtEndOfDurationType.clear();
407     m_animator.clear();
408     m_animatedPropertyType = newTarget ? determineAnimatedPropertyType(newTarget) : AnimatedString;
409 }
410
411 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
412 {
413     if (!m_animator)
414         m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType);
415     ASSERT(m_animatedPropertyType == m_animator->type());
416     return m_animator.get();
417 }
418
419 }
420
421 #endif // ENABLE(SVG)