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 "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 
     37 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document)
     38     : SVGAnimationElement(tagName, document)
     39 {
     40     ASSERT(isSVGAnimateElement(*this));
     41     ScriptWrappable::init(this);
     42 }
     43 
     44 PassRefPtrWillBeRawPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document)
     45 {
     46     return adoptRefWillBeNoop(new SVGAnimateElement(SVGNames::animateTag, document));
     47 }
     48 
     49 SVGAnimateElement::~SVGAnimateElement()
     50 {
     51 }
     52 
     53 AnimatedPropertyType SVGAnimateElement::animatedPropertyType()
     54 {
     55     return ensureAnimator()->type();
     56 }
     57 
     58 bool SVGAnimateElement::hasValidAttributeType()
     59 {
     60     SVGElement* targetElement = this->targetElement();
     61     if (!targetElement)
     62         return false;
     63 
     64     return animatedPropertyType() != AnimatedUnknown && !hasInvalidCSSAttributeType();
     65 }
     66 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    352 bool SVGAnimateElement::isAdditive()
    353 {
    354     if (animationMode() == ByAnimation || animationMode() == FromByAnimation)
    355         if (!animatedPropertyTypeSupportsAddition())
    356             return false;
    357 
    358     return SVGAnimationElement::isAdditive();
    359 }
    360 
    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 
    371 void SVGAnimateElement::setTargetElement(SVGElement* target)
    372 {
    373     SVGAnimationElement::setTargetElement(target);
    374     resetAnimatedPropertyType();
    375 }
    376 
    377 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
    378 {
    379     SVGAnimationElement::setAttributeName(attributeName);
    380     resetAnimatedPropertyType();
    381 }
    382 
    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 
    392 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
    393 {
    394     if (!m_animator)
    395         m_animator = SVGAnimatedTypeAnimator::create(this, targetElement());
    396     return m_animator.get();
    397 }
    398 
    399 void SVGAnimateElement::trace(Visitor* visitor)
    400 {
    401     visitor->trace(m_animator);
    402     SVGAnimationElement::trace(visitor);
    403 }
    404 
    405 }
    406