DocumentOrShadowRoot.cpp (26185B)
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 "DocumentOrShadowRoot.h" 8 9 #include "mozilla/AnimationComparator.h" 10 #include "mozilla/EventStateManager.h" 11 #include "mozilla/PointerLockManager.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/StyleSheet.h" 14 #include "mozilla/dom/AnimatableBinding.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/HTMLInputElement.h" 17 #include "mozilla/dom/ShadowRoot.h" 18 #include "mozilla/dom/StyleSheetList.h" 19 #include "nsContentUtils.h" 20 #include "nsFocusManager.h" 21 #include "nsIFormControl.h" 22 #include "nsLayoutUtils.h" 23 #include "nsNameSpaceManager.h" 24 #include "nsTHashtable.h" 25 #include "nsWindowSizes.h" 26 27 namespace mozilla::dom { 28 29 DocumentOrShadowRoot::DocumentOrShadowRoot(ShadowRoot* aShadowRoot) 30 : mAsNode(aShadowRoot), mKind(Kind::ShadowRoot) { 31 MOZ_ASSERT(mAsNode); 32 } 33 34 DocumentOrShadowRoot::DocumentOrShadowRoot(Document* aDoc) 35 : mAsNode(aDoc), mKind(Kind::Document) { 36 MOZ_ASSERT(mAsNode); 37 } 38 39 void DocumentOrShadowRoot::AddSizeOfOwnedSheetArrayExcludingThis( 40 nsWindowSizes& aSizes, const nsTArray<RefPtr<StyleSheet>>& aSheets) const { 41 size_t n = 0; 42 n += aSheets.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); 43 for (StyleSheet* sheet : aSheets) { 44 if (!sheet->GetAssociatedDocumentOrShadowRoot()) { 45 // Avoid over-reporting shared sheets. 46 continue; 47 } 48 n += sheet->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); 49 } 50 51 if (mKind == Kind::ShadowRoot) { 52 aSizes.mLayoutShadowDomStyleSheetsSize += n; 53 } else { 54 aSizes.mLayoutStyleSheetsSize += n; 55 } 56 } 57 58 void DocumentOrShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { 59 AddSizeOfOwnedSheetArrayExcludingThis(aSizes, mStyleSheets); 60 aSizes.mDOMSizes.mDOMOtherSize += 61 mIdentifierMap.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); 62 } 63 64 DocumentOrShadowRoot::~DocumentOrShadowRoot() { 65 for (StyleSheet* sheet : mStyleSheets) { 66 sheet->ClearAssociatedDocumentOrShadowRoot(); 67 } 68 } 69 70 StyleSheetList* DocumentOrShadowRoot::StyleSheets() { 71 if (!mDOMStyleSheets) { 72 mDOMStyleSheets = new StyleSheetList(*this); 73 } 74 return mDOMStyleSheets; 75 } 76 77 void DocumentOrShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { 78 aSheet.SetAssociatedDocumentOrShadowRoot(this); 79 mStyleSheets.InsertElementAt(aIndex, &aSheet); 80 } 81 82 void DocumentOrShadowRoot::RemoveStyleSheet(StyleSheet& aSheet) { 83 auto index = mStyleSheets.IndexOf(&aSheet); 84 if (index == mStyleSheets.NoIndex) { 85 // We should only hit this case if we are unlinking 86 // in which case mStyleSheets should be cleared. 87 MOZ_ASSERT(mKind != Kind::Document || 88 AsNode().AsDocument()->InUnlinkOrDeletion()); 89 MOZ_ASSERT(mStyleSheets.IsEmpty()); 90 return; 91 } 92 RefPtr<StyleSheet> sheet = std::move(mStyleSheets[index]); 93 mStyleSheets.RemoveElementAt(index); 94 RemoveSheetFromStylesIfApplicable(*sheet); 95 sheet->ClearAssociatedDocumentOrShadowRoot(); 96 AsNode().OwnerDoc()->PostStyleSheetRemovedEvent(aSheet); 97 } 98 99 void DocumentOrShadowRoot::RemoveSheetFromStylesIfApplicable( 100 StyleSheet& aSheet) { 101 if (!aSheet.IsApplicable()) { 102 return; 103 } 104 if (mKind == Kind::Document) { 105 AsNode().AsDocument()->RemoveStyleSheetFromStyleSets(aSheet); 106 } else { 107 MOZ_ASSERT(AsNode().IsShadowRoot()); 108 static_cast<ShadowRoot&>(AsNode()).RemoveSheetFromStyles(aSheet); 109 } 110 } 111 112 // https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets 113 void DocumentOrShadowRoot::OnSetAdoptedStyleSheets(StyleSheet& aSheet, 114 uint32_t aIndex, 115 ErrorResult& aRv) { 116 Document& doc = *AsNode().OwnerDoc(); 117 // 1. If value’s constructed flag is not set, or its constructor document is 118 // not equal to this DocumentOrShadowRoot's node document, throw a 119 // "NotAllowedError" DOMException. 120 121 if (!StaticPrefs:: 122 dom_webcomponents_lift_adoptedstylesheets_restriction_enabled()) { 123 if (!aSheet.IsConstructed()) { 124 return aRv.ThrowNotAllowedError( 125 "Adopted style sheet must be created through the Constructable " 126 "StyleSheets API"); 127 } 128 129 if (!aSheet.ConstructorDocumentMatches(doc)) { 130 return aRv.ThrowNotAllowedError( 131 "Adopted style sheet's constructor document must match the " 132 "document or shadow root's node document"); 133 } 134 } 135 136 auto* shadow = ShadowRoot::FromNode(AsNode()); 137 MOZ_ASSERT((mKind == Kind::ShadowRoot) == !!shadow); 138 139 auto existingIndex = mAdoptedStyleSheets.LastIndexOf(&aSheet); 140 // Ensure it's in the backing array at the right index. 141 mAdoptedStyleSheets.InsertElementAt(aIndex, &aSheet); 142 if (existingIndex == mAdoptedStyleSheets.NoIndex) { 143 // common case: we're not already adopting this sheet. 144 aSheet.AddAdopter(*this); 145 } else if (existingIndex < aIndex) { 146 // We're inserting an already-adopted stylesheet in a later position, so 147 // this one should take precedent and we should remove the old one. 148 RemoveSheetFromStylesIfApplicable(aSheet); 149 } else { 150 // The sheet is already at a position later than or equal to the current 151 // one, and is already adopted by us, we have nothing to do here other than 152 // adding to the current list. 153 return; 154 } 155 156 if (aSheet.IsApplicable()) { 157 if (mKind == Kind::Document) { 158 doc.AddStyleSheetToStyleSets(aSheet); 159 } else { 160 shadow->InsertSheetIntoAuthorData(aIndex, aSheet, mAdoptedStyleSheets); 161 } 162 } 163 } 164 165 void DocumentOrShadowRoot::OnDeleteAdoptedStyleSheets(StyleSheet& aSheet, 166 uint32_t aIndex, 167 ErrorResult&) { 168 MOZ_ASSERT(mAdoptedStyleSheets.ElementAt(aIndex) == &aSheet); 169 mAdoptedStyleSheets.RemoveElementAt(aIndex); 170 auto existingIndex = mAdoptedStyleSheets.LastIndexOf(&aSheet); 171 if (existingIndex != mAdoptedStyleSheets.NoIndex && existingIndex >= aIndex) { 172 // The sheet is still adopted by us and was already later from the one we're 173 // removing, so nothing to do. 174 return; 175 } 176 177 RemoveSheetFromStylesIfApplicable(aSheet); 178 if (existingIndex == mAdoptedStyleSheets.NoIndex) { 179 // The sheet is no longer adopted by us. 180 aSheet.RemoveAdopter(*this); 181 } else if (aSheet.IsApplicable()) { 182 // We need to re-insert the sheet at the right (pre-existing) index. 183 nsINode& node = AsNode(); 184 if (mKind == Kind::Document) { 185 node.AsDocument()->AddStyleSheetToStyleSets(aSheet); 186 } else { 187 ShadowRoot::FromNode(node)->InsertSheetIntoAuthorData( 188 existingIndex, aSheet, mAdoptedStyleSheets); 189 } 190 } 191 } 192 193 void DocumentOrShadowRoot::ClearAdoptedStyleSheets() { 194 auto* shadow = ShadowRoot::FromNode(AsNode()); 195 auto* doc = shadow ? nullptr : AsNode().AsDocument(); 196 MOZ_ASSERT(shadow || doc); 197 IgnoredErrorResult rv; 198 while (!mAdoptedStyleSheets.IsEmpty()) { 199 if (shadow) { 200 ShadowRoot_Binding::AdoptedStyleSheetsHelpers::RemoveLastElement(shadow, 201 rv); 202 } else { 203 Document_Binding::AdoptedStyleSheetsHelpers::RemoveLastElement(doc, rv); 204 } 205 MOZ_DIAGNOSTIC_ASSERT(!rv.Failed(), "Removal doesn't fail"); 206 } 207 } 208 209 void DocumentOrShadowRoot::CloneAdoptedSheetsFrom( 210 const DocumentOrShadowRoot& aSource) { 211 if (aSource.mAdoptedStyleSheets.IsEmpty()) { 212 return; 213 } 214 215 Document& ownerDoc = *AsNode().OwnerDoc(); 216 const Document& sourceDoc = *aSource.AsNode().OwnerDoc(); 217 auto* clonedSheetMap = static_cast<Document::AdoptedStyleSheetCloneCache*>( 218 sourceDoc.GetProperty(nsGkAtoms::adoptedsheetclones)); 219 MOZ_ASSERT(clonedSheetMap); 220 221 // We don't need to care about the reflector (AdoptedStyleSheetsHelpers and 222 // so) because this is only used for static documents. 223 for (const StyleSheet* sheet : aSource.mAdoptedStyleSheets) { 224 RefPtr<StyleSheet> clone = clonedSheetMap->LookupOrInsertWith( 225 sheet, [&] { return sheet->CloneAdoptedSheet(ownerDoc); }); 226 MOZ_ASSERT(clone); 227 MOZ_DIAGNOSTIC_ASSERT(clone->ConstructorDocumentMatches(ownerDoc)); 228 ErrorResult rv; 229 OnSetAdoptedStyleSheets(*clone, mAdoptedStyleSheets.Length(), rv); 230 MOZ_ASSERT(!rv.Failed()); 231 } 232 } 233 234 Element* DocumentOrShadowRoot::GetElementById( 235 const nsAString& aElementId) const { 236 if (MOZ_UNLIKELY(aElementId.IsEmpty())) { 237 ReportEmptyGetElementByIdArg(); 238 return nullptr; 239 } 240 241 if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) { 242 return entry->GetIdElement(); 243 } 244 245 return nullptr; 246 } 247 248 Element* DocumentOrShadowRoot::GetElementById(nsAtom* aElementId) const { 249 if (MOZ_UNLIKELY(aElementId == nsGkAtoms::_empty)) { 250 ReportEmptyGetElementByIdArg(); 251 return nullptr; 252 } 253 254 if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) { 255 return entry->GetIdElement(); 256 } 257 258 return nullptr; 259 } 260 261 already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByTagNameNS( 262 const nsAString& aNamespaceURI, const nsAString& aLocalName) { 263 ErrorResult rv; 264 RefPtr<nsContentList> list = 265 GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv); 266 if (rv.Failed()) { 267 return nullptr; 268 } 269 return list.forget(); 270 } 271 272 already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByTagNameNS( 273 const nsAString& aNamespaceURI, const nsAString& aLocalName, 274 ErrorResult& aResult) { 275 int32_t nameSpaceId = kNameSpaceID_Wildcard; 276 277 if (!aNamespaceURI.EqualsLiteral("*")) { 278 aResult = nsNameSpaceManager::GetInstance()->RegisterNameSpace( 279 aNamespaceURI, nameSpaceId); 280 if (aResult.Failed()) { 281 return nullptr; 282 } 283 } 284 285 NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!"); 286 return NS_GetContentList(&AsNode(), nameSpaceId, aLocalName); 287 } 288 289 already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByClassName( 290 const nsAString& aClasses) { 291 return nsContentUtils::GetElementsByClassName(&AsNode(), aClasses); 292 } 293 294 nsINode* DocumentOrShadowRoot::Retarget(nsINode* aNode) const { 295 for (nsINode* cur = aNode; cur; cur = cur->GetContainingShadowHost()) { 296 if (cur->SubtreeRoot() == &AsNode()) { 297 return cur; 298 } 299 } 300 return nullptr; 301 } 302 303 Element* DocumentOrShadowRoot::GetRetargetedFocusedElement() { 304 auto* content = AsNode().OwnerDoc()->GetUnretargetedFocusedContent(); 305 if (!content) { 306 return nullptr; 307 } 308 if (nsINode* retarget = Retarget(content)) { 309 return retarget->AsElement(); 310 } 311 return nullptr; 312 } 313 314 Element* DocumentOrShadowRoot::GetPointerLockElement() { 315 nsCOMPtr<Element> pointerLockedElement = 316 PointerLockManager::GetLockedElement(); 317 return Element::FromNodeOrNull(Retarget(pointerLockedElement)); 318 } 319 320 Element* DocumentOrShadowRoot::GetFullscreenElement() const { 321 if (!AsNode().IsInComposedDoc()) { 322 return nullptr; 323 } 324 325 Element* element = AsNode().OwnerDoc()->GetUnretargetedFullscreenElement(); 326 NS_ASSERTION(!element || element->State().HasState(ElementState::FULLSCREEN), 327 "Fullscreen element should have fullscreen styles applied"); 328 return Element::FromNodeOrNull(Retarget(element)); 329 } 330 331 namespace { 332 333 using FrameForPointOption = nsLayoutUtils::FrameForPointOption; 334 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions; 335 336 // Whether only one node or multiple nodes is requested. 337 enum class Multiple { 338 No, 339 Yes, 340 }; 341 342 // Whether we should flush layout or not. 343 enum class FlushLayout { 344 No, 345 Yes, 346 }; 347 348 enum class PerformRetargeting { 349 No, 350 Yes, 351 }; 352 353 template <typename NodeOrElement> 354 NodeOrElement* CastTo(nsINode*); 355 356 template <> 357 Element* CastTo<Element>(nsINode* aNode) { 358 return aNode->AsElement(); 359 } 360 361 template <> 362 nsINode* CastTo<nsINode>(nsINode* aNode) { 363 return aNode; 364 } 365 366 template <typename NodeOrElement> 367 static void QueryNodesFromRect(DocumentOrShadowRoot& aRoot, const nsRect& aRect, 368 FrameForPointOptions aOptions, 369 FlushLayout aShouldFlushLayout, 370 Multiple aMultiple, ViewportType aViewportType, 371 PerformRetargeting aPerformRetargeting, 372 nsTArray<RefPtr<NodeOrElement>>& aNodes) { 373 static_assert(std::is_same<nsINode, NodeOrElement>::value || 374 std::is_same<Element, NodeOrElement>::value, 375 "Should returning nodes or elements"); 376 377 constexpr bool returningElements = 378 std::is_same<Element, NodeOrElement>::value; 379 const bool retargeting = aPerformRetargeting == PerformRetargeting::Yes; 380 381 nsCOMPtr<Document> doc = aRoot.AsNode().OwnerDoc(); 382 383 // Make sure the layout information we get is up-to-date, and 384 // ensure we get a root frame (for everything but XUL) 385 if (aShouldFlushLayout == FlushLayout::Yes) { 386 doc->FlushPendingNotifications(FlushType::Layout); 387 } 388 389 PresShell* presShell = doc->GetPresShell(); 390 if (!presShell) { 391 return; 392 } 393 394 nsIFrame* rootFrame = presShell->GetRootFrame(); 395 // XUL docs, unlike HTML, have no frame tree until everything's done loading 396 if (!rootFrame) { 397 return; // return null to premature XUL callers as a reminder to wait 398 } 399 400 aOptions.mBits += FrameForPointOption::IgnorePaintSuppression; 401 aOptions.mBits += FrameForPointOption::IgnoreCrossDoc; 402 403 AutoTArray<nsIFrame*, 8> frames; 404 nsLayoutUtils::GetFramesForArea({rootFrame, aViewportType}, aRect, frames, 405 aOptions); 406 407 for (nsIFrame* frame : frames) { 408 nsINode* node = doc->GetContentInThisDocument(frame); 409 while (node && node->IsInNativeAnonymousSubtree()) { 410 nsIContent* root = node->GetClosestNativeAnonymousSubtreeRoot(); 411 MOZ_ASSERT(root, "content is connected"); 412 MOZ_ASSERT(root->IsRootOfNativeAnonymousSubtree(), "wat"); 413 if (root == &aRoot.AsNode()) { 414 // If we're in the anonymous subtree root we care about, don't retarget. 415 break; 416 } 417 node = root->GetParentOrShadowHostNode(); 418 } 419 420 if (!node) { 421 continue; 422 } 423 424 if (returningElements && !node->IsElement()) { 425 // If this helper is called via ElementsFromPoint, we need to make sure 426 // our frame is an element. Otherwise return whatever the top frame is 427 // even if it isn't the top-painted element. 428 // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so 429 // if 'content' is a child of such an element then we need to manually 430 // defer to the parent here. 431 if (aMultiple == Multiple::Yes && !frame->IsInSVGTextSubtree()) { 432 continue; 433 } 434 435 node = node->GetParent(); 436 if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(node)) { 437 node = shadow->Host(); 438 } 439 } 440 441 // XXXsmaug There is plenty of unspec'ed behavior here 442 // https://github.com/w3c/webcomponents/issues/735 443 // https://github.com/w3c/webcomponents/issues/736 444 if (retargeting) { 445 node = aRoot.Retarget(node); 446 } 447 448 if (node && node != aNodes.SafeLastElement(nullptr)) { 449 aNodes.AppendElement(CastTo<NodeOrElement>(node)); 450 if (aMultiple == Multiple::No) { 451 return; 452 } 453 } 454 } 455 } 456 457 template <typename NodeOrElement> 458 static void QueryNodesFromPoint(DocumentOrShadowRoot& aRoot, float aX, float aY, 459 FrameForPointOptions aOptions, 460 FlushLayout aShouldFlushLayout, 461 Multiple aMultiple, ViewportType aViewportType, 462 PerformRetargeting aPerformRetargeting, 463 nsTArray<RefPtr<NodeOrElement>>& aNodes) { 464 // As per the spec, we return null if either coord is negative. 465 if (!aOptions.mBits.contains(FrameForPointOption::IgnoreRootScrollFrame) && 466 (aX < 0 || aY < 0)) { 467 return; 468 } 469 470 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); 471 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); 472 nsPoint pt(x, y); 473 QueryNodesFromRect(aRoot, nsRect(pt, nsSize(1, 1)), aOptions, 474 aShouldFlushLayout, aMultiple, aViewportType, 475 aPerformRetargeting, aNodes); 476 } 477 478 } // namespace 479 480 Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) { 481 return ElementFromPointHelper(aX, aY, false, true, ViewportType::Layout); 482 } 483 484 void DocumentOrShadowRoot::ElementsFromPoint( 485 float aX, float aY, nsTArray<RefPtr<Element>>& aElements) { 486 QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, 487 ViewportType::Layout, PerformRetargeting::Yes, aElements); 488 } 489 490 void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY, 491 nsTArray<RefPtr<nsINode>>& aNodes) { 492 QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, 493 ViewportType::Layout, PerformRetargeting::Yes, aNodes); 494 } 495 496 nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) { 497 AutoTArray<RefPtr<nsINode>, 1> nodes; 498 QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::No, 499 ViewportType::Layout, PerformRetargeting::Yes, nodes); 500 return nodes.SafeElementAt(0); 501 } 502 503 Element* DocumentOrShadowRoot::ElementFromPointHelper( 504 float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout, 505 ViewportType aViewportType, bool aPerformRetargeting) { 506 EnumSet<FrameForPointOption> options; 507 if (aIgnoreRootScrollFrame) { 508 options += FrameForPointOption::IgnoreRootScrollFrame; 509 } 510 511 auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; 512 auto performRetargeting = 513 aPerformRetargeting ? PerformRetargeting::Yes : PerformRetargeting::No; 514 515 AutoTArray<RefPtr<Element>, 1> elements; 516 QueryNodesFromPoint(*this, aX, aY, options, flush, Multiple::No, 517 aViewportType, performRetargeting, elements); 518 return elements.SafeElementAt(0); 519 } 520 521 void DocumentOrShadowRoot::NodesFromRect(float aX, float aY, float aTopSize, 522 float aRightSize, float aBottomSize, 523 float aLeftSize, 524 bool aIgnoreRootScrollFrame, 525 bool aFlushLayout, bool aOnlyVisible, 526 float aVisibleThreshold, 527 nsTArray<RefPtr<nsINode>>& aReturn) { 528 // Following the same behavior of elementFromPoint, 529 // we don't return anything if either coord is negative 530 if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) { 531 return; 532 } 533 534 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize); 535 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize); 536 nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1; 537 nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1; 538 539 nsRect rect(x, y, w, h); 540 541 FrameForPointOptions options; 542 if (aIgnoreRootScrollFrame) { 543 options.mBits += FrameForPointOption::IgnoreRootScrollFrame; 544 } 545 if (aOnlyVisible) { 546 options.mBits += FrameForPointOption::OnlyVisible; 547 options.mVisibleThreshold = aVisibleThreshold; 548 } 549 550 auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; 551 QueryNodesFromRect(*this, rect, options, flush, Multiple::Yes, 552 ViewportType::Layout, PerformRetargeting::No, aReturn); 553 } 554 555 Element* DocumentOrShadowRoot::AddIDTargetObserver(nsAtom* aID, 556 IDTargetObserver aObserver, 557 void* aData, 558 bool aForImage) { 559 nsDependentAtomString id(aID); 560 561 if (!CheckGetElementByIdArg(id)) { 562 return nullptr; 563 } 564 565 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aID); 566 NS_ENSURE_TRUE(entry, nullptr); 567 568 entry->AddContentChangeCallback(aObserver, aData, aForImage); 569 return aForImage ? entry->GetImageIdElement() : entry->GetIdElement(); 570 } 571 572 void DocumentOrShadowRoot::RemoveIDTargetObserver(nsAtom* aID, 573 IDTargetObserver aObserver, 574 void* aData, bool aForImage) { 575 nsDependentAtomString id(aID); 576 577 if (!CheckGetElementByIdArg(id)) { 578 return; 579 } 580 581 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aID); 582 if (!entry) { 583 return; 584 } 585 586 entry->RemoveContentChangeCallback(aObserver, aData, aForImage); 587 } 588 589 Element* DocumentOrShadowRoot::LookupImageElement(nsAtom* aId) { 590 if (aId->IsEmpty()) { 591 return nullptr; 592 } 593 594 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); 595 return entry ? entry->GetImageIdElement() : nullptr; 596 } 597 598 void DocumentOrShadowRoot::ReportEmptyGetElementByIdArg() const { 599 nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc()); 600 } 601 602 void DocumentOrShadowRoot::GetAnimations( 603 nsTArray<RefPtr<Animation>>& aAnimations) { 604 // As with Element::GetAnimations we initially flush style here. 605 // This should ensure that there are no subsequent changes to the tree 606 // structure while iterating over the children below. 607 if (Document* doc = AsNode().GetComposedDoc()) { 608 doc->FlushPendingNotifications( 609 ChangesToFlush(FlushType::Style, /* aFlushAnimations = */ false, 610 /* aUpdateRelevancy = */ false)); 611 } 612 613 GetAnimationsOptions options; 614 options.mSubtree = true; 615 616 for (RefPtr<nsIContent> child = AsNode().GetFirstChild(); child; 617 child = child->GetNextSibling()) { 618 if (RefPtr<Element> element = Element::FromNode(child)) { 619 nsTArray<RefPtr<Animation>> result; 620 element->GetAnimationsWithoutFlush(options, result); 621 aAnimations.AppendElements(std::move(result)); 622 } 623 } 624 625 aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>()); 626 } 627 628 struct SheetTreeOrderComparator { 629 nsINode* mNode = nullptr; 630 mutable nsContentUtils::NodeIndexCache mCache; 631 632 int operator()(StyleSheet* aSheet) const { 633 auto* sheetNode = aSheet->GetOwnerNode(); 634 MOZ_ASSERT(sheetNode != mNode, "Sheet already in the list?"); 635 if (!sheetNode) { 636 // We go after all the link headers. 637 return 1; 638 } 639 return nsContentUtils::CompareTreePosition<TreeKind::DOM>(mNode, sheetNode, 640 nullptr, &mCache); 641 } 642 }; 643 644 size_t DocumentOrShadowRoot::FindSheetInsertionPointInTree( 645 const StyleSheet& aSheet) const { 646 MOZ_ASSERT(!aSheet.IsConstructed()); 647 nsINode* owningNode = aSheet.GetOwnerNode(); 648 // We should always have an owning node unless Link: headers are at play. 649 MOZ_ASSERT_IF(!owningNode, AsNode().IsDocument()); 650 MOZ_ASSERT_IF(owningNode, owningNode->SubtreeRoot() == &AsNode()); 651 if (mStyleSheets.IsEmpty()) { 652 return 0; 653 } 654 655 if (!owningNode) { 656 // We come from a link header, our position is after all the other link 657 // headers, but before any link-owned node. 658 size_t i = 0; 659 for (const auto& sheet : mStyleSheets) { 660 if (sheet->GetOwnerNode()) { 661 break; 662 } 663 i++; 664 } 665 return i; 666 } 667 668 SheetTreeOrderComparator cmp{owningNode}; 669 if (cmp(mStyleSheets.LastElement()) > 0) { 670 // Optimize for append. 671 return mStyleSheets.Length(); 672 } 673 size_t idx; 674 BinarySearchIf(mStyleSheets, 0, mStyleSheets.Length(), cmp, &idx); 675 return idx; 676 } 677 678 size_t DocumentOrShadowRoot::StyleOrderIndexOfSheet( 679 const StyleSheet& aSheet) const { 680 if (aSheet.IsConstructed()) { 681 // NOTE: constructable sheets can have duplicates, so we need to start 682 // looking from behind. 683 size_t index = mAdoptedStyleSheets.LastIndexOf(&aSheet); 684 return index == mAdoptedStyleSheets.NoIndex ? index : index + SheetCount(); 685 } 686 return mStyleSheets.LastIndexOf(&aSheet); 687 } 688 689 void DocumentOrShadowRoot::TraverseSheetRefInStylesIfApplicable( 690 StyleSheet& aSheet, nsCycleCollectionTraversalCallback& cb) { 691 if (!aSheet.IsApplicable()) { 692 return; 693 } 694 if (mKind == Kind::ShadowRoot) { 695 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]"); 696 cb.NoteXPCOMChild(&aSheet); 697 } else if (AsNode().AsDocument()->StyleSetFilled()) { 698 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( 699 cb, "mStyleSet->mRawSet.stylist.stylesheets.<origin>[i]"); 700 cb.NoteXPCOMChild(&aSheet); 701 } 702 } 703 704 void DocumentOrShadowRoot::TraverseStyleSheets( 705 nsTArray<RefPtr<StyleSheet>>& aSheets, const char* aEdgeName, 706 nsCycleCollectionTraversalCallback& cb) { 707 MOZ_ASSERT(aEdgeName); 708 MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets); 709 for (StyleSheet* sheet : aSheets) { 710 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, aEdgeName); 711 cb.NoteXPCOMChild(sheet); 712 TraverseSheetRefInStylesIfApplicable(*sheet, cb); 713 } 714 } 715 716 void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp, 717 nsCycleCollectionTraversalCallback& cb) { 718 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) 719 tmp->TraverseStyleSheets(tmp->mStyleSheets, "mStyleSheets[i]", cb); 720 721 tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { 722 tmp->TraverseSheetRefInStylesIfApplicable(aSheet, cb); 723 }); 724 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets); 725 726 for (auto iter = tmp->mIdentifierMap.Iter(); !iter.Done(); iter.Next()) { 727 iter.Get()->Traverse(&cb); 728 } 729 } 730 731 void DocumentOrShadowRoot::UnlinkStyleSheets( 732 nsTArray<RefPtr<StyleSheet>>& aSheets) { 733 MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets); 734 for (StyleSheet* sheet : aSheets) { 735 sheet->ClearAssociatedDocumentOrShadowRoot(); 736 RemoveSheetFromStylesIfApplicable(*sheet); 737 } 738 aSheets.Clear(); 739 } 740 741 void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) { 742 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets); 743 tmp->UnlinkStyleSheets(tmp->mStyleSheets); 744 tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { 745 aSheet.RemoveAdopter(*tmp); 746 tmp->RemoveSheetFromStylesIfApplicable(aSheet); 747 }); 748 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets); 749 tmp->mIdentifierMap.Clear(); 750 } 751 752 } // namespace mozilla::dom