diff options
Diffstat (limited to 'Source/WebCore/svg/SVGAnimateElementBase.cpp')
-rw-r--r-- | Source/WebCore/svg/SVGAnimateElementBase.cpp | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/Source/WebCore/svg/SVGAnimateElementBase.cpp b/Source/WebCore/svg/SVGAnimateElementBase.cpp new file mode 100644 index 000000000..11ae7a981 --- /dev/null +++ b/Source/WebCore/svg/SVGAnimateElementBase.cpp @@ -0,0 +1,450 @@ +/* + * 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. + * Copyright (C) 2014 Adobe Systems Incorporated. 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 "SVGAnimateElementBase.h" + +#include "CSSPropertyNames.h" +#include "CSSPropertyParser.h" +#include "QualifiedName.h" +#include "RenderObject.h" +#include "SVGAnimatorFactory.h" +#include "SVGElement.h" +#include "SVGNames.h" +#include "StyleProperties.h" + +namespace WebCore { + +SVGAnimateElementBase::SVGAnimateElementBase(const QualifiedName& tagName, Document& document) + : SVGAnimationElement(tagName, document) + , m_animatedPropertyType(AnimatedString) +{ + ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag) || hasTagName(SVGNames::animateTransformTag)); +} + +SVGAnimateElementBase::~SVGAnimateElementBase() +{ +} + +bool SVGAnimateElementBase::hasValidAttributeType() +{ + SVGElement* targetElement = this->targetElement(); + if (!targetElement) + return false; + + return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType(); +} + +AnimatedPropertyType SVGAnimateElementBase::determineAnimatedPropertyType(SVGElement& targetElement) const +{ + auto propertyTypes = targetElement.animatedPropertyTypesForAttribute(attributeName()); + if (propertyTypes.isEmpty()) + return AnimatedUnknown; + + ASSERT(propertyTypes.size() <= 2); + AnimatedPropertyType type = propertyTypes[0]; + if (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor) + return AnimatedUnknown; + + // Animations of transform lists are not allowed for <animate> or <set> + // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties + if (type == AnimatedTransformList && !hasTagName(SVGNames::animateTransformTag)) + return AnimatedUnknown; + + // Fortunately there's just one special case needed here: SVGMarkerElements orientAttr, which + // corresponds to SVGAnimatedAngle orientAngle and SVGAnimatedEnumeration orientType. We have to + // figure out whose value to change here. + if (targetElement.hasTagName(SVGNames::markerTag) && type == AnimatedAngle) { + ASSERT(propertyTypes.size() == 2); + ASSERT(propertyTypes[0] == AnimatedAngle); + ASSERT(propertyTypes[1] == AnimatedEnumeration); + } else if (propertyTypes.size() == 2) + ASSERT(propertyTypes[0] == propertyTypes[1]); + + return type; +} + +void SVGAnimateElementBase::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement) +{ + ASSERT(resultElement); + SVGElement* targetElement = this->targetElement(); + if (!targetElement) + return; + + ASSERT(m_animatedPropertyType == determineAnimatedPropertyType(*targetElement)); + + ASSERT(percentage >= 0 && percentage <= 1); + ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag)); + ASSERT(m_animatedPropertyType != AnimatedUnknown); + ASSERT(m_animator); + ASSERT(m_animator->type() == m_animatedPropertyType); + ASSERT(m_fromType); + ASSERT(m_fromType->type() == m_animatedPropertyType); + ASSERT(m_toType); + + SVGAnimateElementBase& resultAnimationElement = downcast<SVGAnimateElementBase>(*resultElement); + ASSERT(resultAnimationElement.m_animatedType); + ASSERT(resultAnimationElement.m_animatedPropertyType == m_animatedPropertyType); + + if (hasTagName(SVGNames::setTag)) + percentage = 1; + + if (calcMode() == CalcMode::Discrete) + percentage = percentage < 0.5 ? 0 : 1; + + // Target element might have changed. + m_animator->setContextElement(targetElement); + + // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do + // if after calculateAnimatedValue() ran the cached pointers in the list propery tear + // offs would point nowhere, and we couldn't create copies of those values anymore, + // while detaching. This is covered by assertions, moving this down would fire them. + if (!m_animatedProperties.isEmpty()) + m_animator->animValWillChange(m_animatedProperties); + + // Values-animation accumulates using the last values entry corresponding to the end of duration time. + SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get(); + m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement.m_animatedType.get()); +} + +bool SVGAnimateElementBase::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) +{ + if (toAtEndOfDurationString.isEmpty()) + return false; + m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString); + return true; +} + +bool SVGAnimateElementBase::calculateFromAndToValues(const String& fromString, const String& toString) +{ + SVGElement* targetElement = this->targetElement(); + if (!targetElement) + return false; + + determinePropertyValueTypes(fromString, toString); + ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString); + ASSERT(m_animatedPropertyType == m_animator->type()); + return true; +} + +bool SVGAnimateElementBase::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(!hasTagName(SVGNames::setTag)); + + determinePropertyValueTypes(fromString, byString); + ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString); + ASSERT(m_animatedPropertyType == m_animator->type()); + return true; +} + +#ifndef NDEBUG +static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes) +{ + for (auto& type : animatedTypes) { + for (auto& property : type.properties) { + if (expectedPropertyType != property->animatedPropertyType()) { + // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute. + if (expectedPropertyType == AnimatedAngle && property->animatedPropertyType() == AnimatedEnumeration) + return true; + return false; + } + } + } + + return true; +} +#endif + +void SVGAnimateElementBase::resetAnimatedType() +{ + SVGAnimatedTypeAnimator* animator = ensureAnimator(); + ASSERT(m_animatedPropertyType == animator->type()); + + SVGElement* targetElement = this->targetElement(); + if (!targetElement) + return; + + const QualifiedName& attributeName = this->attributeName(); + ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName); + + if (shouldApply == DontApplyAnimation) + return; + + if (shouldApply == ApplyXMLAnimation || shouldApply == ApplyXMLandCSSAnimation) { + // SVG DOM animVal animation code-path. + m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(*targetElement, attributeName); + if (m_animatedProperties.isEmpty()) + return; + + ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties)); + if (!m_animatedType) + m_animatedType = animator->startAnimValAnimation(m_animatedProperties); + else { + animator->resetAnimValToBaseVal(m_animatedProperties, *m_animatedType); + animator->animValDidChange(m_animatedProperties); + } + return; + } + + // CSS properties animation code-path. + ASSERT(m_animatedProperties.isEmpty()); + String baseValue; + + if (shouldApply == ApplyCSSAnimation) { + ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName)); + computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue); + } + + if (!m_animatedType) + m_animatedType = animator->constructFromString(baseValue); + else + m_animatedType->setValueAsString(attributeName, baseValue); +} + +static inline void applyCSSPropertyToTarget(SVGElement& targetElement, CSSPropertyID id, const String& value) +{ + ASSERT(!targetElement.m_deletionHasBegun); + + if (!targetElement.ensureAnimatedSMILStyleProperties().setProperty(id, value, false)) + return; + + targetElement.invalidateStyleAndLayerComposition(); +} + +static inline void removeCSSPropertyFromTarget(SVGElement& targetElement, CSSPropertyID id) +{ + ASSERT(!targetElement.m_deletionHasBegun); + targetElement.ensureAnimatedSMILStyleProperties().removeProperty(id); + targetElement.invalidateStyleAndLayerComposition(); +} + +static inline void applyCSSPropertyToTargetAndInstances(SVGElement& targetElement, const QualifiedName& attributeName, const String& valueAsString) +{ + // FIXME: Do we really need to check both isConnected and !parentNode? + if (attributeName == anyQName() || !targetElement.isConnected() || !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. + for (auto* instance : targetElement.instances()) + applyCSSPropertyToTarget(*instance, id, valueAsString); +} + +static inline void removeCSSPropertyFromTargetAndInstances(SVGElement& targetElement, const QualifiedName& attributeName) +{ + // FIXME: Do we really need to check both isConnected and !parentNode? + if (attributeName == anyQName() || !targetElement.isConnected() || !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. + for (auto* instance : targetElement.instances()) + removeCSSPropertyFromTarget(*instance, id); +} + +static inline void notifyTargetAboutAnimValChange(SVGElement& targetElement, const QualifiedName& attributeName) +{ + ASSERT(!targetElement.m_deletionHasBegun); + targetElement.svgAttributeChanged(attributeName); +} + +static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement& targetElement, const QualifiedName& attributeName) +{ + if (attributeName == anyQName() || !targetElement.isConnected() || !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 (auto* instance : targetElement.instances()) + notifyTargetAboutAnimValChange(*instance, attributeName); +} + +void SVGAnimateElementBase::clearAnimatedType(SVGElement* targetElement) +{ + if (!m_animatedType) + return; + + if (!targetElement) { + m_animatedType = nullptr; + return; + } + + if (m_animatedProperties.isEmpty()) { + // CSS properties animation code-path. + removeCSSPropertyFromTargetAndInstances(*targetElement, attributeName()); + m_animatedType = nullptr; + return; + } + + ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName()); + if (shouldApply == ApplyXMLandCSSAnimation) + removeCSSPropertyFromTargetAndInstances(*targetElement, attributeName()); + + // SVG DOM animVal animation code-path. + if (m_animator) { + m_animator->stopAnimValAnimation(m_animatedProperties); + notifyTargetAndInstancesAboutAnimValChange(*targetElement, attributeName()); + } + + m_animatedProperties.clear(); + m_animatedType = nullptr; +} + +void SVGAnimateElementBase::applyResultsToTarget() +{ + ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag)); + ASSERT(m_animatedPropertyType != AnimatedUnknown); + ASSERT(m_animator); + + // Early exit if our animated type got destroyed by a previous endedActiveInterval(). + if (!m_animatedType) + return; + + SVGElement* targetElement = this->targetElement(); + const QualifiedName& attributeName = this->attributeName(); + + ASSERT(targetElement); + + if (m_animatedProperties.isEmpty()) { + // 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_animatedType->valueAsString()); + 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_animatedType->valueAsString()); + + // 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. + m_animator->animValDidChange(m_animatedProperties); + notifyTargetAndInstancesAboutAnimValChange(*targetElement, attributeName); +} + +bool SVGAnimateElementBase::animatedPropertyTypeSupportsAddition() const +{ + // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties. + switch (m_animatedPropertyType) { + case AnimatedBoolean: + case AnimatedEnumeration: + case AnimatedPreserveAspectRatio: + case AnimatedString: + case AnimatedUnknown: + return false; + case AnimatedAngle: + case AnimatedColor: + case AnimatedInteger: + case AnimatedIntegerOptionalInteger: + case AnimatedLength: + case AnimatedLengthList: + case AnimatedNumber: + case AnimatedNumberList: + case AnimatedNumberOptionalNumber: + case AnimatedPath: + case AnimatedPoints: + case AnimatedRect: + case AnimatedTransformList: + return true; + default: + RELEASE_ASSERT_NOT_REACHED(); + return true; + } +} + +bool SVGAnimateElementBase::isAdditive() const +{ + if (animationMode() == ByAnimation || animationMode() == FromByAnimation) { + if (!animatedPropertyTypeSupportsAddition()) + return false; + } + + return SVGAnimationElement::isAdditive(); +} + +float SVGAnimateElementBase::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 ensureAnimator()->calculateDistance(fromString, toString); +} + +void SVGAnimateElementBase::setTargetElement(SVGElement* target) +{ + SVGAnimationElement::setTargetElement(target); + resetAnimatedPropertyType(); +} + +void SVGAnimateElementBase::setAttributeName(const QualifiedName& attributeName) +{ + SVGSMILElement::setAttributeName(attributeName); + checkInvalidCSSAttributeType(targetElement()); + resetAnimatedPropertyType(); +} + +void SVGAnimateElementBase::resetAnimatedPropertyType() +{ + SVGAnimationElement::resetAnimatedPropertyType(); + ASSERT(!m_animatedType); + m_fromType = nullptr; + m_toType = nullptr; + m_toAtEndOfDurationType = nullptr; + m_animator = nullptr; + m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(*targetElement()) : AnimatedString; +} + +SVGAnimatedTypeAnimator* SVGAnimateElementBase::ensureAnimator() +{ + if (!m_animator) + m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType); + ASSERT(m_animatedPropertyType == m_animator->type()); + return m_animator.get(); +} + +} // namespace WebCore |