1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/SVGAnimationElement.h"
8 #include "mozilla/dom/SVGSVGElement.h"
9 #include "mozilla/dom/SVGSwitchElement.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/dom/ElementInlines.h"
12 #include "mozilla/SMILAnimationController.h"
13 #include "mozilla/SMILAnimationFunction.h"
14 #include "mozilla/SMILTimeContainer.h"
15 #include "nsContentUtils.h"
16 #include "nsIContentInlines.h"
18 namespace mozilla::dom
{
20 //----------------------------------------------------------------------
21 // nsISupports methods
23 NS_IMPL_ADDREF_INHERITED(SVGAnimationElement
, SVGAnimationElementBase
)
24 NS_IMPL_RELEASE_INHERITED(SVGAnimationElement
, SVGAnimationElementBase
)
26 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement
)
27 NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests
)
28 NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase
)
30 NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement
, SVGAnimationElementBase
,
31 mHrefTarget
, mTimedElement
)
33 //----------------------------------------------------------------------
36 SVGAnimationElement::SVGAnimationElement(
37 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
38 : SVGAnimationElementBase(std::move(aNodeInfo
)), mHrefTarget(this) {}
40 nsresult
SVGAnimationElement::Init() {
41 nsresult rv
= SVGAnimationElementBase::Init();
42 NS_ENSURE_SUCCESS(rv
, rv
);
44 mTimedElement
.SetAnimationElement(this);
45 AnimationFunction().SetAnimationElement(this);
46 mTimedElement
.SetTimeClient(&AnimationFunction());
51 //----------------------------------------------------------------------
53 Element
* SVGAnimationElement::GetTargetElementContent() {
54 if (HasAttr(kNameSpaceID_XLink
, nsGkAtoms::href
) ||
55 HasAttr(nsGkAtoms::href
)) {
56 return mHrefTarget
.get();
58 MOZ_ASSERT(!mHrefTarget
.get(),
59 "We shouldn't have a href target "
60 "if we don't have an xlink:href or href attribute");
62 // No "href" or "xlink:href" attribute --> I should target my parent.
64 // Note that we want to use GetParentElement instead of the flattened tree to
65 // allow <use><animate>, for example.
66 return GetParentElement();
69 bool SVGAnimationElement::GetTargetAttributeName(int32_t* aNamespaceID
,
70 nsAtom
** aLocalName
) const {
71 const nsAttrValue
* nameAttr
= mAttrs
.GetAttr(nsGkAtoms::attributeName
);
73 if (!nameAttr
) return false;
75 NS_ASSERTION(nameAttr
->Type() == nsAttrValue::eAtom
,
76 "attributeName should have been parsed as an atom");
78 return NS_SUCCEEDED(nsContentUtils::SplitQName(
79 this, nsDependentAtomString(nameAttr
->GetAtomValue()), aNamespaceID
,
83 SMILTimedElement
& SVGAnimationElement::TimedElement() { return mTimedElement
; }
85 SVGElement
* SVGAnimationElement::GetTargetElement() {
88 // We'll just call the other GetTargetElement method, and QI to the right type
89 return SVGElement::FromNodeOrNull(GetTargetElementContent());
92 float SVGAnimationElement::GetStartTime(ErrorResult
& rv
) {
95 SMILTimeValue startTime
= mTimedElement
.GetStartTime();
96 if (!startTime
.IsDefinite()) {
97 rv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
101 return float(double(startTime
.GetMillis()) / PR_MSEC_PER_SEC
);
104 float SVGAnimationElement::GetCurrentTimeAsFloat() {
105 // Not necessary to call FlushAnimations() for this
107 SMILTimeContainer
* root
= GetTimeContainer();
109 return float(double(root
->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC
);
115 float SVGAnimationElement::GetSimpleDuration(ErrorResult
& rv
) {
116 // Not necessary to call FlushAnimations() for this
118 SMILTimeValue simpleDur
= mTimedElement
.GetSimpleDuration();
119 if (!simpleDur
.IsDefinite()) {
120 rv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
124 return float(double(simpleDur
.GetMillis()) / PR_MSEC_PER_SEC
);
127 //----------------------------------------------------------------------
128 // nsIContent methods
130 nsresult
SVGAnimationElement::BindToTree(BindContext
& aContext
,
132 MOZ_ASSERT(!mHrefTarget
.get(),
133 "Shouldn't have href-target yet (or it should've been cleared)");
134 nsresult rv
= SVGAnimationElementBase::BindToTree(aContext
, aParent
);
135 NS_ENSURE_SUCCESS(rv
, rv
);
137 // Add myself to the animation controller's master set of animation elements.
138 if (Document
* doc
= aContext
.GetComposedDoc()) {
139 if (SMILAnimationController
* controller
= doc
->GetAnimationController()) {
140 controller
->RegisterAnimationElement(this);
142 const nsAttrValue
* href
=
143 HasAttr(nsGkAtoms::href
)
144 ? mAttrs
.GetAttr(nsGkAtoms::href
, kNameSpaceID_None
)
145 : mAttrs
.GetAttr(nsGkAtoms::href
, kNameSpaceID_XLink
);
147 nsAutoString hrefStr
;
148 href
->ToString(hrefStr
);
150 UpdateHrefTarget(hrefStr
);
153 mTimedElement
.BindToTree(*this);
156 mTimedElement
.SetIsDisabled(IsDisabled());
157 AnimationNeedsResample();
162 void SVGAnimationElement::UnbindFromTree(UnbindContext
& aContext
) {
163 SMILAnimationController
* controller
= OwnerDoc()->GetAnimationController();
165 controller
->UnregisterAnimationElement(this);
168 mHrefTarget
.Unlink();
169 mTimedElement
.DissolveReferences();
171 AnimationNeedsResample();
173 SVGAnimationElementBase::UnbindFromTree(aContext
);
176 bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID
,
178 const nsAString
& aValue
,
179 nsIPrincipal
* aMaybeScriptedPrincipal
,
180 nsAttrValue
& aResult
) {
181 if (aNamespaceID
== kNameSpaceID_None
) {
182 // Deal with target-related attributes here
183 if (aAttribute
== nsGkAtoms::attributeName
) {
184 aResult
.ParseAtom(aValue
);
185 AnimationNeedsResample();
189 nsresult rv
= NS_ERROR_FAILURE
;
191 // First let the animation function try to parse it...
193 AnimationFunction().SetAttr(aAttribute
, aValue
, aResult
, &rv
);
195 // ... and if that didn't recognize the attribute, let the timed element
199 mTimedElement
.SetAttr(aAttribute
, aValue
, aResult
, *this, &rv
);
203 AnimationNeedsResample();
205 ReportAttributeParseFailure(OwnerDoc(), aAttribute
, aValue
);
212 return SVGAnimationElementBase::ParseAttribute(
213 aNamespaceID
, aAttribute
, aValue
, aMaybeScriptedPrincipal
, aResult
);
216 void SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
217 const nsAttrValue
* aValue
,
218 const nsAttrValue
* aOldValue
,
219 nsIPrincipal
* aSubjectPrincipal
,
221 if (!aValue
&& aNamespaceID
== kNameSpaceID_None
) {
222 // Attribute is being removed.
223 if (AnimationFunction().UnsetAttr(aName
) ||
224 mTimedElement
.UnsetAttr(aName
)) {
225 AnimationNeedsResample();
229 SVGAnimationElementBase::AfterSetAttr(aNamespaceID
, aName
, aValue
, aOldValue
,
230 aSubjectPrincipal
, aNotify
);
232 if (SVGTests::IsConditionalProcessingAttribute(aName
)) {
233 if (mTimedElement
.SetIsDisabled(IsDisabled())) {
234 AnimationNeedsResample();
238 if (!IsInComposedDoc()) {
242 if (!((aNamespaceID
== kNameSpaceID_None
||
243 aNamespaceID
== kNameSpaceID_XLink
) &&
244 aName
== nsGkAtoms::href
)) {
249 if (aNamespaceID
== kNameSpaceID_None
) {
250 mHrefTarget
.Unlink();
251 AnimationTargetChanged();
253 // After unsetting href, we may still have xlink:href, so we
254 // should try to add it back.
255 const nsAttrValue
* xlinkHref
=
256 mAttrs
.GetAttr(nsGkAtoms::href
, kNameSpaceID_XLink
);
258 UpdateHrefTarget(xlinkHref
->GetStringValue());
260 } else if (!HasAttr(nsGkAtoms::href
)) {
261 mHrefTarget
.Unlink();
262 AnimationTargetChanged();
263 } // else: we unset xlink:href, but we still have href attribute, so keep
264 // mHrefTarget linking to href.
265 } else if (!(aNamespaceID
== kNameSpaceID_XLink
&&
266 HasAttr(nsGkAtoms::href
))) {
267 // Note: "href" takes priority over xlink:href. So if "xlink:href" is being
268 // set here, we only let that update our target if "href" is *unset*.
269 MOZ_ASSERT(aValue
->Type() == nsAttrValue::eString
,
270 "Expected href attribute to be string type");
271 UpdateHrefTarget(aValue
->GetStringValue());
272 } // else: we're not yet in a document -- we'll update the target on
273 // next BindToTree call.
276 bool SVGAnimationElement::IsDisabled() {
277 if (!SVGTests::PassesConditionalProcessingTests()) {
280 nsIContent
* child
= this;
281 while (nsIContent
* parent
= child
->GetFlattenedTreeParent()) {
282 if (!parent
->IsSVGElement()) {
285 if (auto* svgSwitch
= SVGSwitchElement::FromNodeOrNull(parent
)) {
286 nsIFrame
* frame
= svgSwitch
->GetPrimaryFrame();
287 // If we've been reflowed then the active child has been determined,
288 // otherwise we'll have to calculate whether this is the active child.
289 if (frame
&& !frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
290 if (child
!= svgSwitch
->GetActiveChild()) {
294 if (child
!= SVGTests::FindActiveSwitchChild(svgSwitch
)) {
298 } else if (auto* svgGraphics
= SVGGraphicsElement::FromNode(parent
)) {
299 if (!svgGraphics
->PassesConditionalProcessingTests()) {
308 //----------------------------------------------------------------------
309 // SVG utility methods
311 void SVGAnimationElement::ActivateByHyperlink() {
314 // The behavior for when the target is an animation element is defined in
316 // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics
317 SMILTimeValue seekTime
= mTimedElement
.GetHyperlinkTime();
318 if (seekTime
.IsDefinite()) {
319 SMILTimeContainer
* timeContainer
= GetTimeContainer();
321 timeContainer
->SetCurrentTime(seekTime
.GetMillis());
322 AnimationNeedsResample();
323 // As with SVGSVGElement::SetCurrentTime, we need to trigger
324 // a synchronous sample now.
327 // else, silently fail. We mustn't be part of an SVG document fragment that
328 // is attached to the document tree so there's nothing we can do here
330 BeginElement(IgnoreErrors());
334 //----------------------------------------------------------------------
335 // Implementation helpers
337 SMILTimeContainer
* SVGAnimationElement::GetTimeContainer() {
338 SVGSVGElement
* element
= SVGContentUtils::GetOuterSVGElement(this);
341 return element
->GetTimedDocumentRoot();
347 void SVGAnimationElement::BeginElementAt(float offset
, ErrorResult
& rv
) {
348 // Make sure the timegraph is up-to-date
351 // This will fail if we're not attached to a time container (SVG document
353 rv
= mTimedElement
.BeginElementAt(offset
);
354 if (rv
.Failed()) return;
356 AnimationNeedsResample();
357 // Force synchronous sample so that events resulting from this call arrive in
358 // the expected order and we get an up-to-date paint.
362 void SVGAnimationElement::EndElementAt(float offset
, ErrorResult
& rv
) {
363 // Make sure the timegraph is up-to-date
366 rv
= mTimedElement
.EndElementAt(offset
);
367 if (rv
.Failed()) return;
369 AnimationNeedsResample();
370 // Force synchronous sample
374 bool SVGAnimationElement::IsEventAttributeNameInternal(nsAtom
* aName
) {
375 return nsContentUtils::IsEventAttributeName(aName
, EventNameType_SMIL
);
378 void SVGAnimationElement::UpdateHrefTarget(const nsAString
& aHrefStr
) {
379 if (nsContentUtils::IsLocalRefURL(aHrefStr
)) {
380 mHrefTarget
.ResetToLocalFragmentID(*this, aHrefStr
);
382 mHrefTarget
.Unlink();
384 AnimationTargetChanged();
387 void SVGAnimationElement::AnimationTargetChanged() {
388 mTimedElement
.HandleTargetElementChange(GetTargetElementContent());
389 AnimationNeedsResample();
392 } // namespace mozilla::dom