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 "core/CSSPropertyNames.h"
28 #include "core/css/parser/BisonCSSParser.h"
29 #include "core/css/StylePropertySet.h"
30 #include "core/dom/Document.h"
31 #include "core/dom/QualifiedName.h"
32 #include "core/svg/SVGAnimatedTypeAnimator.h"
33 #include "core/svg/SVGDocumentExtensions.h"
34
35 namespace WebCore {
36
SVGAnimateElement(const QualifiedName & tagName,Document & document)37 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document)
38 : SVGAnimationElement(tagName, document)
39 {
40 ASSERT(isSVGAnimateElement(*this));
41 ScriptWrappable::init(this);
42 }
43
create(Document & document)44 PassRefPtrWillBeRawPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document)
45 {
46 return adoptRefWillBeNoop(new SVGAnimateElement(SVGNames::animateTag, document));
47 }
48
~SVGAnimateElement()49 SVGAnimateElement::~SVGAnimateElement()
50 {
51 }
52
animatedPropertyType()53 AnimatedPropertyType SVGAnimateElement::animatedPropertyType()
54 {
55 return ensureAnimator()->type();
56 }
57
hasValidAttributeType()58 bool SVGAnimateElement::hasValidAttributeType()
59 {
60 SVGElement* targetElement = this->targetElement();
61 if (!targetElement)
62 return false;
63
64 return animatedPropertyType() != AnimatedUnknown && !hasInvalidCSSAttributeType();
65 }
66
calculateAnimatedValue(float percentage,unsigned repeatCount,SVGSMILElement * resultElement)67 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement)
68 {
69 ASSERT(resultElement);
70 SVGElement* targetElement = this->targetElement();
71 if (!targetElement || !isSVGAnimateElement(*resultElement))
72 return;
73
74 ASSERT(percentage >= 0 && percentage <= 1);
75 ASSERT(m_animator);
76 ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this));
77 ASSERT(animatedPropertyType() != AnimatedUnknown);
78 ASSERT(m_fromProperty);
79 ASSERT(m_fromProperty->type() == animatedPropertyType());
80 ASSERT(m_toProperty);
81
82 SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement);
83 ASSERT(resultAnimationElement->m_animatedProperty);
84 ASSERT(resultAnimationElement->animatedPropertyType() == animatedPropertyType());
85
86 if (isSVGSetElement(*this))
87 percentage = 1;
88
89 if (calcMode() == CalcModeDiscrete)
90 percentage = percentage < 0.5 ? 0 : 1;
91
92 // Target element might have changed.
93 m_animator->setContextElement(targetElement);
94
95 // Values-animation accumulates using the last values entry corresponding to the end of duration time.
96 SVGPropertyBase* toAtEndOfDurationProperty = m_toAtEndOfDurationProperty ? m_toAtEndOfDurationProperty.get() : m_toProperty.get();
97 m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromProperty.get(), m_toProperty.get(), toAtEndOfDurationProperty, resultAnimationElement->m_animatedProperty.get());
98 }
99
calculateToAtEndOfDurationValue(const String & toAtEndOfDurationString)100 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
101 {
102 if (toAtEndOfDurationString.isEmpty())
103 return false;
104 m_toAtEndOfDurationProperty = ensureAnimator()->constructFromString(toAtEndOfDurationString);
105 return true;
106 }
107
calculateFromAndToValues(const String & fromString,const String & toString)108 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
109 {
110 SVGElement* targetElement = this->targetElement();
111 if (!targetElement)
112 return false;
113
114 determinePropertyValueTypes(fromString, toString);
115 ensureAnimator()->calculateFromAndToValues(m_fromProperty, m_toProperty, fromString, toString);
116 return true;
117 }
118
calculateFromAndByValues(const String & fromString,const String & byString)119 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
120 {
121 SVGElement* targetElement = this->targetElement();
122 if (!targetElement)
123 return false;
124
125 if (animationMode() == ByAnimation && !isAdditive())
126 return false;
127
128 // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes).
129 if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition())
130 return false;
131
132 ASSERT(!isSVGSetElement(*this));
133
134 determinePropertyValueTypes(fromString, byString);
135 ensureAnimator()->calculateFromAndByValues(m_fromProperty, m_toProperty, fromString, byString);
136 return true;
137 }
138
139 namespace {
140
findElementInstances(SVGElement * targetElement)141 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > findElementInstances(SVGElement* targetElement)
142 {
143 ASSERT(targetElement);
144 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements;
145
146 animatedElements.append(targetElement);
147
148 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement();
149 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end();
150 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) {
151 if (SVGElement* shadowTreeElement = *it)
152 animatedElements.append(shadowTreeElement);
153 }
154
155 return animatedElements;
156 }
157
158 }
159
resetAnimatedType()160 void SVGAnimateElement::resetAnimatedType()
161 {
162 SVGAnimatedTypeAnimator* animator = ensureAnimator();
163
164 SVGElement* targetElement = this->targetElement();
165 const QualifiedName& attributeName = this->attributeName();
166 ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);
167
168 if (shouldApply == DontApplyAnimation)
169 return;
170
171 if (shouldApply == ApplyXMLAnimation) {
172 // SVG DOM animVal animation code-path.
173 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements = findElementInstances(targetElement);
174 ASSERT(!animatedElements.isEmpty());
175
176 WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator end = animatedElements.end();
177 for (WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator it = animatedElements.begin(); it != end; ++it)
178 document().accessSVGExtensions().addElementReferencingTarget(this, *it);
179
180 if (!m_animatedProperty)
181 m_animatedProperty = animator->startAnimValAnimation(animatedElements);
182 else
183 m_animatedProperty = animator->resetAnimValToBaseVal(animatedElements);
184
185 return;
186 }
187
188 // CSS properties animation code-path.
189 String baseValue;
190
191 if (shouldApply == ApplyCSSAnimation) {
192 ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
193 computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
194 }
195
196 m_animatedProperty = animator->constructFromString(baseValue);
197 }
198
applyCSSPropertyToTarget(SVGElement * targetElement,CSSPropertyID id,const String & value)199 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
200 {
201 #if !ENABLE(OILPAN)
202 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun);
203 #endif
204
205 MutableStylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties();
206 if (!propertySet->setProperty(id, value, false, 0))
207 return;
208
209 targetElement->setNeedsStyleRecalc(LocalStyleChange);
210 }
211
removeCSSPropertyFromTarget(SVGElement * targetElement,CSSPropertyID id)212 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
213 {
214 #if !ENABLE(OILPAN)
215 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun);
216 #endif
217 targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id);
218 targetElement->setNeedsStyleRecalc(LocalStyleChange);
219 }
220
applyCSSPropertyToTargetAndInstances(SVGElement * targetElement,const QualifiedName & attributeName,const String & valueAsString)221 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
222 {
223 ASSERT(targetElement);
224 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
225 return;
226
227 CSSPropertyID id = cssPropertyID(attributeName.localName());
228
229 SVGElement::InstanceUpdateBlocker blocker(targetElement);
230 applyCSSPropertyToTarget(targetElement, id, valueAsString);
231
232 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
233 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement();
234 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end();
235 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) {
236 if (SVGElement* shadowTreeElement = *it)
237 applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
238 }
239 }
240
removeCSSPropertyFromTargetAndInstances(SVGElement * targetElement,const QualifiedName & attributeName)241 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
242 {
243 ASSERT(targetElement);
244 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
245 return;
246
247 CSSPropertyID id = cssPropertyID(attributeName.localName());
248
249 SVGElement::InstanceUpdateBlocker blocker(targetElement);
250 removeCSSPropertyFromTarget(targetElement, id);
251
252 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
253 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement();
254 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end();
255 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) {
256 if (SVGElement* shadowTreeElement = *it)
257 removeCSSPropertyFromTarget(shadowTreeElement, id);
258 }
259 }
260
notifyTargetAboutAnimValChange(SVGElement * targetElement,const QualifiedName & attributeName)261 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
262 {
263 #if !ENABLE(OILPAN)
264 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun);
265 #endif
266 targetElement->invalidateSVGAttributes();
267 targetElement->svgAttributeChanged(attributeName);
268 }
269
notifyTargetAndInstancesAboutAnimValChange(SVGElement * targetElement,const QualifiedName & attributeName)270 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
271 {
272 ASSERT(targetElement);
273 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
274 return;
275
276 SVGElement::InstanceUpdateBlocker blocker(targetElement);
277 notifyTargetAboutAnimValChange(targetElement, attributeName);
278
279 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
280 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement();
281 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end();
282 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) {
283 notifyTargetAboutAnimValChange(*it, attributeName);
284 }
285 }
286
clearAnimatedType(SVGElement * targetElement)287 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
288 {
289 if (!m_animatedProperty)
290 return;
291
292 if (!targetElement) {
293 m_animatedProperty.clear();
294 return;
295 }
296
297 if (ensureAnimator()->isAnimatingCSSProperty()) {
298 // CSS properties animation code-path.
299 removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
300 m_animatedProperty.clear();
301 return;
302 }
303
304 // SVG DOM animVal animation code-path.
305 if (m_animator) {
306 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements = findElementInstances(targetElement);
307 m_animator->stopAnimValAnimation(animatedElements);
308 notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
309 }
310
311 m_animatedProperty.clear();
312 }
313
applyResultsToTarget()314 void SVGAnimateElement::applyResultsToTarget()
315 {
316 ASSERT(m_animator);
317 ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this));
318 ASSERT(animatedPropertyType() != AnimatedUnknown);
319
320 // Early exit if our animated type got destructed by a previous endedActiveInterval().
321 if (!m_animatedProperty)
322 return;
323
324 if (m_animator->isAnimatingCSSProperty()) {
325 // CSS properties animation code-path.
326 // Convert the result of the animation to a String and apply it as CSS property on the target & all instances.
327 applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedProperty->valueAsString());
328 return;
329 }
330
331 // SVG DOM animVal animation code-path.
332 // At this point the SVG DOM values are already changed, unlike for CSS.
333 // We only have to trigger update notifications here.
334 notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
335 }
336
animatedPropertyTypeSupportsAddition()337 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition()
338 {
339 // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
340 switch (animatedPropertyType()) {
341 case AnimatedBoolean:
342 case AnimatedEnumeration:
343 case AnimatedPreserveAspectRatio:
344 case AnimatedString:
345 case AnimatedUnknown:
346 return false;
347 default:
348 return true;
349 }
350 }
351
isAdditive()352 bool SVGAnimateElement::isAdditive()
353 {
354 if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
355 if (!animatedPropertyTypeSupportsAddition())
356 return false;
357
358 return SVGAnimationElement::isAdditive();
359 }
360
calculateDistance(const String & fromString,const String & toString)361 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
362 {
363 // FIXME: A return value of float is not enough to support paced animations on lists.
364 SVGElement* targetElement = this->targetElement();
365 if (!targetElement)
366 return -1;
367
368 return ensureAnimator()->calculateDistance(fromString, toString);
369 }
370
setTargetElement(SVGElement * target)371 void SVGAnimateElement::setTargetElement(SVGElement* target)
372 {
373 SVGAnimationElement::setTargetElement(target);
374 resetAnimatedPropertyType();
375 }
376
setAttributeName(const QualifiedName & attributeName)377 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
378 {
379 SVGAnimationElement::setAttributeName(attributeName);
380 resetAnimatedPropertyType();
381 }
382
resetAnimatedPropertyType()383 void SVGAnimateElement::resetAnimatedPropertyType()
384 {
385 ASSERT(!m_animatedProperty);
386 m_fromProperty.clear();
387 m_toProperty.clear();
388 m_toAtEndOfDurationProperty.clear();
389 m_animator.clear();
390 }
391
ensureAnimator()392 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
393 {
394 if (!m_animator)
395 m_animator = SVGAnimatedTypeAnimator::create(this, targetElement());
396 return m_animator.get();
397 }
398
trace(Visitor * visitor)399 void SVGAnimateElement::trace(Visitor* visitor)
400 {
401 visitor->trace(m_animator);
402 SVGAnimationElement::trace(visitor);
403 }
404
405 }
406