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