ShadowRoot.cpp (30852B)
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/ShadowRoot.h" 8 9 #include "ChildIterator.h" 10 #include "mozilla/DeclarationBlock.h" 11 #include "mozilla/EventDispatcher.h" 12 #include "mozilla/GlobalStyleSheetCache.h" 13 #include "mozilla/IdentifierMapEntry.h" 14 #include "mozilla/PresShell.h" 15 #include "mozilla/PresShellInlines.h" 16 #include "mozilla/ScopeExit.h" 17 #include "mozilla/ServoBindings.h" 18 #include "mozilla/ServoStyleRuleMap.h" 19 #include "mozilla/StyleSheet.h" 20 #include "mozilla/dom/BindContext.h" 21 #include "mozilla/dom/DirectionalityUtils.h" 22 #include "mozilla/dom/DocumentFragment.h" 23 #include "mozilla/dom/Element.h" 24 #include "mozilla/dom/ElementBinding.h" 25 #include "mozilla/dom/HTMLDetailsElement.h" 26 #include "mozilla/dom/HTMLSlotElement.h" 27 #include "mozilla/dom/HTMLSummaryElement.h" 28 #include "mozilla/dom/MutationObservers.h" 29 #include "mozilla/dom/StyleSheetList.h" 30 #include "mozilla/dom/Text.h" 31 #include "mozilla/dom/TreeOrderedArrayInlines.h" 32 #include "mozilla/dom/TrustedTypeUtils.h" 33 #include "mozilla/dom/TrustedTypesConstants.h" 34 #include "mozilla/dom/UnbindContext.h" 35 #include "nsContentUtils.h" 36 #include "nsINode.h" 37 #include "nsWindowSizes.h" 38 39 using namespace mozilla; 40 using namespace mozilla::dom; 41 42 NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot) 43 44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment) 45 DocumentOrShadowRoot::Traverse(tmp, cb); 46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 47 48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot) 49 DocumentOrShadowRoot::Unlink(tmp); 50 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment) 51 52 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot) 53 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent) 54 NS_INTERFACE_MAP_END_INHERITING(DocumentFragment) 55 56 NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment) 57 NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment) 58 59 ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode, 60 Element::DelegatesFocus aDelegatesFocus, 61 SlotAssignmentMode aSlotAssignment, 62 IsClonable aIsClonable, IsSerializable aIsSerializable, 63 Declarative aDeclarative, 64 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 65 : DocumentFragment(std::move(aNodeInfo)), 66 DocumentOrShadowRoot(this), 67 mMode(aMode), 68 mDelegatesFocus(aDelegatesFocus), 69 mSlotAssignment(aSlotAssignment), 70 mIsDetailsShadowTree(aElement->IsHTMLElement(nsGkAtoms::details)), 71 mIsAvailableToElementInternals(false), 72 mIsDeclarative(aDeclarative), 73 mIsClonable(aIsClonable), 74 mIsSerializable(aIsSerializable), 75 mReferenceTarget(nsGkAtoms::_empty) { 76 // nsINode.h relies on this. 77 MOZ_ASSERT(static_cast<nsINode*>(this) == reinterpret_cast<nsINode*>(this)); 78 MOZ_ASSERT(static_cast<nsIContent*>(this) == 79 reinterpret_cast<nsIContent*>(this)); 80 81 SetHost(aElement); 82 83 // Nodes in a shadow tree should never store a value 84 // in the subtree root pointer, nodes in the shadow tree 85 // track the subtree root using GetContainingShadow(). 86 ClearSubtreeRootPointer(); 87 88 SetFlags(NODE_IS_IN_SHADOW_TREE); 89 if (Host()->IsInNativeAnonymousSubtree()) { 90 // NOTE(emilio): We could consider just propagating the 91 // IN_NATIVE_ANONYMOUS_SUBTREE flag (not making this an anonymous root), but 92 // that breaks the invariant that if two nodes have the same 93 // NativeAnonymousSubtreeRoot() they are in the same DOM tree, which we rely 94 // on a couple places and would need extra fixes. 95 // 96 // We don't hit this case for now anyways, bug 1824886 would start hitting 97 // it. 98 SetIsNativeAnonymousRoot(); 99 } 100 Bind(); 101 102 ExtendedDOMSlots()->mContainingShadow = this; 103 } 104 105 ShadowRoot::~ShadowRoot() { 106 if (IsInComposedDoc()) { 107 OwnerDoc()->RemoveComposedDocShadowRoot(*this); 108 } 109 110 MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this)); 111 112 UnsetFlags(NODE_IS_IN_SHADOW_TREE); 113 114 // nsINode destructor expects mSubtreeRoot == this. 115 SetSubtreeRootPointer(this); 116 } 117 118 MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf) 119 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf) 120 121 void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 122 size_t* aNodeSize) const { 123 DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize); 124 DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes); 125 aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis( 126 ShadowRootAuthorStylesMallocSizeOf, 127 ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get()); 128 } 129 130 JSObject* ShadowRoot::WrapNode(JSContext* aCx, 131 JS::Handle<JSObject*> aGivenProto) { 132 return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto); 133 } 134 135 void ShadowRoot::NodeInfoChanged(Document* aOldDoc) { 136 DocumentFragment::NodeInfoChanged(aOldDoc); 137 Document* newDoc = OwnerDoc(); 138 const bool fromOrToTemplate = 139 aOldDoc->GetTemplateContentsOwnerIfExists() == newDoc || 140 newDoc->GetTemplateContentsOwnerIfExists() == aOldDoc; 141 if (!fromOrToTemplate) { 142 ClearAdoptedStyleSheets(); 143 } 144 } 145 146 void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) { 147 if (aOther->IsRootOfNativeAnonymousSubtree()) { 148 SetIsNativeAnonymousRoot(); 149 } 150 151 if (aOther->IsUAWidget()) { 152 SetIsUAWidget(); 153 } 154 155 CloneAdoptedSheetsFrom(*aOther); 156 } 157 158 nsresult ShadowRoot::Bind() { 159 MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?"); 160 if (Host()->IsInComposedDoc()) { 161 SetIsConnected(true); 162 Document* doc = OwnerDoc(); 163 doc->AddComposedDocShadowRoot(*this); 164 // If our stylesheets somehow mutated when we were disconnected, we need to 165 // ensure that our style data gets flushed as appropriate. 166 if (mServoStyles && Servo_AuthorStyles_IsDirty(mServoStyles.get())) { 167 doc->RecordShadowStyleChange(*this); 168 } 169 } 170 171 BindContext context(*this); 172 for (nsIContent* child = GetFirstChild(); child; 173 child = child->GetNextSibling()) { 174 nsresult rv = child->BindToTree(context, *this); 175 NS_ENSURE_SUCCESS(rv, rv); 176 } 177 178 return NS_OK; 179 } 180 181 void ShadowRoot::Unbind() { 182 if (IsInComposedDoc()) { 183 SetIsConnected(false); 184 OwnerDoc()->RemoveComposedDocShadowRoot(*this); 185 } 186 187 UnbindContext context(*this, /* aBatchState = */ nullptr); 188 for (nsIContent* child = GetFirstChild(); child; 189 child = child->GetNextSibling()) { 190 child->UnbindFromTree(context); 191 } 192 193 MutationObservers::NotifyParentChainChanged(this); 194 } 195 196 void ShadowRoot::Unattach() { 197 MOZ_ASSERT(!HasSlots(), "Won't work!"); 198 if (!GetHost()) { 199 // It is possible that we've been unlinked already. In such case host 200 // should have called Unbind and ShadowRoot's own unlink. 201 return; 202 } 203 204 Unbind(); 205 SetHost(nullptr); 206 } 207 208 void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) { 209 MOZ_ASSERT(aElement); 210 Document* doc = GetComposedDoc(); 211 if (!doc) { 212 return; 213 } 214 215 if (!aElement->IsInComposedDoc()) { 216 // If RemoveSlot is called from UnbindFromTree while we're moving 217 // (moveBefore) the slot elsewhere, invalidating styles and layout tree 218 // is done explicitly elsewhere. 219 return; 220 } 221 222 PresShell* presShell = doc->GetPresShell(); 223 if (!presShell) { 224 return; 225 } 226 227 presShell->DestroyFramesForAndRestyle(aElement); 228 } 229 230 void ShadowRoot::PartAdded(const Element& aPart) { 231 MOZ_ASSERT(aPart.HasPartAttribute()); 232 MOZ_ASSERT(!mParts.Contains(&aPart)); 233 mParts.AppendElement(&aPart); 234 } 235 236 void ShadowRoot::PartRemoved(const Element& aPart) { 237 MOZ_ASSERT(mParts.Contains(&aPart)); 238 mParts.RemoveElement(&aPart); 239 MOZ_ASSERT(!mParts.Contains(&aPart)); 240 } 241 242 void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) { 243 MOZ_ASSERT(aSlot); 244 245 // Note that if name attribute missing, the slot is a default slot. 246 nsAutoString name; 247 aSlot->GetName(name); 248 249 SlotArray& currentSlots = *mSlotMap.GetOrInsertNew(name); 250 251 size_t index = currentSlots.Insert(*aSlot); 252 253 // For Named slots, slottables are inserted into the other slot 254 // which has the same name already, however it's not the case 255 // for manual slots 256 if (index != 0 && SlotAssignment() == SlotAssignmentMode::Named) { 257 return; 258 } 259 260 InvalidateStyleAndLayoutOnSubtree(aSlot); 261 262 HTMLSlotElement* oldSlot = currentSlots.SafeElementAt(1, nullptr); 263 if (SlotAssignment() == SlotAssignmentMode::Named) { 264 if (oldSlot) { 265 MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot); 266 267 // Move assigned nodes from old slot to new slot. 268 InvalidateStyleAndLayoutOnSubtree(oldSlot); 269 bool doEnqueueSlotChange = false; 270 auto assignedNodes = 271 ToTArray<AutoTArray<nsINode*, 8>>(oldSlot->AssignedNodes()); 272 for (nsINode* assignedNode : assignedNodes) { 273 oldSlot->RemoveAssignedNode(*assignedNode->AsContent()); 274 aSlot->AppendAssignedNode(*assignedNode->AsContent()); 275 doEnqueueSlotChange = true; 276 } 277 278 if (doEnqueueSlotChange) { 279 oldSlot->EnqueueSlotChangeEvent(); 280 aSlot->EnqueueSlotChangeEvent(); 281 } 282 } else { 283 bool doEnqueueSlotChange = false; 284 // Otherwise add appropriate nodes to this slot from the host. 285 for (nsIContent* child = GetHost()->GetFirstChild(); child; 286 child = child->GetNextSibling()) { 287 nsAutoString slotName; 288 GetSlotNameFor(*child, slotName); 289 if (!child->IsSlotable() || !slotName.Equals(name)) { 290 continue; 291 } 292 doEnqueueSlotChange = true; 293 aSlot->AppendAssignedNode(*child); 294 } 295 296 if (doEnqueueSlotChange) { 297 aSlot->EnqueueSlotChangeEvent(); 298 } 299 } 300 } else { 301 bool doEnqueueSlotChange = false; 302 for (const auto& node : aSlot->ManuallyAssignedNodes()) { 303 if (GetHost() != node->GetParent()) { 304 continue; 305 } 306 307 MOZ_ASSERT(node->IsContent(), 308 "Manually assigned nodes should be an element or a text"); 309 nsIContent* content = node->AsContent(); 310 311 aSlot->AppendAssignedNode(*content); 312 doEnqueueSlotChange = true; 313 } 314 if (doEnqueueSlotChange) { 315 aSlot->EnqueueSlotChangeEvent(); 316 } 317 } 318 } 319 320 void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) { 321 MOZ_ASSERT(aSlot); 322 323 nsAutoString name; 324 aSlot->GetName(name); 325 326 MOZ_ASSERT(mSlotMap.Get(name)); 327 328 SlotArray& currentSlots = *mSlotMap.Get(name); 329 MOZ_DIAGNOSTIC_ASSERT(currentSlots.Contains(aSlot), 330 "Slot to de-register wasn't found?"); 331 if (currentSlots.Length() == 1) { 332 MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named, 333 currentSlots.ElementAt(0) == aSlot); 334 335 InvalidateStyleAndLayoutOnSubtree(aSlot); 336 337 mSlotMap.Remove(name); 338 if (!aSlot->AssignedNodes().IsEmpty()) { 339 aSlot->ClearAssignedNodes(); 340 aSlot->EnqueueSlotChangeEvent(); 341 } 342 343 return; 344 } 345 if (SlotAssignment() == SlotAssignmentMode::Manual) { 346 InvalidateStyleAndLayoutOnSubtree(aSlot); 347 if (!aSlot->AssignedNodes().IsEmpty()) { 348 aSlot->ClearAssignedNodes(); 349 aSlot->EnqueueSlotChangeEvent(); 350 } 351 } 352 353 const bool wasFirstSlot = currentSlots.ElementAt(0) == aSlot; 354 currentSlots.RemoveElement(*aSlot); 355 if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) { 356 return; 357 } 358 359 // Move assigned nodes from removed slot to the next slot in 360 // tree order with the same name. 361 InvalidateStyleAndLayoutOnSubtree(aSlot); 362 HTMLSlotElement* replacementSlot = currentSlots.ElementAt(0); 363 auto assignedNodes = 364 ToTArray<AutoTArray<nsINode*, 8>>(aSlot->AssignedNodes()); 365 if (assignedNodes.IsEmpty()) { 366 return; 367 } 368 369 InvalidateStyleAndLayoutOnSubtree(replacementSlot); 370 for (auto* assignedNode : assignedNodes) { 371 aSlot->RemoveAssignedNode(*assignedNode->AsContent()); 372 replacementSlot->AppendAssignedNode(*assignedNode->AsContent()); 373 } 374 375 aSlot->EnqueueSlotChangeEvent(); 376 replacementSlot->EnqueueSlotChangeEvent(); 377 } 378 379 // FIXME(emilio): There's a bit of code duplication between this and the 380 // equivalent ServoStyleSet methods, it'd be nice to not duplicate it... 381 void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) { 382 if (!aSheet.IsApplicable()) { 383 return; 384 } 385 386 MOZ_ASSERT(mServoStyles); 387 if (mStyleRuleMap) { 388 mStyleRuleMap->RuleAdded(aSheet, aRule); 389 } 390 391 if (aRule.IsIncompleteImportRule()) { 392 return; 393 } 394 395 Servo_AuthorStyles_ForceDirty(mServoStyles.get()); 396 ApplicableRulesChanged(); 397 } 398 399 void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) { 400 if (!aSheet.IsApplicable()) { 401 return; 402 } 403 404 MOZ_ASSERT(mServoStyles); 405 if (mStyleRuleMap) { 406 mStyleRuleMap->RuleRemoved(aSheet, aRule); 407 } 408 Servo_AuthorStyles_ForceDirty(mServoStyles.get()); 409 ApplicableRulesChanged(); 410 } 411 412 void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule* aRule, 413 const StyleRuleChange& aChange) { 414 if (!aSheet.IsApplicable()) { 415 return; 416 } 417 if (mStyleRuleMap && aChange.mOldBlock != aChange.mNewBlock) { 418 mStyleRuleMap->RuleDeclarationsChanged(*aRule, aChange.mOldBlock->Raw(), 419 aChange.mNewBlock->Raw()); 420 } 421 MOZ_ASSERT(mServoStyles); 422 Servo_AuthorStyles_ForceDirty(mServoStyles.get()); 423 ApplicableRulesChanged(); 424 } 425 426 void ShadowRoot::ImportRuleLoaded(StyleSheet& aSheet) { 427 if (mStyleRuleMap) { 428 mStyleRuleMap->SheetAdded(aSheet); 429 } 430 431 if (!aSheet.IsApplicable()) { 432 return; 433 } 434 435 // TODO(emilio): Could handle it like a regular sheet insertion, I guess, to 436 // avoid throwing away the whole style data. 437 Servo_AuthorStyles_ForceDirty(mServoStyles.get()); 438 ApplicableRulesChanged(); 439 } 440 441 // We don't need to do anything else than forwarding to the document if 442 // necessary. 443 void ShadowRoot::SheetCloned(StyleSheet& aSheet) { 444 if (Document* doc = GetComposedDoc()) { 445 if (PresShell* shell = doc->GetPresShell()) { 446 shell->StyleSet()->SheetCloned(aSheet); 447 } 448 } 449 } 450 451 void ShadowRoot::ApplicableRulesChanged() { 452 if (Document* doc = GetComposedDoc()) { 453 doc->RecordShadowStyleChange(*this); 454 } 455 } 456 457 void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { 458 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet); 459 if (aSheet.IsApplicable()) { 460 InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets); 461 } 462 } 463 464 StyleSheet* FirstApplicableAdoptedStyleSheet( 465 const nsTArray<RefPtr<StyleSheet>>& aList) { 466 size_t i = 0; 467 for (StyleSheet* sheet : aList) { 468 // Deal with duplicate sheets by only considering the last one. 469 if (sheet->IsApplicable() && MOZ_LIKELY(aList.LastIndexOf(sheet) == i)) { 470 return sheet; 471 } 472 i++; 473 } 474 return nullptr; 475 } 476 477 void ShadowRoot::InsertSheetIntoAuthorData( 478 size_t aIndex, StyleSheet& aSheet, 479 const nsTArray<RefPtr<StyleSheet>>& aList) { 480 MOZ_ASSERT(aSheet.IsApplicable()); 481 MOZ_ASSERT(aList[aIndex] == &aSheet); 482 MOZ_ASSERT(aList.LastIndexOf(&aSheet) == aIndex); 483 MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets); 484 485 if (!mServoStyles) { 486 mServoStyles.reset(Servo_AuthorStyles_Create()); 487 } 488 489 if (mStyleRuleMap) { 490 mStyleRuleMap->SheetAdded(aSheet); 491 } 492 493 auto changedOnExit = 494 mozilla::MakeScopeExit([&] { ApplicableRulesChanged(); }); 495 496 for (size_t i = aIndex + 1; i < aList.Length(); ++i) { 497 StyleSheet* beforeSheet = aList.ElementAt(i); 498 if (!beforeSheet->IsApplicable()) { 499 continue; 500 } 501 502 // If this is a duplicate adopted stylesheet that is not in the right 503 // position (the last one) then we skip over it. Otherwise we're done. 504 if (&aList == &mAdoptedStyleSheets && 505 MOZ_UNLIKELY(aList.LastIndexOf(beforeSheet) != i)) { 506 continue; 507 } 508 509 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet, 510 beforeSheet); 511 return; 512 } 513 514 if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) { 515 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet); 516 return; 517 } 518 519 if (auto* before = FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets)) { 520 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet, 521 before); 522 } else { 523 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet); 524 } 525 } 526 527 // FIXME(emilio): This needs to notify document observers and such, 528 // presumably. 529 void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) { 530 auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets; 531 size_t index = sheetList.LastIndexOf(&aSheet); 532 if (index == sheetList.NoIndex) { 533 // NOTE(emilio): @import sheets are handled in the relevant RuleAdded 534 // notification, which only notifies after the sheet is loaded. 535 // 536 // This setup causes weirdness in other places, we may want to fix this in 537 // bug 1465031. 538 MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(), 539 "It'd better be an @import sheet"); 540 return; 541 } 542 if (aSheet.IsApplicable()) { 543 InsertSheetIntoAuthorData(index, aSheet, sheetList); 544 } else { 545 MOZ_ASSERT(mServoStyles); 546 if (mStyleRuleMap) { 547 mStyleRuleMap->SheetRemoved(aSheet); 548 } 549 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet); 550 ApplicableRulesChanged(); 551 } 552 } 553 554 void ShadowRoot::AppendBuiltInStyleSheet(BuiltInStyleSheet aSheet) { 555 auto* cache = GlobalStyleSheetCache::Singleton(); 556 // NOTE(emilio): It's important to Clone() the stylesheet to avoid leaking, 557 // since the built-in sheet is kept alive forever, and AppendStyleSheet will 558 // set the associated global of the stylesheet. 559 RefPtr sheet = cache->BuiltInSheet(aSheet)->Clone(nullptr, nullptr); 560 AppendStyleSheet(*sheet); 561 } 562 563 void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) { 564 MOZ_ASSERT(aSheet.IsApplicable()); 565 MOZ_ASSERT(mServoStyles); 566 if (mStyleRuleMap) { 567 mStyleRuleMap->SheetRemoved(aSheet); 568 } 569 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet); 570 ApplicableRulesChanged(); 571 } 572 573 void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) { 574 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId); 575 if (entry) { 576 entry->AddIdElement(aElement); 577 } 578 } 579 580 void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) { 581 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); 582 if (entry) { 583 entry->RemoveIdElement(aElement); 584 if (entry->IsEmpty()) { 585 mIdentifierMap.RemoveEntry(entry); 586 } 587 } 588 } 589 590 void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 591 aVisitor.mCanHandle = true; 592 aVisitor.mRootOfClosedTree = IsClosed(); 593 // Inform that we're about to exit the current scope. 594 aVisitor.mRelatedTargetRetargetedInCurrentScope = false; 595 596 // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6 597 if (!aVisitor.mEvent->mFlags.mComposed) { 598 nsIContent* originalTarget = 599 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget); 600 if (originalTarget && originalTarget->GetContainingShadow() == this) { 601 // If we do stop propagation, we still want to propagate 602 // the event to chrome (nsPIDOMWindow::GetParentTarget()). 603 // The load event is special in that we don't ever propagate it 604 // to chrome. 605 nsPIDOMWindowOuter* win = OwnerDoc()->GetWindow(); 606 EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad 607 ? win->GetParentTarget() 608 : nullptr; 609 610 aVisitor.SetParentTarget(parentTarget, true); 611 return; 612 } 613 } 614 615 nsIContent* shadowHost = GetHost(); 616 aVisitor.SetParentTarget(shadowHost, false); 617 618 nsIContent* content = 619 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget); 620 if (content && content->GetContainingShadow() == this) { 621 aVisitor.mEventTargetAtParent = shadowHost; 622 } 623 } 624 625 void ShadowRoot::GetSlotNameFor(const nsIContent& aContent, 626 nsAString& aName) const { 627 if (mIsDetailsShadowTree) { 628 const auto* summary = HTMLSummaryElement::FromNode(aContent); 629 if (summary && summary->IsMainSummary()) { 630 aName.AssignLiteral("internal-main-summary"); 631 } 632 // Otherwise use the default slot. 633 return; 634 } 635 636 // Note that if slot attribute is missing, assign it to the first default 637 // slot, if exists. 638 if (const Element* element = Element::FromNode(aContent)) { 639 element->GetAttr(nsGkAtoms::slot, aName); 640 } 641 } 642 643 ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor( 644 nsIContent& aContent) { 645 HTMLSlotElement* slot = nullptr; 646 647 if (SlotAssignment() == SlotAssignmentMode::Manual) { 648 slot = aContent.GetManualSlotAssignment(); 649 if (!slot || slot->GetContainingShadow() != this) { 650 return {}; 651 } 652 } else { 653 nsAutoString slotName; 654 GetSlotNameFor(aContent, slotName); 655 656 SlotArray* slots = mSlotMap.Get(slotName); 657 if (!slots) { 658 return {}; 659 } 660 slot = (*slots).ElementAt(0); 661 } 662 663 MOZ_ASSERT(slot); 664 665 if (SlotAssignment() == SlotAssignmentMode::Named) { 666 if (!aContent.GetNextSibling()) { 667 // aContent is the last child, no need to loop through the assigned nodes, 668 // we're necessarily the last one. 669 // 670 // This prevents multiple appends into the host from getting quadratic. 671 return {slot, Nothing()}; 672 } 673 } else { 674 // For manual slots, if aContent is the last element, we return Nothing 675 // because we just need to append the element to the assigned nodes. No need 676 // to return an index. 677 if (slot->ManuallyAssignedNodes().SafeLastElement(nullptr) == &aContent) { 678 return {slot, Nothing()}; 679 } 680 } 681 682 // Find the appropriate position in the assigned node list for the newly 683 // assigned content. 684 if (SlotAssignment() == SlotAssignmentMode::Manual) { 685 const nsTArray<nsINode*>& manuallyAssignedNodes = 686 slot->ManuallyAssignedNodes(); 687 auto index = manuallyAssignedNodes.IndexOf(&aContent); 688 if (index != manuallyAssignedNodes.NoIndex) { 689 return {slot, Some(index)}; 690 } 691 } else { 692 const Span assignedNodes = slot->AssignedNodes(); 693 nsIContent* currentContent = GetHost()->GetFirstChild(); 694 for (uint32_t i = 0; i < assignedNodes.Length(); i++) { 695 // Seek through the host's explicit children until the 696 // assigned content is found. 697 while (currentContent && currentContent != assignedNodes[i]) { 698 if (currentContent == &aContent) { 699 return {slot, Some(i)}; 700 } 701 currentContent = currentContent->GetNextSibling(); 702 } 703 } 704 } 705 706 return {slot, Nothing()}; 707 } 708 709 void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) { 710 MOZ_ASSERT(aElementOrText.GetParent() == GetHost()); 711 MOZ_ASSERT(aElementOrText.IsElement() || aElementOrText.IsText()); 712 HTMLSlotElement* oldSlot = aElementOrText.GetAssignedSlot(); 713 714 SlotInsertionPoint assignment = SlotInsertionPointFor(aElementOrText); 715 716 if (assignment.mSlot == oldSlot) { 717 // Nothing to do here. 718 return; 719 } 720 721 // The layout invalidation piece for Manual slots is handled in 722 // HTMLSlotElement::Assign 723 if (aElementOrText.IsElement() && 724 SlotAssignment() == SlotAssignmentMode::Named) { 725 if (Document* doc = GetComposedDoc()) { 726 if (RefPtr<PresShell> presShell = doc->GetPresShell()) { 727 presShell->SlotAssignmentWillChange(*aElementOrText.AsElement(), 728 oldSlot, assignment.mSlot); 729 } 730 } 731 } 732 733 if (oldSlot) { 734 if (SlotAssignment() == SlotAssignmentMode::Named) { 735 oldSlot->RemoveAssignedNode(aElementOrText); 736 // Don't need to EnqueueSlotChangeEvent for Manual slots because it 737 // needs to be done in tree order, so 738 // HTMLSlotElement::Assign will handle it explicitly. 739 oldSlot->EnqueueSlotChangeEvent(); 740 } else { 741 oldSlot->RemoveManuallyAssignedNode(aElementOrText); 742 } 743 } 744 745 if (assignment.mSlot) { 746 if (assignment.mIndex) { 747 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElementOrText); 748 } else { 749 assignment.mSlot->AppendAssignedNode(aElementOrText); 750 } 751 // Similar as above, HTMLSlotElement::Assign handles enqueuing 752 // slotchange event. 753 if (SlotAssignment() == SlotAssignmentMode::Named) { 754 assignment.mSlot->EnqueueSlotChangeEvent(); 755 } 756 } 757 } 758 759 void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) { 760 MOZ_ASSERT(mIsDetailsShadowTree); 761 if (aReason == SummaryChangeReason::Insertion) { 762 // We've inserted a summary element, may need to remove the existing one. 763 SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns); 764 MOZ_RELEASE_ASSERT(array && (*array).Length() == 1); 765 HTMLSlotElement* slot = (*array).ElementAt(0); 766 auto assigned = slot->AssignedNodes(); 767 if (assigned.IsEmpty()) { 768 return; 769 } 770 if (auto* summary = HTMLSummaryElement::FromNode(assigned[0])) { 771 MaybeReassignContent(*summary); 772 } 773 } else if (MOZ_LIKELY(GetHost())) { 774 // We need to null-check GetHost() in case we're unlinking already. 775 auto* details = HTMLDetailsElement::FromNode(Host()); 776 MOZ_DIAGNOSTIC_ASSERT(details); 777 // We've removed a summary element, we may need to assign the new one. 778 if (HTMLSummaryElement* newMainSummary = details->GetFirstSummary()) { 779 MaybeReassignContent(*newMainSummary); 780 } 781 } 782 } 783 784 Element* ShadowRoot::GetActiveElement() { 785 return GetRetargetedFocusedElement(); 786 } 787 788 nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode, 789 nsINode& aNode, bool aDeep, 790 mozilla::ErrorResult& rv) { 791 MOZ_ASSERT(IsUAWidget()); 792 793 if (aParentNode.SubtreeRoot() != this) { 794 rv.Throw(NS_ERROR_INVALID_ARG); 795 return nullptr; 796 } 797 798 RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv); 799 if (rv.Failed()) { 800 return nullptr; 801 } 802 803 return aParentNode.AppendChild(*node, rv); 804 } 805 806 nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode, 807 const nsAString& aTagName, 808 mozilla::ErrorResult& rv) { 809 MOZ_ASSERT(IsUAWidget()); 810 811 if (aParentNode.SubtreeRoot() != this) { 812 rv.Throw(NS_ERROR_INVALID_ARG); 813 return nullptr; 814 } 815 816 // This option is not exposed to UA Widgets 817 ElementCreationOptionsOrString options; 818 819 RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv); 820 if (rv.Failed()) { 821 return nullptr; 822 } 823 824 return aParentNode.AppendChild(*node, rv); 825 } 826 827 void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) { 828 // Need to null-check the host because we may be unlinked already. 829 MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost()); 830 831 HTMLSlotElement* slot = aChild.GetAssignedSlot(); 832 if (!slot) { 833 return; 834 } 835 836 MOZ_DIAGNOSTIC_ASSERT(!aChild.IsRootOfNativeAnonymousSubtree(), 837 "How did aChild end up assigned to a slot?"); 838 // If the slot is going to start showing fallback content, we need to tell 839 // layout about it. 840 if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) { 841 InvalidateStyleAndLayoutOnSubtree(slot); 842 } 843 844 slot->EnqueueSlotChangeEvent(); 845 slot->RemoveAssignedNode(aChild); 846 if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { 847 MaybeReassignMainSummary(SummaryChangeReason::Deletion); 848 } 849 } 850 851 void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) { 852 MOZ_ASSERT(aChild.GetParent() == GetHost()); 853 // Check to ensure that the child not an anonymous subtree root because even 854 // though its parent could be the host it may not be in the host's child 855 // list. 856 if (aChild.IsRootOfNativeAnonymousSubtree()) { 857 return; 858 } 859 860 if (!aChild.IsSlotable()) { 861 return; 862 } 863 864 if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { 865 MaybeReassignMainSummary(SummaryChangeReason::Insertion); 866 } 867 868 SlotInsertionPoint assignment = SlotInsertionPointFor(aChild); 869 if (!assignment.mSlot) { 870 return; 871 } 872 873 // Fallback content will go away, let layout know. 874 if (assignment.mSlot->AssignedNodes().IsEmpty() && 875 assignment.mSlot->HasChildren()) { 876 InvalidateStyleAndLayoutOnSubtree(assignment.mSlot); 877 } 878 879 if (assignment.mIndex) { 880 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild); 881 } else { 882 assignment.mSlot->AppendAssignedNode(aChild); 883 } 884 assignment.mSlot->EnqueueSlotChangeEvent(); 885 } 886 887 ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() { 888 if (!mStyleRuleMap) { 889 mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>(); 890 } 891 mStyleRuleMap->EnsureTable(*this); 892 return *mStyleRuleMap; 893 } 894 895 nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const { 896 *aResult = nullptr; 897 return NS_ERROR_DOM_NOT_SUPPORTED_ERR; 898 } 899 900 void ShadowRoot::SetHTML(const nsAString& aHTML, const SetHTMLOptions& aOptions, 901 ErrorResult& aError) { 902 RefPtr<Element> host = GetHost(); 903 nsContentUtils::SetHTML(this, host, aHTML, aOptions, aError); 904 } 905 906 void ShadowRoot::SetHTMLUnsafe(const TrustedHTMLOrString& aHTML, 907 const SetHTMLUnsafeOptions& aOptions, 908 nsIPrincipal* aSubjectPrincipal, 909 ErrorResult& aError) { 910 RefPtr<Element> host = GetHost(); 911 nsContentUtils::SetHTMLUnsafe(this, host, aHTML, aOptions, 912 true /*aIsShadowRoot*/, aSubjectPrincipal, 913 aError); 914 } 915 916 void ShadowRoot::GetInnerHTML( 917 OwningTrustedHTMLOrNullIsEmptyString& aInnerHTML) { 918 DocumentFragment::GetInnerHTML(aInnerHTML.SetAsNullIsEmptyString()); 919 } 920 921 MOZ_CAN_RUN_SCRIPT void ShadowRoot::SetInnerHTML( 922 const TrustedHTMLOrNullIsEmptyString& aInnerHTML, 923 nsIPrincipal* aSubjectPrincipal, ErrorResult& aError) { 924 constexpr nsLiteralString sink = u"ShadowRoot innerHTML"_ns; 925 926 Maybe<nsAutoString> compliantStringHolder; 927 const nsAString* compliantString = 928 TrustedTypeUtils::GetTrustedTypesCompliantString( 929 aInnerHTML, sink, kTrustedTypesOnlySinkGroup, *this, 930 aSubjectPrincipal, compliantStringHolder, aError); 931 if (aError.Failed()) { 932 return; 933 } 934 935 SetInnerHTMLInternal(*compliantString, aError); 936 } 937 938 void ShadowRoot::GetHTML(const GetHTMLOptions& aOptions, nsAString& aResult) { 939 nsContentUtils::SerializeNodeToMarkup<SerializeShadowRoots::Yes>( 940 this, true, aResult, aOptions.mSerializableShadowRoots, 941 aOptions.mShadowRoots); 942 } 943 void ShadowRoot::SetReferenceTarget(RefPtr<nsAtom> aTarget) { 944 MOZ_ASSERT(aTarget); 945 mReferenceTarget = std::move(aTarget); 946 }