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
35namespace WebCore {
36
37SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document)
38    : SVGAnimationElement(tagName, document)
39{
40    ASSERT(isSVGAnimateElement(*this));
41    ScriptWrappable::init(this);
42}
43
44PassRefPtrWillBeRawPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document)
45{
46    return adoptRefWillBeNoop(new SVGAnimateElement(SVGNames::animateTag, document));
47}
48
49SVGAnimateElement::~SVGAnimateElement()
50{
51}
52
53AnimatedPropertyType SVGAnimateElement::animatedPropertyType()
54{
55    return ensureAnimator()->type();
56}
57
58bool SVGAnimateElement::hasValidAttributeType()
59{
60    SVGElement* targetElement = this->targetElement();
61    if (!targetElement)
62        return false;
63
64    return animatedPropertyType() != AnimatedUnknown && !hasInvalidCSSAttributeType();
65}
66
67void 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
100bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
101{
102    if (toAtEndOfDurationString.isEmpty())
103        return false;
104    m_toAtEndOfDurationProperty = ensureAnimator()->constructFromString(toAtEndOfDurationString);
105    return true;
106}
107
108bool 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
119bool 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
139namespace {
140
141WillBeHeapVector<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
160void 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
199static 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
212static 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
221static 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
241static 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
261static 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
270static 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
287void 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
314void 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
337bool 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
352bool SVGAnimateElement::isAdditive()
353{
354    if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
355        if (!animatedPropertyTypeSupportsAddition())
356            return false;
357
358    return SVGAnimationElement::isAdditive();
359}
360
361float 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
371void SVGAnimateElement::setTargetElement(SVGElement* target)
372{
373    SVGAnimationElement::setTargetElement(target);
374    resetAnimatedPropertyType();
375}
376
377void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
378{
379    SVGAnimationElement::setAttributeName(attributeName);
380    resetAnimatedPropertyType();
381}
382
383void 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
392SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
393{
394    if (!m_animator)
395        m_animator = SVGAnimatedTypeAnimator::create(this, targetElement());
396    return m_animator.get();
397}
398
399void SVGAnimateElement::trace(Visitor* visitor)
400{
401    visitor->trace(m_animator);
402    SVGAnimationElement::trace(visitor);
403}
404
405}
406