Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / svg / SVGAnimationElement.cpp
blob4f939171bfabebcabf7c453100c87eb0753ab734
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 //----------------------------------------------------------------------
34 // Implementation
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());
48 return NS_OK;
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,
80 aLocalName));
83 SMILTimedElement& SVGAnimationElement::TimedElement() { return mTimedElement; }
85 SVGElement* SVGAnimationElement::GetTargetElement() {
86 FlushAnimations();
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) {
93 FlushAnimations();
95 SMILTimeValue startTime = mTimedElement.GetStartTime();
96 if (!startTime.IsDefinite()) {
97 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
98 return 0.f;
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();
108 if (root) {
109 return float(double(root->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC);
112 return 0.0f;
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);
121 return 0.f;
124 return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC);
127 //----------------------------------------------------------------------
128 // nsIContent methods
130 nsresult SVGAnimationElement::BindToTree(BindContext& aContext,
131 nsINode& aParent) {
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);
146 if (href) {
147 nsAutoString hrefStr;
148 href->ToString(hrefStr);
150 UpdateHrefTarget(hrefStr);
153 mTimedElement.BindToTree(*this);
156 mTimedElement.SetIsDisabled(IsDisabled());
157 AnimationNeedsResample();
159 return NS_OK;
162 void SVGAnimationElement::UnbindFromTree(UnbindContext& aContext) {
163 SMILAnimationController* controller = OwnerDoc()->GetAnimationController();
164 if (controller) {
165 controller->UnregisterAnimationElement(this);
168 mHrefTarget.Unlink();
169 mTimedElement.DissolveReferences();
171 AnimationNeedsResample();
173 SVGAnimationElementBase::UnbindFromTree(aContext);
176 bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID,
177 nsAtom* aAttribute,
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();
186 return true;
189 nsresult rv = NS_ERROR_FAILURE;
191 // First let the animation function try to parse it...
192 bool foundMatch =
193 AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv);
195 // ... and if that didn't recognize the attribute, let the timed element
196 // try to parse it.
197 if (!foundMatch) {
198 foundMatch =
199 mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv);
202 if (foundMatch) {
203 AnimationNeedsResample();
204 if (NS_FAILED(rv)) {
205 ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue);
206 return false;
208 return true;
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,
220 bool aNotify) {
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()) {
239 return;
242 if (!((aNamespaceID == kNameSpaceID_None ||
243 aNamespaceID == kNameSpaceID_XLink) &&
244 aName == nsGkAtoms::href)) {
245 return;
248 if (!aValue) {
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);
257 if (xlinkHref) {
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()) {
278 return true;
280 nsIContent* child = this;
281 while (nsIContent* parent = child->GetFlattenedTreeParent()) {
282 if (!parent->IsSVGElement()) {
283 return false;
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()) {
291 return true;
293 } else {
294 if (child != SVGTests::FindActiveSwitchChild(svgSwitch)) {
295 return true;
298 } else if (auto* svgGraphics = SVGGraphicsElement::FromNode(parent)) {
299 if (!svgGraphics->PassesConditionalProcessingTests()) {
300 return true;
303 child = parent;
305 return false;
308 //----------------------------------------------------------------------
309 // SVG utility methods
311 void SVGAnimationElement::ActivateByHyperlink() {
312 FlushAnimations();
314 // The behavior for when the target is an animation element is defined in
315 // SMIL Animation:
316 // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics
317 SMILTimeValue seekTime = mTimedElement.GetHyperlinkTime();
318 if (seekTime.IsDefinite()) {
319 SMILTimeContainer* timeContainer = GetTimeContainer();
320 if (timeContainer) {
321 timeContainer->SetCurrentTime(seekTime.GetMillis());
322 AnimationNeedsResample();
323 // As with SVGSVGElement::SetCurrentTime, we need to trigger
324 // a synchronous sample now.
325 FlushAnimations();
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
329 } else {
330 BeginElement(IgnoreErrors());
334 //----------------------------------------------------------------------
335 // Implementation helpers
337 SMILTimeContainer* SVGAnimationElement::GetTimeContainer() {
338 SVGSVGElement* element = SVGContentUtils::GetOuterSVGElement(this);
340 if (element) {
341 return element->GetTimedDocumentRoot();
344 return nullptr;
347 void SVGAnimationElement::BeginElementAt(float offset, ErrorResult& rv) {
348 // Make sure the timegraph is up-to-date
349 FlushAnimations();
351 // This will fail if we're not attached to a time container (SVG document
352 // fragment).
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.
359 FlushAnimations();
362 void SVGAnimationElement::EndElementAt(float offset, ErrorResult& rv) {
363 // Make sure the timegraph is up-to-date
364 FlushAnimations();
366 rv = mTimedElement.EndElementAt(offset);
367 if (rv.Failed()) return;
369 AnimationNeedsResample();
370 // Force synchronous sample
371 FlushAnimations();
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);
381 } else {
382 mHrefTarget.Unlink();
384 AnimationTargetChanged();
387 void SVGAnimationElement::AnimationTargetChanged() {
388 mTimedElement.HandleTargetElementChange(GetTargetElementContent());
389 AnimationNeedsResample();
392 } // namespace mozilla::dom