SVGAnimationElement.cpp (13100B)
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/. */ 6 7 #include "mozilla/dom/SVGAnimationElement.h" 8 9 #include "mozilla/SMILAnimationController.h" 10 #include "mozilla/SMILAnimationFunction.h" 11 #include "mozilla/SMILTimeContainer.h" 12 #include "mozilla/dom/BindContext.h" 13 #include "mozilla/dom/ElementInlines.h" 14 #include "mozilla/dom/SVGSVGElement.h" 15 #include "mozilla/dom/SVGSwitchElement.h" 16 #include "nsAttrValueOrString.h" 17 #include "nsContentUtils.h" 18 #include "nsIContentInlines.h" 19 20 namespace mozilla::dom { 21 22 //---------------------------------------------------------------------- 23 // nsISupports methods 24 25 NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase) 26 NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase) 27 28 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement) 29 NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests) 30 NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase) 31 32 NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement, SVGAnimationElementBase, 33 mHrefTarget, mTimedElement) 34 35 //---------------------------------------------------------------------- 36 // Implementation 37 38 SVGAnimationElement::SVGAnimationElement( 39 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 40 : SVGAnimationElementBase(std::move(aNodeInfo)), mHrefTarget(this) {} 41 42 nsresult SVGAnimationElement::Init() { 43 nsresult rv = SVGAnimationElementBase::Init(); 44 NS_ENSURE_SUCCESS(rv, rv); 45 46 mTimedElement.SetAnimationElement(this); 47 AnimationFunction().SetAnimationElement(this); 48 mTimedElement.SetTimeClient(&AnimationFunction()); 49 50 return NS_OK; 51 } 52 53 //---------------------------------------------------------------------- 54 55 Element* SVGAnimationElement::GetTargetElementContent() { 56 if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) || 57 HasAttr(nsGkAtoms::href)) { 58 return mHrefTarget.get(); 59 } 60 MOZ_ASSERT(!mHrefTarget.get(), 61 "We shouldn't have a href target " 62 "if we don't have an xlink:href or href attribute"); 63 64 // No "href" or "xlink:href" attribute --> I should target my parent. 65 // 66 // Note that we want to use GetParentElement instead of the flattened tree to 67 // allow <use><animate>, for example. 68 return GetParentElement(); 69 } 70 71 bool SVGAnimationElement::GetTargetAttributeName(int32_t* aNamespaceID, 72 nsAtom** aLocalName) const { 73 const nsAttrValue* nameAttr = mAttrs.GetAttr(nsGkAtoms::attributeName); 74 75 if (!nameAttr) return false; 76 77 NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom, 78 "attributeName should have been parsed as an atom"); 79 80 return NS_SUCCEEDED(nsContentUtils::SplitQName( 81 this, nsDependentAtomString(nameAttr->GetAtomValue()), aNamespaceID, 82 aLocalName)); 83 } 84 85 SMILTimedElement& SVGAnimationElement::TimedElement() { return mTimedElement; } 86 87 SVGElement* SVGAnimationElement::GetTargetElement() { 88 FlushAnimations(); 89 90 // We'll just call the other GetTargetElement method, and QI to the right type 91 return SVGElement::FromNodeOrNull(GetTargetElementContent()); 92 } 93 94 float SVGAnimationElement::GetStartTime(ErrorResult& aRv) { 95 FlushAnimations(); 96 97 SMILTimeValue startTime = mTimedElement.GetStartTime(); 98 if (!startTime.IsDefinite()) { 99 aRv.ThrowInvalidStateError("Indefinite start time"); 100 return 0.f; 101 } 102 103 return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC); 104 } 105 106 float SVGAnimationElement::GetCurrentTimeAsFloat() { 107 // Not necessary to call FlushAnimations() for this 108 109 SMILTimeContainer* root = GetTimeContainer(); 110 if (root) { 111 return float(double(root->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC); 112 } 113 114 return 0.0f; 115 } 116 117 float SVGAnimationElement::GetSimpleDuration(ErrorResult& aRv) { 118 // Not necessary to call FlushAnimations() for this 119 120 SMILTimeValue simpleDur = mTimedElement.GetSimpleDuration(); 121 if (!simpleDur.IsDefinite()) { 122 aRv.ThrowNotSupportedError("Duration is indefinite"); 123 return 0.f; 124 } 125 126 return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC); 127 } 128 129 //---------------------------------------------------------------------- 130 // nsIContent methods 131 132 nsresult SVGAnimationElement::BindToTree(BindContext& aContext, 133 nsINode& aParent) { 134 MOZ_ASSERT(!mHrefTarget.get(), 135 "Shouldn't have href-target yet (or it should've been cleared)"); 136 nsresult rv = SVGAnimationElementBase::BindToTree(aContext, aParent); 137 NS_ENSURE_SUCCESS(rv, rv); 138 139 // Add myself to the animation controller's master set of animation elements. 140 if (Document* doc = aContext.GetComposedDoc()) { 141 if (SMILAnimationController* controller = doc->GetAnimationController()) { 142 controller->RegisterAnimationElement(this); 143 } 144 const nsAttrValue* href = 145 HasAttr(nsGkAtoms::href) 146 ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) 147 : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); 148 if (href) { 149 nsAutoString hrefStr; 150 href->ToString(hrefStr); 151 152 UpdateHrefTarget(hrefStr); 153 } 154 155 mTimedElement.BindToTree(*this); 156 } 157 158 mTimedElement.SetIsDisabled(IsDisabled()); 159 AnimationNeedsResample(); 160 161 return NS_OK; 162 } 163 164 void SVGAnimationElement::UnbindFromTree(UnbindContext& aContext) { 165 SMILAnimationController* controller = OwnerDoc()->GetAnimationController(); 166 if (controller) { 167 controller->UnregisterAnimationElement(this); 168 } 169 170 mHrefTarget.Unlink(); 171 mTimedElement.DissolveReferences(); 172 173 AnimationNeedsResample(); 174 175 SVGAnimationElementBase::UnbindFromTree(aContext); 176 } 177 178 bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID, 179 nsAtom* aAttribute, 180 const nsAString& aValue, 181 nsIPrincipal* aMaybeScriptedPrincipal, 182 nsAttrValue& aResult) { 183 if (aNamespaceID == kNameSpaceID_None) { 184 // Deal with target-related attributes here 185 if (aAttribute == nsGkAtoms::attributeName) { 186 aResult.ParseAtom(aValue); 187 AnimationNeedsResample(); 188 return true; 189 } 190 191 nsresult rv = NS_ERROR_FAILURE; 192 193 // First let the animation function try to parse it... 194 bool foundMatch = 195 AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv); 196 197 // ... and if that didn't recognize the attribute, let the timed element 198 // try to parse it. 199 if (!foundMatch) { 200 foundMatch = 201 mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv); 202 } 203 204 if (foundMatch) { 205 AnimationNeedsResample(); 206 if (NS_FAILED(rv)) { 207 ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); 208 return false; 209 } 210 return true; 211 } 212 } 213 214 return SVGAnimationElementBase::ParseAttribute( 215 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); 216 } 217 218 void SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, 219 const nsAttrValue* aValue, 220 const nsAttrValue* aOldValue, 221 nsIPrincipal* aSubjectPrincipal, 222 bool aNotify) { 223 if (!aValue && aNamespaceID == kNameSpaceID_None) { 224 // Attribute is being removed. 225 if (AnimationFunction().UnsetAttr(aName) || 226 mTimedElement.UnsetAttr(aName)) { 227 AnimationNeedsResample(); 228 } 229 } 230 231 SVGAnimationElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, 232 aSubjectPrincipal, aNotify); 233 234 if (SVGTests::IsConditionalProcessingAttribute(aName)) { 235 if (mTimedElement.SetIsDisabled(IsDisabled())) { 236 AnimationNeedsResample(); 237 } 238 } 239 240 if (!IsInComposedDoc()) { 241 return; 242 } 243 244 if (!((aNamespaceID == kNameSpaceID_None || 245 aNamespaceID == kNameSpaceID_XLink) && 246 aName == nsGkAtoms::href)) { 247 return; 248 } 249 250 if (!aValue) { 251 if (aNamespaceID == kNameSpaceID_None) { 252 mHrefTarget.Unlink(); 253 AnimationTargetChanged(); 254 255 // After unsetting href, we may still have xlink:href, so we 256 // should try to add it back. 257 const nsAttrValue* xlinkHref = 258 mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); 259 if (xlinkHref) { 260 UpdateHrefTarget(nsAttrValueOrString(xlinkHref).String()); 261 } 262 } else if (!HasAttr(nsGkAtoms::href)) { 263 mHrefTarget.Unlink(); 264 AnimationTargetChanged(); 265 } // else: we unset xlink:href, but we still have href attribute, so keep 266 // mHrefTarget linking to href. 267 } else if (!(aNamespaceID == kNameSpaceID_XLink && 268 HasAttr(nsGkAtoms::href))) { 269 // Note: "href" takes priority over xlink:href. So if "xlink:href" is being 270 // set here, we only let that update our target if "href" is *unset*. 271 MOZ_ASSERT(aValue->Type() == nsAttrValue::eString || 272 aValue->Type() == nsAttrValue::eAtom, 273 "Expected href attribute to be string or atom type"); 274 UpdateHrefTarget(nsAttrValueOrString(aValue).String()); 275 } // else: we're not yet in a document -- we'll update the target on 276 // next BindToTree call. 277 } 278 279 bool SVGAnimationElement::IsDisabled() { 280 if (!SVGTests::PassesConditionalProcessingTests()) { 281 return true; 282 } 283 nsIContent* child = this; 284 while (nsIContent* parent = child->GetFlattenedTreeParent()) { 285 if (!parent->IsSVGElement()) { 286 return false; 287 } 288 if (auto* svgSwitch = SVGSwitchElement::FromNodeOrNull(parent)) { 289 nsIFrame* frame = svgSwitch->GetPrimaryFrame(); 290 // If we've been reflowed then the active child has been determined, 291 // otherwise we'll have to calculate whether this is the active child. 292 if (frame && !frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 293 if (child != svgSwitch->GetActiveChild()) { 294 return true; 295 } 296 } else { 297 if (child != SVGTests::FindActiveSwitchChild(svgSwitch)) { 298 return true; 299 } 300 } 301 } else if (auto* svgGraphics = SVGGraphicsElement::FromNode(parent)) { 302 if (!svgGraphics->PassesConditionalProcessingTests()) { 303 return true; 304 } 305 } 306 child = parent; 307 } 308 return false; 309 } 310 311 //---------------------------------------------------------------------- 312 // SVG utility methods 313 314 void SVGAnimationElement::ActivateByHyperlink() { 315 FlushAnimations(); 316 317 // The behavior for when the target is an animation element is defined in 318 // SMIL Animation: 319 // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics 320 SMILTimeValue seekTime = mTimedElement.GetHyperlinkTime(); 321 if (seekTime.IsDefinite()) { 322 SMILTimeContainer* timeContainer = GetTimeContainer(); 323 if (timeContainer) { 324 timeContainer->SetCurrentTime(seekTime.GetMillis()); 325 AnimationNeedsResample(); 326 // As with SVGSVGElement::SetCurrentTime, we need to trigger 327 // a synchronous sample now. 328 FlushAnimations(); 329 } 330 // else, silently fail. We mustn't be part of an SVG document fragment that 331 // is attached to the document tree so there's nothing we can do here 332 } else { 333 BeginElement(IgnoreErrors()); 334 } 335 } 336 337 //---------------------------------------------------------------------- 338 // Implementation helpers 339 340 SMILTimeContainer* SVGAnimationElement::GetTimeContainer() { 341 SVGSVGElement* element = SVGContentUtils::GetOuterSVGElement(this); 342 343 if (element) { 344 return element->GetTimedDocumentRoot(); 345 } 346 347 return nullptr; 348 } 349 350 void SVGAnimationElement::BeginElementAt(float offset, ErrorResult& aRv) { 351 // Make sure the timegraph is up-to-date 352 FlushAnimations(); 353 354 // This will fail if we're not attached to a time container (SVG document 355 // fragment). 356 aRv = mTimedElement.BeginElementAt(offset); 357 if (aRv.Failed()) return; 358 359 AnimationNeedsResample(); 360 // Force synchronous sample so that events resulting from this call arrive in 361 // the expected order and we get an up-to-date paint. 362 FlushAnimations(); 363 } 364 365 void SVGAnimationElement::EndElementAt(float offset, ErrorResult& aRv) { 366 // Make sure the timegraph is up-to-date 367 FlushAnimations(); 368 369 aRv = mTimedElement.EndElementAt(offset); 370 if (aRv.Failed()) return; 371 372 AnimationNeedsResample(); 373 // Force synchronous sample 374 FlushAnimations(); 375 } 376 377 bool SVGAnimationElement::IsEventAttributeNameInternal(nsAtom* aName) { 378 return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL); 379 } 380 381 void SVGAnimationElement::UpdateHrefTarget(const nsAString& aHrefStr) { 382 if (nsContentUtils::IsLocalRefURL(aHrefStr)) { 383 mHrefTarget.ResetToLocalFragmentID(*this, aHrefStr); 384 } else { 385 mHrefTarget.Unlink(); 386 } 387 AnimationTargetChanged(); 388 } 389 390 void SVGAnimationElement::AnimationTargetChanged() { 391 mTimedElement.HandleTargetElementChange(GetTargetElementContent()); 392 AnimationNeedsResample(); 393 } 394 395 } // namespace mozilla::dom