| /* |
| * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@kde.org> |
| * Copyright (C) 2008 Apple Inc. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| |
| #include "core/svg/SVGAnimateElement.h" |
| |
| #include "core/CSSPropertyNames.h" |
| #include "core/css/StylePropertySet.h" |
| #include "core/css/parser/CSSParser.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/QualifiedName.h" |
| #include "core/svg/SVGAnimatedTypeAnimator.h" |
| #include "core/svg/SVGDocumentExtensions.h" |
| |
| namespace blink { |
| |
| SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document) |
| : SVGAnimationElement(tagName, document) |
| , m_animator(this) |
| { |
| } |
| |
| PassRefPtrWillBeRawPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document) |
| { |
| return adoptRefWillBeNoop(new SVGAnimateElement(SVGNames::animateTag, document)); |
| } |
| |
| SVGAnimateElement::~SVGAnimateElement() |
| { |
| } |
| |
| bool SVGAnimateElement::isSVGAnimationAttributeSettingJavaScriptURL(const Attribute& attribute) const |
| { |
| if ((attribute.name() == SVGNames::fromAttr || attribute.name() == SVGNames::toAttr) && attributeValueIsJavaScriptURL(attribute)) |
| return true; |
| |
| if (attribute.name() == SVGNames::valuesAttr) { |
| Vector<String> parts; |
| if (!parseValues(attribute.value(), parts)) { |
| // Assume the worst. |
| return true; |
| } |
| for (const auto& part : parts) { |
| if (protocolIsJavaScript(part)) |
| return true; |
| } |
| } |
| |
| return SVGSMILElement::isSVGAnimationAttributeSettingJavaScriptURL(attribute); |
| } |
| |
| AnimatedPropertyType SVGAnimateElement::animatedPropertyType() |
| { |
| if (!targetElement()) |
| return AnimatedUnknown; |
| |
| m_animator.reset(targetElement()); |
| return m_animator.type(); |
| } |
| |
| bool SVGAnimateElement::hasValidAttributeType() |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| |
| return animatedPropertyType() != AnimatedUnknown && !hasInvalidCSSAttributeType(); |
| } |
| |
| void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement) |
| { |
| ASSERT(resultElement); |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement || !isSVGAnimateElement(*resultElement)) |
| return; |
| |
| ASSERT(percentage >= 0 && percentage <= 1); |
| ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this)); |
| ASSERT(animatedPropertyType() != AnimatedUnknown); |
| ASSERT(m_fromProperty); |
| ASSERT(m_fromProperty->type() == animatedPropertyType()); |
| ASSERT(m_toProperty); |
| |
| SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement); |
| ASSERT(resultAnimationElement->m_animatedProperty); |
| ASSERT(resultAnimationElement->animatedPropertyType() == animatedPropertyType()); |
| |
| if (isSVGSetElement(*this)) |
| percentage = 1; |
| |
| if (calcMode() == CalcModeDiscrete) |
| percentage = percentage < 0.5 ? 0 : 1; |
| |
| // Target element might have changed. |
| m_animator.setContextElement(targetElement); |
| |
| // Values-animation accumulates using the last values entry corresponding to the end of duration time. |
| SVGPropertyBase* toAtEndOfDurationProperty = m_toAtEndOfDurationProperty ? m_toAtEndOfDurationProperty.get() : m_toProperty.get(); |
| m_animator.calculateAnimatedValue(percentage, repeatCount, m_fromProperty.get(), m_toProperty.get(), toAtEndOfDurationProperty, resultAnimationElement->m_animatedProperty.get()); |
| } |
| |
| bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) |
| { |
| if (toAtEndOfDurationString.isEmpty()) |
| return false; |
| m_toAtEndOfDurationProperty = m_animator.constructFromString(toAtEndOfDurationString); |
| return true; |
| } |
| |
| bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| |
| determinePropertyValueTypes(fromString, toString); |
| m_animator.calculateFromAndToValues(m_fromProperty, m_toProperty, fromString, toString); |
| return true; |
| } |
| |
| bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| |
| if (animationMode() == ByAnimation && !isAdditive()) |
| return false; |
| |
| // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes). |
| if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition()) |
| return false; |
| |
| ASSERT(!isSVGSetElement(*this)); |
| |
| determinePropertyValueTypes(fromString, byString); |
| m_animator.calculateFromAndByValues(m_fromProperty, m_toProperty, fromString, byString); |
| return true; |
| } |
| |
| SVGElementInstances SVGAnimateElement::findElementInstances(SVGElement* targetElement) |
| { |
| ASSERT(targetElement); |
| SVGElementInstances animatedElements; |
| |
| animatedElements.append(targetElement); |
| |
| const auto& instances = targetElement->instancesForElement(); |
| animatedElements.appendRange(instances.begin(), instances.end()); |
| |
| return animatedElements; |
| } |
| |
| void SVGAnimateElement::resetAnimatedType() |
| { |
| SVGElement* targetElement = this->targetElement(); |
| const QualifiedName& attributeName = this->attributeName(); |
| |
| m_animator.reset(targetElement); |
| |
| ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName); |
| |
| if (shouldApply == DontApplyAnimation) |
| return; |
| if (shouldApply == ApplyXMLAnimation || shouldApply == ApplyXMLandCSSAnimation) { |
| // SVG DOM animVal animation code-path. |
| SVGElementInstances animatedElements = findElementInstances(targetElement); |
| ASSERT(!animatedElements.isEmpty()); |
| |
| for (SVGElement* element : animatedElements) |
| addReferenceTo(element); |
| |
| if (!m_animatedProperty) |
| m_animatedProperty = m_animator.startAnimValAnimation(animatedElements); |
| else |
| m_animatedProperty = m_animator.resetAnimValToBaseVal(animatedElements); |
| |
| return; |
| } |
| |
| // CSS properties animation code-path. |
| String baseValue; |
| |
| if (shouldApply == ApplyCSSAnimation) { |
| ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName)); |
| computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue); |
| } |
| |
| m_animatedProperty = m_animator.constructFromString(baseValue); |
| } |
| |
| static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value) |
| { |
| #if !ENABLE(OILPAN) |
| ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); |
| #endif |
| |
| MutableStylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties(); |
| if (!propertySet->setProperty(id, value, false, 0)) |
| return; |
| |
| targetElement->setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Animation)); |
| } |
| |
| static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id) |
| { |
| #if !ENABLE(OILPAN) |
| ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); |
| #endif |
| targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id); |
| targetElement->setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Animation)); |
| } |
| |
| static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString) |
| { |
| ASSERT(targetElement); |
| if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) |
| return; |
| |
| CSSPropertyID id = cssPropertyID(attributeName.localName()); |
| |
| SVGElement::InstanceUpdateBlocker blocker(targetElement); |
| applyCSSPropertyToTarget(targetElement, id, valueAsString); |
| |
| // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. |
| const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement>>& instances = targetElement->instancesForElement(); |
| for (SVGElement* shadowTreeElement : instances) { |
| if (shadowTreeElement) |
| applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString); |
| } |
| } |
| |
| static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName) |
| { |
| ASSERT(targetElement); |
| if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) |
| return; |
| |
| CSSPropertyID id = cssPropertyID(attributeName.localName()); |
| |
| SVGElement::InstanceUpdateBlocker blocker(targetElement); |
| removeCSSPropertyFromTarget(targetElement, id); |
| |
| // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. |
| const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement>>& instances = targetElement->instancesForElement(); |
| for (SVGElement* shadowTreeElement : instances) { |
| if (shadowTreeElement) |
| removeCSSPropertyFromTarget(shadowTreeElement, id); |
| } |
| } |
| |
| static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) |
| { |
| #if !ENABLE(OILPAN) |
| ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); |
| #endif |
| targetElement->invalidateSVGAttributes(); |
| targetElement->svgAttributeChanged(attributeName); |
| } |
| |
| static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) |
| { |
| ASSERT(targetElement); |
| if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) |
| return; |
| |
| SVGElement::InstanceUpdateBlocker blocker(targetElement); |
| notifyTargetAboutAnimValChange(targetElement, attributeName); |
| |
| // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. |
| for (SVGElement* element : targetElement->instancesForElement()) |
| notifyTargetAboutAnimValChange(element, attributeName); |
| } |
| |
| void SVGAnimateElement::clearAnimatedType() |
| { |
| if (!m_animatedProperty) |
| return; |
| |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) { |
| m_animatedProperty.clear(); |
| return; |
| } |
| |
| ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName()); |
| if (shouldApply == ApplyXMLandCSSAnimation) { |
| removeCSSPropertyFromTargetAndInstances(targetElement, attributeName()); |
| } else if (m_animator.isAnimatingCSSProperty()) { |
| // CSS properties animation code-path. |
| removeCSSPropertyFromTargetAndInstances(targetElement, attributeName()); |
| m_animatedProperty.clear(); |
| m_animator.clear(); |
| return; |
| } |
| |
| // SVG DOM animVal animation code-path. |
| if (m_animatedProperty) { |
| SVGElementInstances animatedElements = findElementInstances(targetElement); |
| m_animator.stopAnimValAnimation(animatedElements); |
| notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName()); |
| } |
| |
| m_animatedProperty.clear(); |
| m_animator.clear(); |
| } |
| |
| void SVGAnimateElement::applyResultsToTarget() |
| { |
| ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this)); |
| ASSERT(animatedPropertyType() != AnimatedUnknown); |
| |
| // Early exit if our animated type got destructed by a previous endedActiveInterval(). |
| if (!m_animatedProperty) |
| return; |
| |
| // We do update the style and the animation property independent of each other. |
| ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement(), attributeName()); |
| if (shouldApply == ApplyXMLandCSSAnimation) { |
| applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedProperty->valueAsString()); |
| } else if (m_animator.isAnimatingCSSProperty()) { |
| // CSS properties animation code-path. |
| // Convert the result of the animation to a String and apply it as CSS property on the target & all instances. |
| applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedProperty->valueAsString()); |
| return; |
| } |
| |
| // SVG DOM animVal animation code-path. |
| // At this point the SVG DOM values are already changed, unlike for CSS. |
| // We only have to trigger update notifications here. |
| notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName()); |
| } |
| |
| bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() |
| { |
| // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties. |
| switch (animatedPropertyType()) { |
| case AnimatedBoolean: |
| case AnimatedEnumeration: |
| case AnimatedPreserveAspectRatio: |
| case AnimatedString: |
| case AnimatedUnknown: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| bool SVGAnimateElement::isAdditive() |
| { |
| if (animationMode() == ByAnimation || animationMode() == FromByAnimation) |
| if (!animatedPropertyTypeSupportsAddition()) |
| return false; |
| |
| return SVGAnimationElement::isAdditive(); |
| } |
| |
| float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString) |
| { |
| // FIXME: A return value of float is not enough to support paced animations on lists. |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return -1; |
| |
| return m_animator.calculateDistance(fromString, toString); |
| } |
| |
| void SVGAnimateElement::setTargetElement(SVGElement* target) |
| { |
| SVGAnimationElement::setTargetElement(target); |
| resetAnimatedPropertyType(); |
| } |
| |
| void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName) |
| { |
| SVGAnimationElement::setAttributeName(attributeName); |
| resetAnimatedPropertyType(); |
| } |
| |
| void SVGAnimateElement::resetAnimatedPropertyType() |
| { |
| ASSERT(!m_animatedProperty); |
| m_fromProperty.clear(); |
| m_toProperty.clear(); |
| m_toAtEndOfDurationProperty.clear(); |
| m_animator.clear(); |
| } |
| |
| DEFINE_TRACE(SVGAnimateElement) |
| { |
| visitor->trace(m_fromProperty); |
| visitor->trace(m_toProperty); |
| visitor->trace(m_toAtEndOfDurationProperty); |
| visitor->trace(m_animatedProperty); |
| visitor->trace(m_animator); |
| SVGAnimationElement::trace(visitor); |
| } |
| |
| } |