• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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