blob: ea8c6ee4d89d6a03aba2bdaf62b3a66ea380219d [file] [log] [blame]
/*
* 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);
}
}