| /* |
| * 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 "core/svg/SVGAnimateElement.h" |
| |
| #include "core/css/CSSComputedStyleDeclaration.h" |
| #include "core/css/CSSPropertyValueSet.h" |
| #include "core/css/StyleChangeReason.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/QualifiedName.h" |
| #include "core/svg/SVGAnimatedColor.h" |
| #include "core/svg/SVGLength.h" |
| #include "core/svg/SVGLengthList.h" |
| #include "core/svg/SVGNumber.h" |
| #include "core/svg/SVGString.h" |
| #include "core/svg/properties/SVGAnimatedProperty.h" |
| #include "core/svg/properties/SVGProperty.h" |
| #include "core/xlink_names.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| bool IsTargetAttributeCSSProperty(const SVGElement& target_element, |
| const QualifiedName& attribute_name) { |
| return SVGElement::IsAnimatableCSSProperty(attribute_name) || |
| target_element.IsPresentationAttribute(attribute_name); |
| } |
| |
| String ComputeCSSPropertyValue(SVGElement* element, CSSPropertyID id) { |
| DCHECK(element); |
| // TODO(fs): StyleEngine doesn't support document without a frame. |
| // Refer to comment in Element::computedStyle. |
| DCHECK(element->InActiveDocument()); |
| |
| // Don't include any properties resulting from CSS Transitions/Animations or |
| // SMIL animations, as we want to retrieve the "base value". |
| element->SetUseOverrideComputedStyle(true); |
| String value = |
| CSSComputedStyleDeclaration::Create(element)->GetPropertyValue(id); |
| element->SetUseOverrideComputedStyle(false); |
| return value; |
| } |
| |
| AnimatedPropertyValueType PropertyValueType(const QualifiedName& attribute_name, |
| const String& value) { |
| DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit")); |
| if (value.IsEmpty() || value != inherit || |
| !SVGElement::IsAnimatableCSSProperty(attribute_name)) |
| return kRegularPropertyValue; |
| return kInheritValue; |
| } |
| |
| QualifiedName ConstructQualifiedName(const SVGElement& svg_element, |
| const AtomicString& attribute_name) { |
| if (attribute_name.IsEmpty()) |
| return AnyQName(); |
| if (!attribute_name.Contains(':')) |
| return QualifiedName(g_null_atom, attribute_name, g_null_atom); |
| |
| AtomicString prefix; |
| AtomicString local_name; |
| if (!Document::ParseQualifiedName(attribute_name, prefix, local_name, |
| IGNORE_EXCEPTION_FOR_TESTING)) |
| return AnyQName(); |
| |
| const AtomicString& namespace_uri = svg_element.lookupNamespaceURI(prefix); |
| if (namespace_uri.IsEmpty()) |
| return AnyQName(); |
| |
| QualifiedName resolved_attr_name(g_null_atom, local_name, namespace_uri); |
| // "Animation elements treat attributeName='xlink:href' as being an alias |
| // for targetting the 'href' attribute." |
| // https://svgwg.org/svg2-draft/types.html#__svg__SVGURIReference__href |
| if (resolved_attr_name == XLinkNames::hrefAttr) |
| return SVGNames::hrefAttr; |
| return resolved_attr_name; |
| } |
| |
| } // unnamed namespace |
| |
| SVGAnimateElement::SVGAnimateElement(const QualifiedName& tag_name, |
| Document& document) |
| : SVGAnimationElement(tag_name, document), |
| type_(kAnimatedUnknown), |
| css_property_id_(CSSPropertyInvalid), |
| from_property_value_type_(kRegularPropertyValue), |
| to_property_value_type_(kRegularPropertyValue), |
| attribute_type_(kAttributeTypeAuto) {} |
| |
| SVGAnimateElement* SVGAnimateElement::Create(Document& document) { |
| return new SVGAnimateElement(SVGNames::animateTag, document); |
| } |
| |
| SVGAnimateElement::~SVGAnimateElement() {} |
| |
| bool SVGAnimateElement::IsSVGAnimationAttributeSettingJavaScriptURL( |
| const Attribute& attribute) const { |
| if ((attribute.GetName() == SVGNames::fromAttr || |
| attribute.GetName() == SVGNames::toAttr) && |
| AttributeValueIsJavaScriptURL(attribute)) |
| return true; |
| |
| if (attribute.GetName() == 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); |
| } |
| |
| Node::InsertionNotificationRequest SVGAnimateElement::InsertedInto( |
| ContainerNode* root_parent) { |
| SVGAnimationElement::InsertedInto(root_parent); |
| if (root_parent->isConnected()) { |
| SetAttributeName(ConstructQualifiedName( |
| *this, FastGetAttribute(SVGNames::attributeNameAttr))); |
| } |
| return kInsertionDone; |
| } |
| |
| void SVGAnimateElement::RemovedFrom(ContainerNode* root_parent) { |
| if (root_parent->isConnected()) |
| SetAttributeName(AnyQName()); |
| SVGAnimationElement::RemovedFrom(root_parent); |
| } |
| |
| void SVGAnimateElement::ParseAttribute( |
| const AttributeModificationParams& params) { |
| if (params.name == SVGNames::attributeTypeAttr) { |
| SetAttributeType(params.new_value); |
| AnimationAttributeChanged(); |
| return; |
| } |
| if (params.name == SVGNames::attributeNameAttr) { |
| SetAttributeName(ConstructQualifiedName(*this, params.new_value)); |
| AnimationAttributeChanged(); |
| return; |
| } |
| SVGAnimationElement::ParseAttribute(params); |
| } |
| |
| void SVGAnimateElement::ResolveTargetProperty() { |
| DCHECK(targetElement()); |
| target_property_ = targetElement()->PropertyFromAttribute(AttributeName()); |
| if (target_property_) { |
| type_ = target_property_->GetType(); |
| css_property_id_ = target_property_->CssPropertyId(); |
| |
| // Only <animateTransform> is allowed to animate AnimatedTransformList. |
| // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties |
| if (type_ == kAnimatedTransformList) { |
| type_ = kAnimatedUnknown; |
| css_property_id_ = CSSPropertyInvalid; |
| } |
| } else { |
| type_ = SVGElement::AnimatedPropertyTypeForCSSAttribute(AttributeName()); |
| css_property_id_ = type_ != kAnimatedUnknown |
| ? cssPropertyID(AttributeName().LocalName()) |
| : CSSPropertyInvalid; |
| } |
| // Blacklist <script> targets here for now to prevent unpleasantries. This |
| // also disallows the perfectly "valid" animation of 'className' on said |
| // element. If SVGScriptElement.href is transitioned off of SVGAnimatedHref, |
| // this can be removed. |
| if (IsSVGScriptElement(*targetElement())) { |
| type_ = kAnimatedUnknown; |
| css_property_id_ = CSSPropertyInvalid; |
| } |
| DCHECK(type_ != kAnimatedPoint && type_ != kAnimatedStringList && |
| type_ != kAnimatedTransform && type_ != kAnimatedTransformList); |
| } |
| |
| void SVGAnimateElement::ClearTargetProperty() { |
| target_property_ = nullptr; |
| type_ = kAnimatedUnknown; |
| css_property_id_ = CSSPropertyInvalid; |
| } |
| |
| AnimatedPropertyType SVGAnimateElement::GetAnimatedPropertyType() { |
| if (!targetElement()) |
| return kAnimatedUnknown; |
| ResolveTargetProperty(); |
| return type_; |
| } |
| |
| bool SVGAnimateElement::HasValidTarget() { |
| if (!SVGAnimationElement::HasValidTarget()) |
| return false; |
| if (AttributeName() == AnyQName()) |
| return false; |
| ResolveTargetProperty(); |
| if (type_ == kAnimatedUnknown) |
| return false; |
| // Always animate CSS properties using the ApplyCSSAnimation code path, |
| // regardless of the attributeType value. |
| // If attributeType="CSS" and attributeName doesn't point to a CSS property, |
| // ignore the animation. |
| return IsTargetAttributeCSSProperty(*targetElement(), AttributeName()) || |
| GetAttributeType() != kAttributeTypeCSS; |
| } |
| |
| bool SVGAnimateElement::ShouldApplyAnimation( |
| const SVGElement& target_element, |
| const QualifiedName& attribute_name) { |
| return target_element.parentNode() && HasValidTarget(); |
| } |
| |
| SVGPropertyBase* SVGAnimateElement::CreatePropertyForAttributeAnimation( |
| const String& value) const { |
| // SVG DOM animVal animation code-path. |
| // TransformList must be animated via <animateTransform>, and its |
| // {from,by,to} attribute values needs to be parsed w.r.t. its "type" |
| // attribute. Spec: |
| // http://www.w3.org/TR/SVG/single-page.html#animate-AnimateTransformElement |
| DCHECK_NE(type_, kAnimatedTransformList); |
| DCHECK(target_property_); |
| return target_property_->CurrentValueBase()->CloneForAnimation(value); |
| } |
| |
| SVGPropertyBase* SVGAnimateElement::CreatePropertyForCSSAnimation( |
| const String& value) const { |
| // CSS properties animation code-path. |
| // Create a basic instance of the corresponding SVG property. |
| // The instance will not have full context info. (e.g. SVGLengthMode) |
| switch (type_) { |
| case kAnimatedColor: |
| return SVGColorProperty::Create(value); |
| case kAnimatedNumber: { |
| SVGNumber* property = SVGNumber::Create(); |
| property->SetValueAsString(value); |
| return property; |
| } |
| case kAnimatedLength: { |
| SVGLength* property = SVGLength::Create(); |
| property->SetValueAsString(value); |
| return property; |
| } |
| case kAnimatedLengthList: { |
| SVGLengthList* property = SVGLengthList::Create(); |
| property->SetValueAsString(value); |
| return property; |
| } |
| case kAnimatedString: { |
| SVGString* property = SVGString::Create(); |
| property->SetValueAsString(value); |
| return property; |
| } |
| // These types don't appear in the table in |
| // SVGElement::animatedPropertyTypeForCSSAttribute() and thus don't need |
| // support. |
| case kAnimatedAngle: |
| case kAnimatedBoolean: |
| case kAnimatedEnumeration: |
| case kAnimatedInteger: |
| case kAnimatedIntegerOptionalInteger: |
| case kAnimatedNumberList: |
| case kAnimatedNumberOptionalNumber: |
| case kAnimatedPath: |
| case kAnimatedPoint: |
| case kAnimatedPoints: |
| case kAnimatedPreserveAspectRatio: |
| case kAnimatedRect: |
| case kAnimatedStringList: |
| case kAnimatedTransform: |
| case kAnimatedTransformList: |
| case kAnimatedUnknown: |
| break; |
| default: |
| break; |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| SVGPropertyBase* SVGAnimateElement::CreatePropertyForAnimation( |
| const String& value) const { |
| if (IsAnimatingSVGDom()) |
| return CreatePropertyForAttributeAnimation(value); |
| DCHECK(IsAnimatingCSSProperty()); |
| return CreatePropertyForCSSAnimation(value); |
| } |
| |
| SVGPropertyBase* SVGAnimateElement::AdjustForInheritance( |
| SVGPropertyBase* property_value, |
| AnimatedPropertyValueType value_type) const { |
| if (value_type != kInheritValue) |
| return property_value; |
| // TODO(fs): At the moment the computed style gets returned as a String and |
| // needs to get parsed again. In the future we might want to work with the |
| // value type directly to avoid the String parsing. |
| DCHECK(targetElement()); |
| Element* parent = targetElement()->parentElement(); |
| if (!parent || !parent->IsSVGElement()) |
| return property_value; |
| SVGElement* svg_parent = ToSVGElement(parent); |
| // Replace 'inherit' by its computed property value. |
| String value = ComputeCSSPropertyValue(svg_parent, css_property_id_); |
| return CreatePropertyForAnimation(value); |
| } |
| |
| void SVGAnimateElement::CalculateAnimatedValue(float percentage, |
| unsigned repeat_count, |
| SVGSMILElement* result_element) { |
| DCHECK(result_element); |
| DCHECK(targetElement()); |
| if (!IsSVGAnimateElement(*result_element)) |
| return; |
| |
| DCHECK(percentage >= 0 && percentage <= 1); |
| DCHECK_NE(GetAnimatedPropertyType(), kAnimatedUnknown); |
| DCHECK(from_property_); |
| DCHECK_EQ(from_property_->GetType(), GetAnimatedPropertyType()); |
| DCHECK(to_property_); |
| |
| SVGAnimateElement* result_animation_element = |
| ToSVGAnimateElement(result_element); |
| DCHECK(result_animation_element->animated_value_); |
| DCHECK_EQ(result_animation_element->GetAnimatedPropertyType(), |
| GetAnimatedPropertyType()); |
| |
| if (IsSVGSetElement(*this)) |
| percentage = 1; |
| |
| if (GetCalcMode() == kCalcModeDiscrete) |
| percentage = percentage < 0.5 ? 0 : 1; |
| |
| // Target element might have changed. |
| SVGElement* target_element = this->targetElement(); |
| |
| // Values-animation accumulates using the last values entry corresponding to |
| // the end of duration time. |
| SVGPropertyBase* animated_value = result_animation_element->animated_value_; |
| SVGPropertyBase* to_at_end_of_duration_value = |
| to_at_end_of_duration_property_ ? to_at_end_of_duration_property_ |
| : to_property_; |
| SVGPropertyBase* from_value = GetAnimationMode() == kToAnimation |
| ? animated_value |
| : from_property_.Get(); |
| SVGPropertyBase* to_value = to_property_; |
| |
| // Apply CSS inheritance rules. |
| from_value = AdjustForInheritance(from_value, from_property_value_type_); |
| to_value = AdjustForInheritance(to_value, to_property_value_type_); |
| |
| animated_value->CalculateAnimatedValue( |
| this, percentage, repeat_count, from_value, to_value, |
| to_at_end_of_duration_value, target_element); |
| } |
| |
| bool SVGAnimateElement::CalculateToAtEndOfDurationValue( |
| const String& to_at_end_of_duration_string) { |
| if (to_at_end_of_duration_string.IsEmpty()) |
| return false; |
| to_at_end_of_duration_property_ = |
| CreatePropertyForAnimation(to_at_end_of_duration_string); |
| return true; |
| } |
| |
| bool SVGAnimateElement::CalculateFromAndToValues(const String& from_string, |
| const String& to_string) { |
| DCHECK(targetElement()); |
| from_property_ = CreatePropertyForAnimation(from_string); |
| from_property_value_type_ = PropertyValueType(AttributeName(), from_string); |
| to_property_ = CreatePropertyForAnimation(to_string); |
| to_property_value_type_ = PropertyValueType(AttributeName(), to_string); |
| return true; |
| } |
| |
| bool SVGAnimateElement::CalculateFromAndByValues(const String& from_string, |
| const String& by_string) { |
| DCHECK(targetElement()); |
| |
| if (GetAnimationMode() == kByAnimation && !IsAdditive()) |
| return false; |
| |
| // from-by animation may only be used with attributes that support addition |
| // (e.g. most numeric attributes). |
| if (GetAnimationMode() == kFromByAnimation && |
| !AnimatedPropertyTypeSupportsAddition()) |
| return false; |
| |
| DCHECK(!IsSVGSetElement(*this)); |
| |
| from_property_ = CreatePropertyForAnimation(from_string); |
| from_property_value_type_ = PropertyValueType(AttributeName(), from_string); |
| to_property_ = CreatePropertyForAnimation(by_string); |
| to_property_value_type_ = PropertyValueType(AttributeName(), by_string); |
| to_property_->Add(from_property_, targetElement()); |
| return true; |
| } |
| |
| void SVGAnimateElement::ResetAnimatedType() { |
| ResolveTargetProperty(); |
| |
| SVGElement* target_element = this->targetElement(); |
| const QualifiedName& attribute_name = this->AttributeName(); |
| |
| if (!ShouldApplyAnimation(*target_element, attribute_name)) |
| return; |
| if (IsAnimatingSVGDom()) { |
| // SVG DOM animVal animation code-path. |
| animated_value_ = target_property_->CreateAnimatedValue(); |
| DCHECK_EQ(animated_value_->GetType(), type_); |
| target_element->SetAnimatedAttribute(attribute_name, animated_value_); |
| return; |
| } |
| DCHECK(IsAnimatingCSSProperty()); |
| // Presentation attributes which has an SVG DOM representation should use the |
| // "SVG DOM" code-path (above.) |
| DCHECK(SVGElement::IsAnimatableCSSProperty(attribute_name)); |
| |
| // CSS properties animation code-path. |
| String base_value = ComputeCSSPropertyValue(target_element, css_property_id_); |
| animated_value_ = CreatePropertyForAnimation(base_value); |
| } |
| |
| void SVGAnimateElement::ClearAnimatedType() { |
| if (!animated_value_) |
| return; |
| |
| // The animated property lock is held for the "result animation" (see |
| // SMILTimeContainer::updateAnimations()) while we're processing an animation |
| // group. We will very likely crash later if we clear the animated type while |
| // the lock is held. See crbug.com/581546. |
| DCHECK(!AnimatedTypeIsLocked()); |
| |
| SVGElement* target_element = this->targetElement(); |
| if (!target_element) { |
| animated_value_.Clear(); |
| return; |
| } |
| |
| bool should_apply = ShouldApplyAnimation(*target_element, AttributeName()); |
| if (IsAnimatingCSSProperty()) { |
| // CSS properties animation code-path. |
| if (should_apply) { |
| MutableCSSPropertyValueSet* property_set = |
| target_element->EnsureAnimatedSMILStyleProperties(); |
| if (property_set->RemoveProperty(css_property_id_)) { |
| target_element->SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::Create(StyleChangeReason::kAnimation)); |
| } |
| } |
| } |
| if (IsAnimatingSVGDom()) { |
| // SVG DOM animVal animation code-path. |
| target_element->ClearAnimatedAttribute(AttributeName()); |
| if (should_apply) |
| target_element->InvalidateAnimatedAttribute(AttributeName()); |
| } |
| |
| animated_value_.Clear(); |
| ClearTargetProperty(); |
| } |
| |
| void SVGAnimateElement::ApplyResultsToTarget() { |
| DCHECK_NE(GetAnimatedPropertyType(), kAnimatedUnknown); |
| |
| // Early exit if our animated type got destructed by a previous |
| // endedActiveInterval(). |
| if (!animated_value_) |
| return; |
| |
| if (!ShouldApplyAnimation(*targetElement(), AttributeName())) |
| return; |
| |
| // We do update the style and the animation property independent of each |
| // other. |
| if (IsAnimatingCSSProperty()) { |
| // CSS properties animation code-path. |
| // Convert the result of the animation to a String and apply it as CSS |
| // property on the target. |
| MutableCSSPropertyValueSet* property_set = |
| targetElement()->EnsureAnimatedSMILStyleProperties(); |
| if (property_set |
| ->SetProperty( |
| css_property_id_, animated_value_->ValueAsString(), false, |
| targetElement()->GetDocument().SecureContextMode(), nullptr) |
| .did_change) { |
| targetElement()->SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::Create(StyleChangeReason::kAnimation)); |
| } |
| } |
| if (IsAnimatingSVGDom()) { |
| // 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. |
| targetElement()->InvalidateAnimatedAttribute(AttributeName()); |
| } |
| } |
| |
| bool SVGAnimateElement::AnimatedPropertyTypeSupportsAddition() { |
| // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties. |
| switch (GetAnimatedPropertyType()) { |
| case kAnimatedBoolean: |
| case kAnimatedEnumeration: |
| case kAnimatedPreserveAspectRatio: |
| case kAnimatedString: |
| case kAnimatedUnknown: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| bool SVGAnimateElement::IsAdditive() { |
| if (GetAnimationMode() == kByAnimation || |
| GetAnimationMode() == kFromByAnimation) { |
| if (!AnimatedPropertyTypeSupportsAddition()) |
| return false; |
| } |
| |
| return SVGAnimationElement::IsAdditive(); |
| } |
| |
| float SVGAnimateElement::CalculateDistance(const String& from_string, |
| const String& to_string) { |
| DCHECK(targetElement()); |
| // FIXME: A return value of float is not enough to support paced animations on |
| // lists. |
| SVGPropertyBase* from_value = CreatePropertyForAnimation(from_string); |
| SVGPropertyBase* to_value = CreatePropertyForAnimation(to_string); |
| return from_value->CalculateDistance(to_value, targetElement()); |
| } |
| |
| void SVGAnimateElement::WillChangeAnimationTarget() { |
| SVGAnimationElement::WillChangeAnimationTarget(); |
| if (targetElement()) |
| ClearAnimatedType(); |
| } |
| |
| void SVGAnimateElement::DidChangeAnimationTarget() { |
| SVGAnimationElement::DidChangeAnimationTarget(); |
| ResetAnimatedPropertyType(); |
| } |
| |
| void SVGAnimateElement::SetAttributeName(const QualifiedName& attribute_name) { |
| WillChangeAnimationTarget(); |
| attribute_name_ = attribute_name; |
| DidChangeAnimationTarget(); |
| } |
| |
| void SVGAnimateElement::SetAttributeType(const AtomicString& attribute_type) { |
| WillChangeAnimationTarget(); |
| if (attribute_type == "CSS") |
| attribute_type_ = kAttributeTypeCSS; |
| else if (attribute_type == "XML") |
| attribute_type_ = kAttributeTypeXML; |
| else |
| attribute_type_ = kAttributeTypeAuto; |
| DidChangeAnimationTarget(); |
| } |
| |
| void SVGAnimateElement::ResetAnimatedPropertyType() { |
| DCHECK(!animated_value_); |
| InvalidatedValuesCache(); |
| from_property_.Clear(); |
| to_property_.Clear(); |
| to_at_end_of_duration_property_.Clear(); |
| ClearTargetProperty(); |
| } |
| |
| void SVGAnimateElement::Trace(blink::Visitor* visitor) { |
| visitor->Trace(from_property_); |
| visitor->Trace(to_property_); |
| visitor->Trace(to_at_end_of_duration_property_); |
| visitor->Trace(animated_value_); |
| visitor->Trace(target_property_); |
| SVGAnimationElement::Trace(visitor); |
| } |
| |
| } // namespace blink |