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
36namespace WebCore {
37
38SVGAnimateElement::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
46PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document)
47{
48    return adoptRef(new SVGAnimateElement(SVGNames::animateTag, document));
49}
50
51SVGAnimateElement::~SVGAnimateElement()
52{
53    if (targetElement())
54        clearAnimatedType(targetElement());
55}
56
57bool SVGAnimateElement::hasValidAttributeType()
58{
59    SVGElement* targetElement = this->targetElement();
60    if (!targetElement)
61        return false;
62
63    return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType();
64}
65
66AnimatedPropertyType 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
98void 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
141bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
142{
143    if (toAtEndOfDurationString.isEmpty())
144        return false;
145    m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
146    return true;
147}
148
149bool 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
161bool 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
183static 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
201void 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
247static 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
258static 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
265static 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
285static 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
305static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
306{
307    ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun);
308    targetElement->svgAttributeChanged(attributeName);
309}
310
311static 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
329void 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
356void 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
380bool 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
395bool SVGAnimateElement::isAdditive() const
396{
397    if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
398        if (!animatedPropertyTypeSupportsAddition())
399            return false;
400
401    return SVGAnimationElement::isAdditive();
402}
403
404float 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
414void SVGAnimateElement::setTargetElement(SVGElement* target)
415{
416    SVGAnimationElement::setTargetElement(target);
417    resetAnimatedPropertyType();
418}
419
420void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
421{
422    SVGAnimationElement::setAttributeName(attributeName);
423    resetAnimatedPropertyType();
424}
425
426void 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
436SVGAnimatedTypeAnimator* 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