SVGUseElement.cpp (22264B)
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/SVGUseElement.h" 8 9 #include "SVGGeometryProperty.h" 10 #include "mozilla/ErrorResult.h" 11 #include "mozilla/SVGObserverUtils.h" 12 #include "mozilla/SVGUseFrame.h" 13 #include "mozilla/ScopeExit.h" 14 #include "mozilla/StaticPrefs_svg.h" 15 #include "mozilla/URLExtraData.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/ReferrerInfo.h" 18 #include "mozilla/dom/SVGGraphicsElement.h" 19 #include "mozilla/dom/SVGLengthBinding.h" 20 #include "mozilla/dom/SVGSVGElement.h" 21 #include "mozilla/dom/SVGSwitchElement.h" 22 #include "mozilla/dom/SVGSymbolElement.h" 23 #include "mozilla/dom/SVGUseElementBinding.h" 24 #include "mozilla/dom/ShadowIncludingTreeIterator.h" 25 #include "nsContentUtils.h" 26 #include "nsGkAtoms.h" 27 #include "nsIReferrerInfo.h" 28 #include "nsIURI.h" 29 30 NS_IMPL_NS_NEW_SVG_ELEMENT(Use) 31 32 namespace mozilla::dom { 33 34 JSObject* SVGUseElement::WrapNode(JSContext* aCx, 35 JS::Handle<JSObject*> aGivenProto) { 36 return SVGUseElement_Binding::Wrap(aCx, this, aGivenProto); 37 } 38 39 //////////////////////////////////////////////////////////////////////// 40 // implementation 41 42 SVGElement::LengthInfo SVGUseElement::sLengthInfo[4] = { 43 {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 44 SVGContentUtils::X}, 45 {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 46 SVGContentUtils::Y}, 47 {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 48 SVGContentUtils::X}, 49 {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 50 SVGContentUtils::Y}, 51 }; 52 53 SVGElement::StringInfo SVGUseElement::sStringInfo[2] = { 54 {nsGkAtoms::href, kNameSpaceID_None, true}, 55 {nsGkAtoms::href, kNameSpaceID_XLink, true}}; 56 57 //---------------------------------------------------------------------- 58 // nsISupports methods 59 60 NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement) 61 62 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement, 63 SVGUseElementBase) 64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal) 65 tmp->UnlinkSource(); 66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement, 68 SVGUseElementBase) 69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal) 70 tmp->mReferencedElementTracker.Traverse(&cb); 71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 72 73 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGUseElement, SVGUseElementBase, 74 nsIMutationObserver) 75 76 //---------------------------------------------------------------------- 77 // Implementation 78 79 SVGUseElement::SVGUseElement( 80 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 81 : SVGUseElementBase(std::move(aNodeInfo)), mReferencedElementTracker(this) { 82 SetEnabledCallbacks(kCharacterDataChanged | kAttributeChanged | 83 kContentAppended | kContentInserted | 84 kContentWillBeRemoved | kNodeWillBeDestroyed); 85 } 86 87 SVGUseElement::~SVGUseElement() { 88 UnlinkSource(); 89 MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->SVGUseElementNeedsShadowTreeUpdate(*this), 90 "Dying without unbinding?"); 91 } 92 93 namespace SVGT = SVGGeometryProperty::Tags; 94 95 //---------------------------------------------------------------------- 96 // nsINode methods 97 98 void SVGUseElement::ProcessAttributeChange(int32_t aNamespaceID, 99 nsAtom* aAttribute) { 100 if (OwnerDoc()->CloningForSVGUse()) { 101 return; 102 } 103 if (aNamespaceID == kNameSpaceID_None) { 104 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { 105 const bool hadValidDimensions = HasValidDimensions(); 106 const bool isUsed = OurWidthAndHeightAreUsed(); 107 if (isUsed) { 108 SyncWidthOrHeight(aAttribute); 109 } 110 111 if (auto* frame = GetFrame()) { 112 frame->DimensionAttributeChanged(hadValidDimensions, isUsed); 113 } 114 } 115 } 116 117 if ((aNamespaceID == kNameSpaceID_XLink || 118 aNamespaceID == kNameSpaceID_None) && 119 aAttribute == nsGkAtoms::href) { 120 // We're changing our nature, clear out the clone information. 121 if (auto* frame = GetFrame()) { 122 frame->HrefChanged(); 123 } 124 UnlinkSource(); 125 TriggerReclone(); 126 } 127 } 128 129 void SVGUseElement::DidAnimateAttribute(int32_t aNameSpaceID, 130 nsAtom* aAttribute) { 131 ProcessAttributeChange(aNameSpaceID, aAttribute); 132 } 133 134 void SVGUseElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aAttribute, 135 const nsAttrValue* aValue, 136 const nsAttrValue* aOldValue, 137 nsIPrincipal* aSubjectPrincipal, 138 bool aNotify) { 139 ProcessAttributeChange(aNamespaceID, aAttribute); 140 return SVGUseElementBase::AfterSetAttr(aNamespaceID, aAttribute, aValue, 141 aOldValue, aSubjectPrincipal, aNotify); 142 } 143 144 nsresult SVGUseElement::Clone(dom::NodeInfo* aNodeInfo, 145 nsINode** aResult) const { 146 *aResult = nullptr; 147 SVGUseElement* it = 148 new (aNodeInfo->NodeInfoManager()) SVGUseElement(do_AddRef(aNodeInfo)); 149 150 nsCOMPtr<nsINode> kungFuDeathGrip(it); 151 nsresult rv1 = it->Init(); 152 nsresult rv2 = const_cast<SVGUseElement*>(this)->CopyInnerTo(it); 153 154 if (aNodeInfo->GetDocument()->CloningForSVGUse()) { 155 // SVGUseElement specific portion - record who we cloned from 156 it->mOriginal = const_cast<SVGUseElement*>(this); 157 } 158 159 if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { 160 kungFuDeathGrip.swap(*aResult); 161 } 162 163 return NS_FAILED(rv1) ? rv1 : rv2; 164 } 165 166 nsresult SVGUseElement::BindToTree(BindContext& aContext, nsINode& aParent) { 167 nsresult rv = SVGUseElementBase::BindToTree(aContext, aParent); 168 NS_ENSURE_SUCCESS(rv, rv); 169 170 TriggerReclone(); 171 return NS_OK; 172 } 173 174 void SVGUseElement::UnbindFromTree(UnbindContext& aContext) { 175 SVGUseElementBase::UnbindFromTree(aContext); 176 OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this); 177 } 178 179 already_AddRefed<DOMSVGAnimatedString> SVGUseElement::Href() { 180 return mStringAttributes[HREF].IsExplicitlySet() || 181 !mStringAttributes[XLINK_HREF].IsExplicitlySet() 182 ? mStringAttributes[HREF].ToDOMAnimatedString(this) 183 : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); 184 } 185 186 //---------------------------------------------------------------------- 187 188 already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::X() { 189 return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); 190 } 191 192 already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Y() { 193 return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); 194 } 195 196 already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Width() { 197 return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); 198 } 199 200 already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Height() { 201 return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); 202 } 203 204 //---------------------------------------------------------------------- 205 // nsIMutationObserver methods 206 207 void SVGUseElement::CharacterDataChanged(nsIContent* aContent, 208 const CharacterDataChangeInfo&) { 209 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), 210 aContent)) { 211 TriggerReclone(); 212 } 213 } 214 215 void SVGUseElement::AttributeChanged(Element* aElement, int32_t aNamespaceID, 216 nsAtom* aAttribute, AttrModType, 217 const nsAttrValue* aOldValue) { 218 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), 219 aElement)) { 220 TriggerReclone(); 221 } 222 } 223 224 void SVGUseElement::ContentAppended(nsIContent* aFirstNewContent, 225 const ContentAppendInfo&) { 226 // FIXME(emilio, bug 1442336): Why does this check the parent but 227 // ContentInserted the child? 228 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), 229 aFirstNewContent->GetParent())) { 230 TriggerReclone(); 231 } 232 } 233 234 void SVGUseElement::ContentInserted(nsIContent* aChild, 235 const ContentInsertInfo&) { 236 // FIXME(emilio, bug 1442336): Why does this check the child but 237 // ContentAppended the parent? 238 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), 239 aChild)) { 240 TriggerReclone(); 241 } 242 } 243 244 void SVGUseElement::ContentWillBeRemoved(nsIContent* aChild, 245 const ContentRemoveInfo&) { 246 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), 247 aChild)) { 248 TriggerReclone(); 249 } 250 } 251 252 void SVGUseElement::NodeWillBeDestroyed(nsINode* aNode) { 253 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); 254 UnlinkSource(); 255 } 256 257 // Returns whether this node could ever be displayed. 258 static bool NodeCouldBeRendered(const nsINode& aNode) { 259 if (const auto* symbol = SVGSymbolElement::FromNode(aNode)) { 260 return symbol->CouldBeRendered(); 261 } 262 if (auto* svgSwitch = 263 SVGSwitchElement::FromNodeOrNull(aNode.GetParentNode())) { 264 if (&aNode != svgSwitch->GetActiveChild()) { 265 return false; 266 } 267 } else if (const auto* svgGraphics = SVGGraphicsElement::FromNode(aNode)) { 268 if (!svgGraphics->PassesConditionalProcessingTests()) { 269 return false; 270 } 271 } 272 return true; 273 } 274 275 // <svg:use> can be used (no pun intended) to trivially cause an explosion of 276 // clones that could potentially DoS the browser. We have a configurable limit 277 // to control this. 278 static bool IsTooMuchRecursion(uint32_t aCount) { 279 switch (StaticPrefs::svg_use_element_recursive_clone_limit_enabled()) { 280 case 0: 281 return false; 282 case 1: 283 break; 284 default: 285 if (!XRE_IsParentProcess()) { 286 return false; 287 } 288 break; 289 } 290 return aCount >= StaticPrefs::svg_use_element_recursive_clone_limit(); 291 } 292 293 // Circular loop detection, plus detection of whether this shadow tree is 294 // rendered at all. 295 auto SVGUseElement::ScanAncestors(const Element& aTarget) const -> ScanResult { 296 uint32_t count = 0; 297 return ScanAncestorsInternal(aTarget, count); 298 } 299 300 auto SVGUseElement::ScanAncestorsInternal(const Element& aTarget, 301 uint32_t& aCount) const 302 -> ScanResult { 303 if (&aTarget == this) { 304 return ScanResult::CyclicReference; 305 } 306 if (mOriginal) { 307 if (IsTooMuchRecursion(++aCount)) { 308 return ScanResult::TooDeep; 309 } 310 auto result = mOriginal->ScanAncestorsInternal(aTarget, aCount); 311 switch (result) { 312 case ScanResult::TooDeep: 313 case ScanResult::CyclicReference: 314 return result; 315 case ScanResult::Ok: 316 case ScanResult::Invisible: 317 break; 318 } 319 } 320 321 auto result = ScanResult::Ok; 322 for (nsINode* parent = GetParentOrShadowHostNode(); parent; 323 parent = parent->GetParentOrShadowHostNode()) { 324 if (parent == &aTarget) { 325 return ScanResult::CyclicReference; 326 } 327 if (auto* use = SVGUseElement::FromNode(*parent)) { 328 if (IsTooMuchRecursion(++aCount)) { 329 return ScanResult::TooDeep; 330 } 331 if (mOriginal && use->mOriginal == mOriginal) { 332 return ScanResult::CyclicReference; 333 } 334 } 335 // Do we have other similar cases we can optimize out easily? 336 if (!NodeCouldBeRendered(*parent)) { 337 // NOTE(emilio): We can't just return here. If we're cyclic, we need to 338 // know. 339 result = ScanResult::Invisible; 340 } 341 } 342 return result; 343 } 344 345 //---------------------------------------------------------------------- 346 347 static bool IsForbiddenUseNode(const nsINode& aNode) { 348 if (!aNode.IsElement()) { 349 return false; 350 } 351 const auto* svg = SVGElement::FromNode(aNode); 352 return !svg || !svg->IsSVGGraphicsElement(); 353 } 354 355 static void CollectForbiddenNodes(Element& aRoot, 356 nsTArray<RefPtr<nsINode>>& aNodes) { 357 auto iter = dom::ShadowIncludingTreeIterator(aRoot); 358 while (iter) { 359 nsINode* node = *iter; 360 if (IsForbiddenUseNode(*node)) { 361 aNodes.AppendElement(node); 362 iter.SkipChildren(); 363 continue; 364 } 365 ++iter; 366 } 367 } 368 369 // SVG1 restricted <use> trees to SVGGraphicsElements. 370 // https://www.w3.org/TR/SVG11/struct.html#UseElement: 371 // 372 // Any ‘svg’, ‘symbol’, ‘g’, graphics element or other ‘use’ is potentially a 373 // template object that can be re-used (i.e., "instanced") in the SVG 374 // document via a ‘use’ element. The ‘use’ element references another element 375 // and indicates that the graphical contents of that element is 376 // included/drawn at that given point in the document. 377 // 378 // SVG2 doesn't have that same restriction. 379 // https://www.w3.org/TR/SVG2/struct.html#UseShadowTree: 380 // 381 // Previous versions of SVG restricted the contents of the shadow tree to SVG 382 // graphics elements. This specification allows any valid SVG document 383 // subtree to be cloned. Cloning non-graphical content, however, will not 384 // usually have any visible effect. 385 // 386 // But it's pretty ambiguous as to what the behavior should be for some 387 // elements, because <script> is inert, but <iframe> is not, see: 388 // https://github.com/w3c/svgwg/issues/876 389 // 390 // So, fairly confusing, all-in-all. 391 static void RemoveForbiddenNodes(Element& aRoot, bool aIsCrossDocument) { 392 switch (StaticPrefs::svg_use_element_graphics_element_restrictions()) { 393 case 0: 394 return; 395 case 1: 396 if (!aIsCrossDocument) { 397 return; 398 } 399 break; 400 default: 401 break; 402 } 403 404 AutoTArray<RefPtr<nsINode>, 10> unsafeNodes; 405 CollectForbiddenNodes(aRoot, unsafeNodes); 406 for (auto& unsafeNode : unsafeNodes) { 407 unsafeNode->Remove(); 408 } 409 } 410 411 void SVGUseElement::UpdateShadowTree() { 412 MOZ_ASSERT(IsInComposedDoc()); 413 414 if (mReferencedElementTracker.get()) { 415 mReferencedElementTracker.get()->RemoveMutationObserver(this); 416 } 417 418 LookupHref(); 419 420 RefPtr<ShadowRoot> shadow = GetShadowRoot(); 421 if (!shadow) { 422 shadow = AttachShadowWithoutNameChecks(ShadowRootMode::Closed); 423 } 424 MOZ_ASSERT(shadow); 425 426 auto* targetElement = 427 SVGGraphicsElement::FromNodeOrNull(mReferencedElementTracker.get()); 428 RefPtr<Element> newElement; 429 430 auto UpdateShadowTree = mozilla::MakeScopeExit([&]() { 431 if (nsIContent* firstChild = shadow->GetFirstChild()) { 432 MOZ_ASSERT(!firstChild->GetNextSibling()); 433 shadow->RemoveChildNode(firstChild, /* aNotify = */ true); 434 } 435 436 if (newElement) { 437 shadow->AppendChildTo(newElement, /* aNotify = */ true, IgnoreErrors()); 438 } 439 }); 440 441 // make sure target is valid type for <use> 442 if (!targetElement) { 443 return; 444 } 445 446 if (ScanAncestors(*targetElement) != ScanResult::Ok) { 447 return; 448 } 449 450 nsCOMPtr<nsIURI> baseURI = targetElement->GetBaseURI(); 451 if (!baseURI) { 452 return; 453 } 454 455 { 456 const bool isCrossDocument = targetElement->OwnerDoc() != OwnerDoc(); 457 458 nsNodeInfoManager* nodeInfoManager = 459 isCrossDocument ? OwnerDoc()->NodeInfoManager() : nullptr; 460 461 nsCOMPtr<nsINode> newNode = 462 targetElement->Clone(true, nodeInfoManager, IgnoreErrors()); 463 if (!newNode) { 464 return; 465 } 466 467 MOZ_ASSERT(newNode->IsElement()); 468 newElement = newNode.forget().downcast<Element>(); 469 RemoveForbiddenNodes(*newElement, isCrossDocument); 470 } 471 472 if (newElement->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) { 473 auto* newSVGElement = static_cast<SVGElement*>(newElement.get()); 474 if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet()) 475 newSVGElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]); 476 if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet()) 477 newSVGElement->SetLength(nsGkAtoms::height, 478 mLengthAttributes[ATTR_HEIGHT]); 479 } 480 481 // Bug 1415044 the specs do not say which referrer information we should use. 482 // This may change if there's any spec comes out. 483 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*this); 484 mContentURLData = new URLExtraData(baseURI.forget(), referrerInfo.forget(), 485 do_AddRef(NodePrincipal())); 486 487 targetElement->AddMutationObserver(this); 488 } 489 490 Document* SVGUseElement::GetSourceDocument() const { 491 nsIContent* targetElement = mReferencedElementTracker.get(); 492 return targetElement ? targetElement->OwnerDoc() : nullptr; 493 } 494 495 nsIURI* SVGUseElement::GetSourceDocURI() const { 496 if (auto* doc = GetSourceDocument()) { 497 return doc->GetDocumentURI(); 498 } 499 return nullptr; 500 } 501 502 const Encoding* SVGUseElement::GetSourceDocCharacterSet() const { 503 if (auto* doc = GetSourceDocument()) { 504 return doc->GetDocumentCharacterSet(); 505 } 506 return nullptr; 507 } 508 509 static nsINode* GetClonedChild(const SVGUseElement& aUseElement) { 510 const ShadowRoot* shadow = aUseElement.GetShadowRoot(); 511 return shadow ? shadow->GetFirstChild() : nullptr; 512 } 513 514 bool SVGUseElement::OurWidthAndHeightAreUsed() const { 515 nsINode* clonedChild = GetClonedChild(*this); 516 return clonedChild && 517 clonedChild->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol); 518 } 519 520 //---------------------------------------------------------------------- 521 // implementation helpers 522 523 void SVGUseElement::SyncWidthOrHeight(nsAtom* aName) { 524 NS_ASSERTION(aName == nsGkAtoms::width || aName == nsGkAtoms::height, 525 "The clue is in the function name"); 526 NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this"); 527 528 if (!OurWidthAndHeightAreUsed()) { 529 return; 530 } 531 532 auto* target = SVGElement::FromNode(GetClonedChild(*this)); 533 uint32_t index = 534 sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT; 535 536 if (mLengthAttributes[index].IsExplicitlySet()) { 537 target->SetLength(aName, mLengthAttributes[index]); 538 return; 539 } 540 if (target->IsSVGElement(nsGkAtoms::svg)) { 541 // Our width/height attribute is now no longer explicitly set, so we 542 // need to revert the clone's width/height to the width/height of the 543 // content that's being cloned. 544 TriggerReclone(); 545 return; 546 } 547 // Our width/height attribute is now no longer explicitly set, so we 548 // need to set the value to 100% 549 SVGAnimatedLength length; 550 length.Init(SVGContentUtils::XY, 0xff, 100, 551 SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE); 552 target->SetLength(aName, length); 553 } 554 555 void SVGUseElement::LookupHref() { 556 nsAutoString href; 557 if (mStringAttributes[HREF].IsExplicitlySet()) { 558 mStringAttributes[HREF].GetAnimValue(href, this); 559 } else { 560 mStringAttributes[XLINK_HREF].GetAnimValue(href, this); 561 } 562 563 if (href.IsEmpty()) { 564 return; 565 } 566 567 Element* treeToWatch = mOriginal ? mOriginal.get() : this; 568 if (nsContentUtils::IsLocalRefURL(href)) { 569 mReferencedElementTracker.ResetToLocalFragmentID(*treeToWatch, href); 570 return; 571 } 572 573 nsCOMPtr<nsIURI> baseURI = treeToWatch->GetBaseURI(); 574 nsCOMPtr<nsIURI> targetURI; 575 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, 576 GetComposedDoc(), baseURI); 577 if (!targetURI) { 578 return; 579 } 580 581 // Don't allow <use href="data:...">. Using "#ref" inside a data: document is 582 // handled above. 583 if (targetURI->SchemeIs("data")) { 584 return; 585 } 586 587 nsIReferrerInfo* referrer = 588 OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources(); 589 mReferencedElementTracker.ResetToURIWithFragmentID(*treeToWatch, targetURI, 590 referrer); 591 } 592 593 void SVGUseElement::TriggerReclone() { 594 if (Document* doc = GetComposedDoc()) { 595 doc->ScheduleSVGUseElementShadowTreeUpdate(*this); 596 } 597 } 598 599 void SVGUseElement::UnlinkSource() { 600 if (mReferencedElementTracker.get()) { 601 mReferencedElementTracker.get()->RemoveMutationObserver(this); 602 } 603 mReferencedElementTracker.Unlink(); 604 } 605 606 //---------------------------------------------------------------------- 607 // SVGElement methods 608 609 /* virtual */ 610 gfxMatrix SVGUseElement::ChildToUserSpaceTransform() const { 611 float x, y; 612 if (!SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(this, &x, &y)) { 613 const_cast<SVGUseElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr); 614 } 615 return gfxMatrix::Translation(x, y); 616 } 617 618 /* virtual */ 619 bool SVGUseElement::HasValidDimensions() const { 620 if (!OurWidthAndHeightAreUsed()) { 621 return true; 622 } 623 624 return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || 625 mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && 626 (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || 627 mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); 628 } 629 630 SVGElement::LengthAttributesInfo SVGUseElement::GetLengthInfo() { 631 return LengthAttributesInfo(mLengthAttributes, sLengthInfo, 632 std::size(sLengthInfo)); 633 } 634 635 SVGElement::StringAttributesInfo SVGUseElement::GetStringInfo() { 636 return StringAttributesInfo(mStringAttributes, sStringInfo, 637 std::size(sStringInfo)); 638 } 639 640 SVGUseFrame* SVGUseElement::GetFrame() const { 641 nsIFrame* frame = GetPrimaryFrame(); 642 // We might be a plain SVGContainerFrame if we didn't pass the conditional 643 // processing checks. 644 if (!frame || !frame->IsSVGUseFrame()) { 645 MOZ_ASSERT_IF(frame, frame->Type() == LayoutFrameType::None); 646 return nullptr; 647 } 648 return static_cast<SVGUseFrame*>(frame); 649 } 650 651 //---------------------------------------------------------------------- 652 // nsIContent methods 653 654 NS_IMETHODIMP_(bool) 655 SVGUseElement::IsAttributeMapped(const nsAtom* name) const { 656 return name == nsGkAtoms::x || name == nsGkAtoms::y || 657 SVGUseElementBase::IsAttributeMapped(name); 658 } 659 660 NonCustomCSSPropertyId SVGUseElement::GetCSSPropertyIdForAttrEnum( 661 uint8_t aAttrEnum) { 662 switch (aAttrEnum) { 663 case ATTR_X: 664 return eCSSProperty_x; 665 case ATTR_Y: 666 return eCSSProperty_y; 667 default: 668 // Currently we don't map width or height to style 669 return eCSSProperty_UNKNOWN; 670 } 671 } 672 673 } // namespace mozilla::dom