Make some things that return never-null pointers return references instead.
[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     ASSERT(resultElement->hasTagName(SVGNames::animateTag)
115         || resultElement->hasTagName(SVGNames::animateColorTag)
116         || resultElement->hasTagName(SVGNames::animateTransformTag)
117         || resultElement->hasTagName(SVGNames::setTag));
118
119     SVGAnimateElement* resultAnimationElement = static_cast<SVGAnimateElement*>(resultElement);
120     ASSERT(resultAnimationElement->m_animatedType);
121     ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType);
122
123     if (hasTagName(SVGNames::setTag))
124         percentage = 1;
125
126     if (calcMode() == CalcModeDiscrete)
127         percentage = percentage < 0.5 ? 0 : 1;
128
129     // Target element might have changed.
130     m_animator->setContextElement(targetElement);
131
132     // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do
133     // if after calculateAnimatedValue() ran the cached pointers in the list propery tear
134     // offs would point nowhere, and we couldn't create copies of those values anymore,
135     // while detaching. This is covered by assertions, moving this down would fire them.
136     if (!m_animatedProperties.isEmpty())
137         m_animator->animValWillChange(m_animatedProperties);
138
139     // Values-animation accumulates using the last values entry corresponding to the end of duration time.
140     SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get();
141     m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get());
142 }
143
144 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
145 {
146     if (toAtEndOfDurationString.isEmpty())
147         return false;
148     m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
149     return true;
150 }
151
152 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
153 {
154     SVGElement* targetElement = this->targetElement();
155     if (!targetElement)
156         return false;
157
158     determinePropertyValueTypes(fromString, toString);
159     ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString);
160     ASSERT(m_animatedPropertyType == m_animator->type());
161     return true;
162 }
163
164 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
165 {
166     SVGElement* targetElement = this->targetElement();
167     if (!targetElement)
168         return false;
169
170     if (animationMode() == ByAnimation && !isAdditive())
171         return false;
172
173     // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes).
174     if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition())
175         return false;
176
177     ASSERT(!hasTagName(SVGNames::setTag));
178
179     determinePropertyValueTypes(fromString, byString);
180     ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString);
181     ASSERT(m_animatedPropertyType == m_animator->type());
182     return true;
183 }
184
185 #ifndef NDEBUG
186 static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes)
187 {
188     SVGElementAnimatedPropertyList::const_iterator end = animatedTypes.end();
189     for (SVGElementAnimatedPropertyList::const_iterator it = animatedTypes.begin(); it != end; ++it) {
190         for (size_t i = 0; i < it->properties.size(); ++i) {
191             if (expectedPropertyType != it->properties[i]->animatedPropertyType()) {
192                 // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute.
193                 if (expectedPropertyType == AnimatedAngle && it->properties[i]->animatedPropertyType() == AnimatedEnumeration)
194                     return true;
195                 return false;
196             }
197         }
198     }
199
200     return true;
201 }
202 #endif
203
204 void SVGAnimateElement::resetAnimatedType()
205 {
206     SVGAnimatedTypeAnimator* animator = ensureAnimator();
207     ASSERT(m_animatedPropertyType == animator->type());
208
209     SVGElement* targetElement = this->targetElement();
210     const QualifiedName& attributeName = this->attributeName();
211     ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);
212
213     if (shouldApply == DontApplyAnimation)
214         return;
215
216     if (shouldApply == ApplyXMLAnimation) {
217         // SVG DOM animVal animation code-path.
218         m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName);
219         ASSERT(!m_animatedProperties.isEmpty());
220
221         ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties));
222         if (!m_animatedType)
223             m_animatedType = animator->startAnimValAnimation(m_animatedProperties);
224         else {
225             animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get());
226             animator->animValDidChange(m_animatedProperties);
227         }
228         return;
229     }
230
231     // CSS properties animation code-path.
232     ASSERT(m_animatedProperties.isEmpty());
233     String baseValue;
234
235     if (shouldApply == ApplyCSSAnimation) {
236         ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
237         computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
238     }
239
240     if (!m_animatedType)
241         m_animatedType = animator->constructFromString(baseValue);
242     else
243         m_animatedType->setValueAsString(attributeName, baseValue);
244 }
245
246 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
247 {
248     ASSERT(!targetElement->m_deletionHasBegun);
249
250     if (!targetElement->ensureAnimatedSMILStyleProperties().setProperty(id, value, false, 0))
251         return;
252
253     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
254 }
255
256 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
257 {
258     ASSERT(!targetElement->m_deletionHasBegun);
259     targetElement->ensureAnimatedSMILStyleProperties().removeProperty(id);
260     targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
261 }
262
263 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
264 {
265     ASSERT(targetElement);
266     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
267         return;
268
269     CSSPropertyID id = cssPropertyID(attributeName.localName());
270
271     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
272     applyCSSPropertyToTarget(targetElement, id, valueAsString);
273
274     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
275     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
276     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
277     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
278         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
279             applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
280     }
281 }
282
283 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
284 {
285     ASSERT(targetElement);
286     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
287         return;
288
289     CSSPropertyID id = cssPropertyID(attributeName.localName());
290
291     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
292     removeCSSPropertyFromTarget(targetElement, id);
293
294     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
295     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
296     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
297     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
298         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
299             removeCSSPropertyFromTarget(shadowTreeElement, id);
300     }
301 }
302
303 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
304 {
305     ASSERT(!targetElement->m_deletionHasBegun);
306     targetElement->svgAttributeChanged(attributeName);
307 }
308
309 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
310 {
311     ASSERT(targetElement);
312     if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
313         return;
314
315     SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
316     notifyTargetAboutAnimValChange(targetElement, attributeName);
317
318     // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
319     const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
320     const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
321     for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
322         if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
323             notifyTargetAboutAnimValChange(shadowTreeElement, attributeName);
324     }
325 }
326
327 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
328 {
329     if (!m_animatedType)
330         return;
331
332     if (!targetElement) {
333         m_animatedType.clear();
334         return;
335     }
336
337     if (m_animatedProperties.isEmpty()) {
338         // CSS properties animation code-path.
339         removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
340         m_animatedType.clear();
341         return;
342     }
343
344     // SVG DOM animVal animation code-path.
345     if (m_animator) {
346         m_animator->stopAnimValAnimation(m_animatedProperties);
347         notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
348     }
349
350     m_animatedProperties.clear();
351     m_animatedType.clear();
352 }
353
354 void SVGAnimateElement::applyResultsToTarget()
355 {
356     ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
357     ASSERT(m_animatedPropertyType != AnimatedUnknown);
358     ASSERT(m_animator);
359
360     // Early exit if our animated type got destructed by a previous endedActiveInterval().
361     if (!m_animatedType)
362         return;
363
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     // SVG DOM animVal animation code-path.
372     // At this point the SVG DOM values are already changed, unlike for CSS.
373     // We only have to trigger update notifications here.
374     m_animator->animValDidChange(m_animatedProperties);
375     notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
376 }
377
378 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() const
379 {
380     // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
381     switch (m_animatedPropertyType) {
382     case AnimatedBoolean:
383     case AnimatedEnumeration:
384     case AnimatedPreserveAspectRatio:
385     case AnimatedString:
386     case AnimatedUnknown:
387         return false;
388     case AnimatedAngle:
389     case AnimatedColor:
390     case AnimatedInteger:
391     case AnimatedIntegerOptionalInteger:
392     case AnimatedLength:
393     case AnimatedLengthList:
394     case AnimatedNumber:
395     case AnimatedNumberList:
396     case AnimatedNumberOptionalNumber:
397     case AnimatedPath:
398     case AnimatedPoints:
399     case AnimatedRect:
400     case AnimatedTransformList:
401         return true;
402     default:
403         RELEASE_ASSERT_NOT_REACHED();
404         return true;
405     }
406 }
407
408 bool SVGAnimateElement::isAdditive() const
409 {
410     if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
411         if (!animatedPropertyTypeSupportsAddition())
412             return false;
413
414     return SVGAnimationElement::isAdditive();
415 }
416
417 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
418 {
419     // FIXME: A return value of float is not enough to support paced animations on lists.
420     SVGElement* targetElement = this->targetElement();
421     if (!targetElement)
422         return -1;
423
424     return ensureAnimator()->calculateDistance(fromString, toString);
425 }
426
427 void SVGAnimateElement::setTargetElement(SVGElement* target)
428 {
429     SVGAnimationElement::setTargetElement(target);
430     resetAnimatedPropertyType();
431 }
432
433 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
434 {
435     SVGAnimationElement::setAttributeName(attributeName);
436     resetAnimatedPropertyType();
437 }
438
439 void SVGAnimateElement::resetAnimatedPropertyType()
440 {
441     ASSERT(!m_animatedType);
442     m_fromType.clear();
443     m_toType.clear();
444     m_toAtEndOfDurationType.clear();
445     m_animator.clear();
446     m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(targetElement()) : AnimatedString;
447 }
448
449 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
450 {
451     if (!m_animator)
452         m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType);
453     ASSERT(m_animatedPropertyType == m_animator->type());
454     return m_animator.get();
455 }
456
457 }
458
459 #endif // ENABLE(SVG)