Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / ShadowRoot.cpp
blob94db7bad85c5f70706a05a9ad616a1bb71ce27ed
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/BindContext.h"
8 #include "mozilla/dom/ShadowRoot.h"
9 #include "mozilla/dom/DocumentFragment.h"
10 #include "ChildIterator.h"
11 #include "nsContentUtils.h"
12 #include "nsINode.h"
13 #include "nsWindowSizes.h"
14 #include "mozilla/dom/DirectionalityUtils.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/HTMLDetailsElement.h"
17 #include "mozilla/dom/HTMLSlotElement.h"
18 #include "mozilla/dom/HTMLSummaryElement.h"
19 #include "mozilla/dom/MutationObservers.h"
20 #include "mozilla/dom/Text.h"
21 #include "mozilla/dom/TreeOrderedArrayInlines.h"
22 #include "mozilla/dom/UnbindContext.h"
23 #include "mozilla/EventDispatcher.h"
24 #include "mozilla/IdentifierMapEntry.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/PresShellInlines.h"
27 #include "mozilla/ScopeExit.h"
28 #include "mozilla/ServoStyleRuleMap.h"
29 #include "mozilla/StyleSheet.h"
30 #include "mozilla/dom/StyleSheetList.h"
32 using namespace mozilla;
33 using namespace mozilla::dom;
35 NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment)
38 DocumentOrShadowRoot::Traverse(tmp, cb);
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
42 DocumentOrShadowRoot::Unlink(tmp);
43 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)
45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
46 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
47 NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
49 NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
50 NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
52 ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
53 Element::DelegatesFocus aDelegatesFocus,
54 SlotAssignmentMode aSlotAssignment,
55 IsClonable aIsClonable, Declarative aDeclarative,
56 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
57 : DocumentFragment(std::move(aNodeInfo)),
58 DocumentOrShadowRoot(this),
59 mMode(aMode),
60 mDelegatesFocus(aDelegatesFocus),
61 mSlotAssignment(aSlotAssignment),
62 mIsDetailsShadowTree(aElement->IsHTMLElement(nsGkAtoms::details)),
63 mIsAvailableToElementInternals(false),
64 mIsDeclarative(aDeclarative),
65 mIsClonable(aIsClonable) {
66 // nsINode.h relies on this.
67 MOZ_ASSERT(static_cast<nsINode*>(this) == reinterpret_cast<nsINode*>(this));
68 MOZ_ASSERT(static_cast<nsIContent*>(this) ==
69 reinterpret_cast<nsIContent*>(this));
71 SetHost(aElement);
73 // Nodes in a shadow tree should never store a value
74 // in the subtree root pointer, nodes in the shadow tree
75 // track the subtree root using GetContainingShadow().
76 ClearSubtreeRootPointer();
78 SetFlags(NODE_IS_IN_SHADOW_TREE);
79 if (Host()->IsInNativeAnonymousSubtree()) {
80 // NOTE(emilio): We could consider just propagating the
81 // IN_NATIVE_ANONYMOUS_SUBTREE flag (not making this an anonymous root), but
82 // that breaks the invariant that if two nodes have the same
83 // NativeAnonymousSubtreeRoot() they are in the same DOM tree, which we rely
84 // on a couple places and would need extra fixes.
86 // We don't hit this case for now anyways, bug 1824886 would start hitting
87 // it.
88 SetIsNativeAnonymousRoot();
90 Bind();
92 ExtendedDOMSlots()->mContainingShadow = this;
95 ShadowRoot::~ShadowRoot() {
96 if (IsInComposedDoc()) {
97 OwnerDoc()->RemoveComposedDocShadowRoot(*this);
100 MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));
102 UnsetFlags(NODE_IS_IN_SHADOW_TREE);
104 // nsINode destructor expects mSubtreeRoot == this.
105 SetSubtreeRootPointer(this);
108 MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf)
109 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf)
111 void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
112 size_t* aNodeSize) const {
113 DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize);
114 DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes);
115 aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis(
116 ShadowRootAuthorStylesMallocSizeOf,
117 ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get());
120 JSObject* ShadowRoot::WrapNode(JSContext* aCx,
121 JS::Handle<JSObject*> aGivenProto) {
122 return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto);
125 void ShadowRoot::NodeInfoChanged(Document* aOldDoc) {
126 DocumentFragment::NodeInfoChanged(aOldDoc);
127 Document* newDoc = OwnerDoc();
128 const bool fromOrToTemplate =
129 aOldDoc->GetTemplateContentsOwnerIfExists() == newDoc ||
130 newDoc->GetTemplateContentsOwnerIfExists() == aOldDoc;
131 if (!fromOrToTemplate) {
132 ClearAdoptedStyleSheets();
136 void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) {
137 if (aOther->IsRootOfNativeAnonymousSubtree()) {
138 SetIsNativeAnonymousRoot();
141 if (aOther->IsUAWidget()) {
142 SetIsUAWidget();
145 size_t sheetCount = aOther->SheetCount();
146 for (size_t i = 0; i < sheetCount; ++i) {
147 StyleSheet* sheet = aOther->SheetAt(i);
148 if (sheet->IsApplicable()) {
149 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, this);
150 if (clonedSheet) {
151 AppendStyleSheet(*clonedSheet.get());
155 CloneAdoptedSheetsFrom(*aOther);
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);
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);
178 return NS_OK;
181 void ShadowRoot::Unbind() {
182 if (IsInComposedDoc()) {
183 SetIsConnected(false);
184 OwnerDoc()->RemoveComposedDocShadowRoot(*this);
187 UnbindContext context(*this);
188 for (nsIContent* child = GetFirstChild(); child;
189 child = child->GetNextSibling()) {
190 child->UnbindFromTree(context);
193 MutationObservers::NotifyParentChainChanged(this);
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;
204 Unbind();
205 SetHost(nullptr);
208 void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) {
209 MOZ_ASSERT(aElement);
210 Document* doc = GetComposedDoc();
211 if (!doc) {
212 return;
215 PresShell* presShell = doc->GetPresShell();
216 if (!presShell) {
217 return;
220 presShell->DestroyFramesForAndRestyle(aElement);
223 void ShadowRoot::PartAdded(const Element& aPart) {
224 MOZ_ASSERT(aPart.HasPartAttribute());
225 MOZ_ASSERT(!mParts.Contains(&aPart));
226 mParts.AppendElement(&aPart);
229 void ShadowRoot::PartRemoved(const Element& aPart) {
230 MOZ_ASSERT(mParts.Contains(&aPart));
231 mParts.RemoveElement(&aPart);
232 MOZ_ASSERT(!mParts.Contains(&aPart));
235 void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
236 MOZ_ASSERT(aSlot);
238 // Note that if name attribute missing, the slot is a default slot.
239 nsAutoString name;
240 aSlot->GetName(name);
242 SlotArray& currentSlots = *mSlotMap.GetOrInsertNew(name);
244 size_t index = currentSlots.Insert(*aSlot);
246 // For Named slots, slottables are inserted into the other slot
247 // which has the same name already, however it's not the case
248 // for manual slots
249 if (index != 0 && SlotAssignment() == SlotAssignmentMode::Named) {
250 return;
253 HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(1);
254 if (SlotAssignment() == SlotAssignmentMode::Named) {
255 if (oldSlot) {
256 MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot);
258 // Move assigned nodes from old slot to new slot.
259 InvalidateStyleAndLayoutOnSubtree(oldSlot);
260 const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes();
261 bool doEnqueueSlotChange = false;
262 while (assignedNodes.Length() > 0) {
263 nsINode* assignedNode = assignedNodes[0];
265 oldSlot->RemoveAssignedNode(*assignedNode->AsContent());
266 aSlot->AppendAssignedNode(*assignedNode->AsContent());
267 doEnqueueSlotChange = true;
270 if (doEnqueueSlotChange) {
271 oldSlot->EnqueueSlotChangeEvent();
272 aSlot->EnqueueSlotChangeEvent();
273 SlotStateChanged(oldSlot);
274 SlotStateChanged(aSlot);
276 } else {
277 bool doEnqueueSlotChange = false;
278 // Otherwise add appropriate nodes to this slot from the host.
279 for (nsIContent* child = GetHost()->GetFirstChild(); child;
280 child = child->GetNextSibling()) {
281 nsAutoString slotName;
282 GetSlotNameFor(*child, slotName);
283 if (!child->IsSlotable() || !slotName.Equals(name)) {
284 continue;
286 doEnqueueSlotChange = true;
287 aSlot->AppendAssignedNode(*child);
290 if (doEnqueueSlotChange) {
291 aSlot->EnqueueSlotChangeEvent();
292 SlotStateChanged(aSlot);
295 } else {
296 bool doEnqueueSlotChange = false;
297 for (const auto& node : aSlot->ManuallyAssignedNodes()) {
298 if (GetHost() != node->GetParent()) {
299 continue;
302 MOZ_ASSERT(node->IsContent(),
303 "Manually assigned nodes should be an element or a text");
304 nsIContent* content = node->AsContent();
306 aSlot->AppendAssignedNode(*content);
307 doEnqueueSlotChange = true;
309 if (doEnqueueSlotChange) {
310 aSlot->EnqueueSlotChangeEvent();
311 SlotStateChanged(aSlot);
316 void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
317 MOZ_ASSERT(aSlot);
319 nsAutoString name;
320 aSlot->GetName(name);
322 MOZ_ASSERT(mSlotMap.Get(name));
324 SlotArray& currentSlots = *mSlotMap.Get(name);
325 MOZ_DIAGNOSTIC_ASSERT(currentSlots->Contains(aSlot),
326 "Slot to de-register wasn't found?");
327 if (currentSlots->Length() == 1) {
328 MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named,
329 currentSlots->ElementAt(0) == aSlot);
331 InvalidateStyleAndLayoutOnSubtree(aSlot);
333 mSlotMap.Remove(name);
334 if (!aSlot->AssignedNodes().IsEmpty()) {
335 aSlot->ClearAssignedNodes();
336 aSlot->EnqueueSlotChangeEvent();
339 return;
341 if (SlotAssignment() == SlotAssignmentMode::Manual) {
342 InvalidateStyleAndLayoutOnSubtree(aSlot);
343 if (!aSlot->AssignedNodes().IsEmpty()) {
344 aSlot->ClearAssignedNodes();
345 aSlot->EnqueueSlotChangeEvent();
349 const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot;
350 currentSlots.RemoveElement(*aSlot);
351 if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) {
352 return;
355 // Move assigned nodes from removed slot to the next slot in
356 // tree order with the same name.
357 InvalidateStyleAndLayoutOnSubtree(aSlot);
358 HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0);
359 const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
360 if (assignedNodes.IsEmpty()) {
361 return;
364 InvalidateStyleAndLayoutOnSubtree(replacementSlot);
365 while (!assignedNodes.IsEmpty()) {
366 nsINode* assignedNode = assignedNodes[0];
368 aSlot->RemoveAssignedNode(*assignedNode->AsContent());
369 replacementSlot->AppendAssignedNode(*assignedNode->AsContent());
372 aSlot->EnqueueSlotChangeEvent();
373 replacementSlot->EnqueueSlotChangeEvent();
376 // FIXME(emilio): There's a bit of code duplication between this and the
377 // equivalent ServoStyleSet methods, it'd be nice to not duplicate it...
378 void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
379 if (!aSheet.IsApplicable()) {
380 return;
383 MOZ_ASSERT(mServoStyles);
384 if (mStyleRuleMap) {
385 mStyleRuleMap->RuleAdded(aSheet, aRule);
388 if (aRule.IsIncompleteImportRule()) {
389 return;
392 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
393 ApplicableRulesChanged();
396 void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
397 if (!aSheet.IsApplicable()) {
398 return;
401 MOZ_ASSERT(mServoStyles);
402 if (mStyleRuleMap) {
403 mStyleRuleMap->RuleRemoved(aSheet, aRule);
405 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
406 ApplicableRulesChanged();
409 void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*,
410 StyleRuleChangeKind) {
411 if (!aSheet.IsApplicable()) {
412 return;
415 MOZ_ASSERT(mServoStyles);
416 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
417 ApplicableRulesChanged();
420 void ShadowRoot::ImportRuleLoaded(CSSImportRule&, StyleSheet& aSheet) {
421 if (mStyleRuleMap) {
422 mStyleRuleMap->SheetAdded(aSheet);
425 if (!aSheet.IsApplicable()) {
426 return;
429 // TODO(emilio): Could handle it like a regular sheet insertion, I guess, to
430 // avoid throwing away the whole style data.
431 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
432 ApplicableRulesChanged();
435 // We don't need to do anything else than forwarding to the document if
436 // necessary.
437 void ShadowRoot::SheetCloned(StyleSheet& aSheet) {
438 if (Document* doc = GetComposedDoc()) {
439 if (PresShell* shell = doc->GetPresShell()) {
440 shell->StyleSet()->SheetCloned(aSheet);
445 void ShadowRoot::ApplicableRulesChanged() {
446 if (Document* doc = GetComposedDoc()) {
447 doc->RecordShadowStyleChange(*this);
451 void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
452 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
453 if (aSheet.IsApplicable()) {
454 InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets);
458 StyleSheet* FirstApplicableAdoptedStyleSheet(
459 const nsTArray<RefPtr<StyleSheet>>& aList) {
460 size_t i = 0;
461 for (StyleSheet* sheet : aList) {
462 // Deal with duplicate sheets by only considering the last one.
463 if (sheet->IsApplicable() && MOZ_LIKELY(aList.LastIndexOf(sheet) == i)) {
464 return sheet;
466 i++;
468 return nullptr;
471 void ShadowRoot::InsertSheetIntoAuthorData(
472 size_t aIndex, StyleSheet& aSheet,
473 const nsTArray<RefPtr<StyleSheet>>& aList) {
474 MOZ_ASSERT(aSheet.IsApplicable());
475 MOZ_ASSERT(aList[aIndex] == &aSheet);
476 MOZ_ASSERT(aList.LastIndexOf(&aSheet) == aIndex);
477 MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets);
479 if (!mServoStyles) {
480 mServoStyles.reset(Servo_AuthorStyles_Create());
483 if (mStyleRuleMap) {
484 mStyleRuleMap->SheetAdded(aSheet);
487 auto changedOnExit =
488 mozilla::MakeScopeExit([&] { ApplicableRulesChanged(); });
490 for (size_t i = aIndex + 1; i < aList.Length(); ++i) {
491 StyleSheet* beforeSheet = aList.ElementAt(i);
492 if (!beforeSheet->IsApplicable()) {
493 continue;
496 // If this is a duplicate adopted stylesheet that is not in the right
497 // position (the last one) then we skip over it. Otherwise we're done.
498 if (&aList == &mAdoptedStyleSheets &&
499 MOZ_UNLIKELY(aList.LastIndexOf(beforeSheet) != i)) {
500 continue;
503 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
504 beforeSheet);
505 return;
508 if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) {
509 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
510 return;
513 if (auto* before = FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets)) {
514 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
515 before);
516 } else {
517 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
521 // FIXME(emilio): This needs to notify document observers and such,
522 // presumably.
523 void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
524 auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets;
525 int32_t index = sheetList.LastIndexOf(&aSheet);
526 if (index < 0) {
527 // NOTE(emilio): @import sheets are handled in the relevant RuleAdded
528 // notification, which only notifies after the sheet is loaded.
530 // This setup causes weirdness in other places, we may want to fix this in
531 // bug 1465031.
532 MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(),
533 "It'd better be an @import sheet");
534 return;
536 if (aSheet.IsApplicable()) {
537 InsertSheetIntoAuthorData(size_t(index), aSheet, sheetList);
538 } else {
539 MOZ_ASSERT(mServoStyles);
540 if (mStyleRuleMap) {
541 mStyleRuleMap->SheetRemoved(aSheet);
543 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
544 ApplicableRulesChanged();
548 void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) {
549 MOZ_ASSERT(aSheet.IsApplicable());
550 MOZ_ASSERT(mServoStyles);
551 if (mStyleRuleMap) {
552 mStyleRuleMap->SheetRemoved(aSheet);
554 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
555 ApplicableRulesChanged();
558 void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) {
559 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
560 if (entry) {
561 entry->AddIdElement(aElement);
565 void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
566 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
567 if (entry) {
568 entry->RemoveIdElement(aElement);
569 if (entry->IsEmpty()) {
570 mIdentifierMap.RemoveEntry(entry);
575 void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
576 aVisitor.mCanHandle = true;
577 aVisitor.mRootOfClosedTree = IsClosed();
578 // Inform that we're about to exit the current scope.
579 aVisitor.mRelatedTargetRetargetedInCurrentScope = false;
581 // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
582 if (!aVisitor.mEvent->mFlags.mComposed) {
583 nsCOMPtr<nsIContent> originalTarget =
584 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
585 if (originalTarget && originalTarget->GetContainingShadow() == this) {
586 // If we do stop propagation, we still want to propagate
587 // the event to chrome (nsPIDOMWindow::GetParentTarget()).
588 // The load event is special in that we don't ever propagate it
589 // to chrome.
590 nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
591 EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
592 ? win->GetParentTarget()
593 : nullptr;
595 aVisitor.SetParentTarget(parentTarget, true);
596 return;
600 nsIContent* shadowHost = GetHost();
601 aVisitor.SetParentTarget(shadowHost, false);
603 nsCOMPtr<nsIContent> content(
604 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget));
605 if (content && content->GetContainingShadow() == this) {
606 aVisitor.mEventTargetAtParent = shadowHost;
610 void ShadowRoot::GetSlotNameFor(const nsIContent& aContent,
611 nsAString& aName) const {
612 if (mIsDetailsShadowTree) {
613 const auto* summary = HTMLSummaryElement::FromNode(aContent);
614 if (summary && summary->IsMainSummary()) {
615 aName.AssignLiteral("internal-main-summary");
617 // Otherwise use the default slot.
618 return;
621 // Note that if slot attribute is missing, assign it to the first default
622 // slot, if exists.
623 if (const Element* element = Element::FromNode(aContent)) {
624 element->GetAttr(nsGkAtoms::slot, aName);
628 ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
629 nsIContent& aContent) {
630 HTMLSlotElement* slot = nullptr;
632 if (SlotAssignment() == SlotAssignmentMode::Manual) {
633 slot = aContent.GetManualSlotAssignment();
634 if (!slot || slot->GetContainingShadow() != this) {
635 return {};
637 } else {
638 nsAutoString slotName;
639 GetSlotNameFor(aContent, slotName);
641 SlotArray* slots = mSlotMap.Get(slotName);
642 if (!slots) {
643 return {};
645 slot = (*slots)->ElementAt(0);
648 MOZ_ASSERT(slot);
650 if (SlotAssignment() == SlotAssignmentMode::Named) {
651 if (!aContent.GetNextSibling()) {
652 // aContent is the last child, no need to loop through the assigned nodes,
653 // we're necessarily the last one.
655 // This prevents multiple appends into the host from getting quadratic.
656 return {slot, Nothing()};
658 } else {
659 // For manual slots, if aContent is the last element, we return Nothing
660 // because we just need to append the element to the assigned nodes. No need
661 // to return an index.
662 if (slot->ManuallyAssignedNodes().SafeLastElement(nullptr) == &aContent) {
663 return {slot, Nothing()};
667 // Find the appropriate position in the assigned node list for the newly
668 // assigned content.
669 if (SlotAssignment() == SlotAssignmentMode::Manual) {
670 const nsTArray<nsINode*>& manuallyAssignedNodes =
671 slot->ManuallyAssignedNodes();
672 auto index = manuallyAssignedNodes.IndexOf(&aContent);
673 if (index != manuallyAssignedNodes.NoIndex) {
674 return {slot, Some(index)};
676 } else {
677 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
678 nsIContent* currentContent = GetHost()->GetFirstChild();
679 for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
680 // Seek through the host's explicit children until the
681 // assigned content is found.
682 while (currentContent && currentContent != assignedNodes[i]) {
683 if (currentContent == &aContent) {
684 return {slot, Some(i)};
686 currentContent = currentContent->GetNextSibling();
691 return {slot, Nothing()};
694 void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) {
695 MOZ_ASSERT(aElementOrText.GetParent() == GetHost());
696 MOZ_ASSERT(aElementOrText.IsElement() || aElementOrText.IsText());
697 HTMLSlotElement* oldSlot = aElementOrText.GetAssignedSlot();
699 SlotInsertionPoint assignment = SlotInsertionPointFor(aElementOrText);
701 if (assignment.mSlot == oldSlot) {
702 // Nothing to do here.
703 return;
706 // The layout invalidation piece for Manual slots is handled in
707 // HTMLSlotElement::Assign
708 if (aElementOrText.IsElement() &&
709 SlotAssignment() == SlotAssignmentMode::Named) {
710 if (Document* doc = GetComposedDoc()) {
711 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
712 presShell->SlotAssignmentWillChange(*aElementOrText.AsElement(),
713 oldSlot, assignment.mSlot);
718 if (oldSlot) {
719 if (SlotAssignment() == SlotAssignmentMode::Named) {
720 oldSlot->RemoveAssignedNode(aElementOrText);
721 // Don't need to EnqueueSlotChangeEvent for Manual slots because it
722 // needs to be done in tree order, so
723 // HTMLSlotElement::Assign will handle it explicitly.
724 oldSlot->EnqueueSlotChangeEvent();
725 } else {
726 oldSlot->RemoveManuallyAssignedNode(aElementOrText);
730 if (assignment.mSlot) {
731 if (assignment.mIndex) {
732 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElementOrText);
733 } else {
734 assignment.mSlot->AppendAssignedNode(aElementOrText);
736 // Similar as above, HTMLSlotElement::Assign handles enqueuing
737 // slotchange event.
738 if (SlotAssignment() == SlotAssignmentMode::Named) {
739 assignment.mSlot->EnqueueSlotChangeEvent();
744 void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) {
745 MOZ_ASSERT(mIsDetailsShadowTree);
746 if (aReason == SummaryChangeReason::Insertion) {
747 // We've inserted a summary element, may need to remove the existing one.
748 SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns);
749 MOZ_RELEASE_ASSERT(array && (*array)->Length() == 1);
750 HTMLSlotElement* slot = (*array)->ElementAt(0);
751 auto* summary = HTMLSummaryElement::FromNodeOrNull(
752 slot->AssignedNodes().SafeElementAt(0));
753 if (summary) {
754 MaybeReassignContent(*summary);
756 } else if (MOZ_LIKELY(GetHost())) {
757 // We need to null-check GetHost() in case we're unlinking already.
758 auto* details = HTMLDetailsElement::FromNode(Host());
759 MOZ_DIAGNOSTIC_ASSERT(details);
760 // We've removed a summary element, we may need to assign the new one.
761 if (HTMLSummaryElement* newMainSummary = details->GetFirstSummary()) {
762 MaybeReassignContent(*newMainSummary);
767 Element* ShadowRoot::GetActiveElement() {
768 return GetRetargetedFocusedElement();
771 nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode,
772 nsINode& aNode, bool aDeep,
773 mozilla::ErrorResult& rv) {
774 MOZ_ASSERT(IsUAWidget());
776 if (aParentNode.SubtreeRoot() != this) {
777 rv.Throw(NS_ERROR_INVALID_ARG);
778 return nullptr;
781 RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv);
782 if (rv.Failed()) {
783 return nullptr;
786 return aParentNode.AppendChild(*node, rv);
789 nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode,
790 const nsAString& aTagName,
791 mozilla::ErrorResult& rv) {
792 MOZ_ASSERT(IsUAWidget());
794 if (aParentNode.SubtreeRoot() != this) {
795 rv.Throw(NS_ERROR_INVALID_ARG);
796 return nullptr;
799 // This option is not exposed to UA Widgets
800 ElementCreationOptionsOrString options;
802 RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv);
803 if (rv.Failed()) {
804 return nullptr;
807 return aParentNode.AppendChild(*node, rv);
810 void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) {
811 // Need to null-check the host because we may be unlinked already.
812 MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost());
814 HTMLSlotElement* slot = aChild.GetAssignedSlot();
815 if (!slot) {
816 return;
819 MOZ_DIAGNOSTIC_ASSERT(!aChild.IsRootOfNativeAnonymousSubtree(),
820 "How did aChild end up assigned to a slot?");
821 // If the slot is going to start showing fallback content, we need to tell
822 // layout about it.
823 if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) {
824 InvalidateStyleAndLayoutOnSubtree(slot);
827 slot->RemoveAssignedNode(aChild);
828 slot->EnqueueSlotChangeEvent();
830 if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
831 MaybeReassignMainSummary(SummaryChangeReason::Deletion);
835 void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
836 MOZ_ASSERT(aChild.GetParent() == GetHost());
837 // Check to ensure that the child not an anonymous subtree root because even
838 // though its parent could be the host it may not be in the host's child
839 // list.
840 if (aChild.IsRootOfNativeAnonymousSubtree()) {
841 return;
844 if (!aChild.IsSlotable()) {
845 return;
848 if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
849 MaybeReassignMainSummary(SummaryChangeReason::Insertion);
852 SlotInsertionPoint assignment = SlotInsertionPointFor(aChild);
853 if (!assignment.mSlot) {
854 return;
857 // Fallback content will go away, let layout know.
858 if (assignment.mSlot->AssignedNodes().IsEmpty() &&
859 assignment.mSlot->HasChildren()) {
860 InvalidateStyleAndLayoutOnSubtree(assignment.mSlot);
863 if (assignment.mIndex) {
864 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild);
865 } else {
866 assignment.mSlot->AppendAssignedNode(aChild);
868 assignment.mSlot->EnqueueSlotChangeEvent();
871 ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() {
872 if (!mStyleRuleMap) {
873 mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>();
875 mStyleRuleMap->EnsureTable(*this);
876 return *mStyleRuleMap;
879 nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
880 *aResult = nullptr;
881 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
884 void ShadowRoot::SetHTMLUnsafe(const nsAString& aHTML) {
885 RefPtr<Element> host = GetHost();
886 nsContentUtils::SetHTMLUnsafe(this, host, aHTML);