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.
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.
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.
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.
26 #include "SVGAnimateElement.h"
28 #include "CSSParser.h"
29 #include "CSSPropertyNames.h"
30 #include "QualifiedName.h"
31 #include "RenderObject.h"
32 #include "SVGAnimatorFactory.h"
34 #include "SVGStyledElement.h"
35 #include "StylePropertySet.h"
39 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
40 : SVGAnimationElement(tagName, document)
41 , m_animatedPropertyType(AnimatedString)
43 ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag) || hasTagName(SVGNames::animateTransformTag));
46 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
48 return adoptRef(new SVGAnimateElement(tagName, document));
51 SVGAnimateElement::~SVGAnimateElement()
55 bool SVGAnimateElement::hasValidAttributeType()
57 SVGElement* targetElement = this->targetElement();
61 return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType();
64 AnimatedPropertyType SVGAnimateElement::determineAnimatedPropertyType(SVGElement* targetElement) const
66 ASSERT(targetElement);
68 Vector<AnimatedPropertyType> propertyTypes;
69 targetElement->animatedPropertyTypeForAttribute(attributeName(), propertyTypes);
70 if (propertyTypes.isEmpty())
71 return AnimatedUnknown;
73 ASSERT(propertyTypes.size() <= 2);
74 AnimatedPropertyType type = propertyTypes[0];
75 if (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor)
76 return AnimatedUnknown;
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;
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]);
96 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement)
98 ASSERT(resultElement);
99 SVGElement* targetElement = this->targetElement();
103 ASSERT(m_animatedPropertyType == determineAnimatedPropertyType(targetElement));
105 ASSERT(percentage >= 0 && percentage <= 1);
106 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
107 ASSERT(m_animatedPropertyType != AnimatedUnknown);
109 ASSERT(m_animator->type() == m_animatedPropertyType);
111 ASSERT(m_fromType->type() == m_animatedPropertyType);
114 SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement);
115 ASSERT(resultAnimationElement->m_animatedType);
116 ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType);
118 if (hasTagName(SVGNames::setTag))
121 if (calcMode() == CalcModeDiscrete)
122 percentage = percentage < 0.5 ? 0 : 1;
124 // Target element might have changed.
125 m_animator->setContextElement(targetElement);
127 // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do
128 // if after calculateAnimatedValue() ran the cached pointers in the list propery tear
129 // offs would point nowhere, and we couldn't create copies of those values anymore,
130 // while detaching. This is covered by assertions, moving this down would fire them.
131 if (!m_animatedProperties.isEmpty())
132 m_animator->animValWillChange(m_animatedProperties);
134 // Values-animation accumulates using the last values entry corresponding to the end of duration time.
135 SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get();
136 m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get());
139 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
141 if (toAtEndOfDurationString.isEmpty())
143 m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
147 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
149 SVGElement* targetElement = this->targetElement();
153 determinePropertyValueTypes(fromString, toString);
154 ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString);
155 ASSERT(m_animatedPropertyType == m_animator->type());
159 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
161 SVGElement* targetElement = this->targetElement();
165 if (animationMode() == ByAnimation && !isAdditive())
168 // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes).
169 if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition())
172 ASSERT(!hasTagName(SVGNames::setTag));
174 determinePropertyValueTypes(fromString, byString);
175 ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString);
176 ASSERT(m_animatedPropertyType == m_animator->type());
181 static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes)
183 SVGElementAnimatedPropertyList::const_iterator end = animatedTypes.end();
184 for (SVGElementAnimatedPropertyList::const_iterator it = animatedTypes.begin(); it != end; ++it) {
185 for (size_t i = 0; i < it->properties.size(); ++i) {
186 if (expectedPropertyType != it->properties[i]->animatedPropertyType()) {
187 // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute.
188 if (expectedPropertyType == AnimatedAngle && it->properties[i]->animatedPropertyType() == AnimatedEnumeration)
199 void SVGAnimateElement::resetAnimatedType()
201 SVGAnimatedTypeAnimator* animator = ensureAnimator();
202 ASSERT(m_animatedPropertyType == animator->type());
204 SVGElement* targetElement = this->targetElement();
205 const QualifiedName& attributeName = this->attributeName();
206 ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);
208 if (shouldApply == DontApplyAnimation)
211 if (shouldApply == ApplyXMLAnimation) {
212 // SVG DOM animVal animation code-path.
213 m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName);
214 ASSERT(!m_animatedProperties.isEmpty());
216 ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties));
218 m_animatedType = animator->startAnimValAnimation(m_animatedProperties);
220 animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get());
221 animator->animValDidChange(m_animatedProperties);
226 // CSS properties animation code-path.
227 ASSERT(m_animatedProperties.isEmpty());
230 if (shouldApply == ApplyCSSAnimation) {
231 ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
232 computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
236 m_animatedType = animator->constructFromString(baseValue);
238 m_animatedType->setValueAsString(attributeName, baseValue);
241 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
243 ASSERT(!targetElement->m_deletionHasBegun);
245 if (!targetElement->ensureAnimatedSMILStyleProperties().setProperty(id, value, false, 0))
248 targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
251 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
253 ASSERT(!targetElement->m_deletionHasBegun);
254 targetElement->ensureAnimatedSMILStyleProperties().removeProperty(id);
255 targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
258 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
260 ASSERT(targetElement);
261 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
264 CSSPropertyID id = cssPropertyID(attributeName.localName());
266 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
267 applyCSSPropertyToTarget(targetElement, id, valueAsString);
269 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
270 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
271 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
272 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
273 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
274 applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
278 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
280 ASSERT(targetElement);
281 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
284 CSSPropertyID id = cssPropertyID(attributeName.localName());
286 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
287 removeCSSPropertyFromTarget(targetElement, id);
289 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
290 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
291 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
292 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
293 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
294 removeCSSPropertyFromTarget(shadowTreeElement, id);
298 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
300 ASSERT(!targetElement->m_deletionHasBegun);
301 targetElement->svgAttributeChanged(attributeName);
304 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
306 ASSERT(targetElement);
307 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
310 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
311 notifyTargetAboutAnimValChange(targetElement, attributeName);
313 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
314 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
315 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
316 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
317 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
318 notifyTargetAboutAnimValChange(shadowTreeElement, attributeName);
322 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
327 if (!targetElement) {
328 m_animatedType.clear();
332 if (m_animatedProperties.isEmpty()) {
333 // CSS properties animation code-path.
334 removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
335 m_animatedType.clear();
339 // SVG DOM animVal animation code-path.
341 m_animator->stopAnimValAnimation(m_animatedProperties);
342 notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
345 m_animatedProperties.clear();
346 m_animatedType.clear();
349 void SVGAnimateElement::applyResultsToTarget()
351 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
352 ASSERT(m_animatedPropertyType != AnimatedUnknown);
355 // Early exit if our animated type got destructed by a previous endedActiveInterval().
359 if (m_animatedProperties.isEmpty()) {
360 // CSS properties animation code-path.
361 // Convert the result of the animation to a String and apply it as CSS property on the target & all instances.
362 applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedType->valueAsString());
366 // SVG DOM animVal animation code-path.
367 // At this point the SVG DOM values are already changed, unlike for CSS.
368 // We only have to trigger update notifications here.
369 m_animator->animValDidChange(m_animatedProperties);
370 notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
373 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() const
375 // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
376 switch (m_animatedPropertyType) {
377 case AnimatedBoolean:
378 case AnimatedEnumeration:
379 case AnimatedPreserveAspectRatio:
381 case AnimatedUnknown:
385 case AnimatedInteger:
386 case AnimatedIntegerOptionalInteger:
388 case AnimatedLengthList:
390 case AnimatedNumberList:
391 case AnimatedNumberOptionalNumber:
395 case AnimatedTransformList:
398 RELEASE_ASSERT_NOT_REACHED();
403 bool SVGAnimateElement::isAdditive() const
405 if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
406 if (!animatedPropertyTypeSupportsAddition())
409 return SVGAnimationElement::isAdditive();
412 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
414 // FIXME: A return value of float is not enough to support paced animations on lists.
415 SVGElement* targetElement = this->targetElement();
419 return ensureAnimator()->calculateDistance(fromString, toString);
422 void SVGAnimateElement::setTargetElement(SVGElement* target)
424 SVGAnimationElement::setTargetElement(target);
425 resetAnimatedPropertyType();
428 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
430 SVGAnimationElement::setAttributeName(attributeName);
431 resetAnimatedPropertyType();
434 void SVGAnimateElement::resetAnimatedPropertyType()
436 ASSERT(!m_animatedType);
439 m_toAtEndOfDurationType.clear();
441 m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(targetElement()) : AnimatedString;
444 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
447 m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType);
448 ASSERT(m_animatedPropertyType == m_animator->type());
449 return m_animator.get();
454 #endif // ENABLE(SVG)