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