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