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