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