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"
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),
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));
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
88 SetIsNativeAnonymousRoot();
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()) {
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);
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
);
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!");
199 // It is possible that we've been unlinked already. In such case host
200 // should have called Unbind and ShadowRoot's own unlink.
208 void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element
* aElement
) {
209 MOZ_ASSERT(aElement
);
210 Document
* doc
= GetComposedDoc();
215 PresShell
* presShell
= doc
->GetPresShell();
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
) {
238 // Note that if name attribute missing, the slot is a default slot.
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
249 if (index
!= 0 && SlotAssignment() == SlotAssignmentMode::Named
) {
253 HTMLSlotElement
* oldSlot
= currentSlots
->SafeElementAt(1);
254 if (SlotAssignment() == SlotAssignmentMode::Named
) {
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
);
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
)) {
286 doEnqueueSlotChange
= true;
287 aSlot
->AppendAssignedNode(*child
);
290 if (doEnqueueSlotChange
) {
291 aSlot
->EnqueueSlotChangeEvent();
292 SlotStateChanged(aSlot
);
296 bool doEnqueueSlotChange
= false;
297 for (const auto& node
: aSlot
->ManuallyAssignedNodes()) {
298 if (GetHost() != node
->GetParent()) {
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
) {
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();
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
) {
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()) {
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()) {
383 MOZ_ASSERT(mServoStyles
);
385 mStyleRuleMap
->RuleAdded(aSheet
, aRule
);
388 if (aRule
.IsIncompleteImportRule()) {
392 Servo_AuthorStyles_ForceDirty(mServoStyles
.get());
393 ApplicableRulesChanged();
396 void ShadowRoot::RuleRemoved(StyleSheet
& aSheet
, css::Rule
& aRule
) {
397 if (!aSheet
.IsApplicable()) {
401 MOZ_ASSERT(mServoStyles
);
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()) {
415 MOZ_ASSERT(mServoStyles
);
416 Servo_AuthorStyles_ForceDirty(mServoStyles
.get());
417 ApplicableRulesChanged();
420 void ShadowRoot::ImportRuleLoaded(CSSImportRule
&, StyleSheet
& aSheet
) {
422 mStyleRuleMap
->SheetAdded(aSheet
);
425 if (!aSheet
.IsApplicable()) {
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
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
) {
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
)) {
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
);
480 mServoStyles
.reset(Servo_AuthorStyles_Create());
484 mStyleRuleMap
->SheetAdded(aSheet
);
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()) {
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
)) {
503 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles
.get(), &aSheet
,
508 if (mAdoptedStyleSheets
.IsEmpty() || &aList
== &mAdoptedStyleSheets
) {
509 Servo_AuthorStyles_AppendStyleSheet(mServoStyles
.get(), &aSheet
);
513 if (auto* before
= FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets
)) {
514 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles
.get(), &aSheet
,
517 Servo_AuthorStyles_AppendStyleSheet(mServoStyles
.get(), &aSheet
);
521 // FIXME(emilio): This needs to notify document observers and such,
523 void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet
& aSheet
) {
524 auto& sheetList
= aSheet
.IsConstructed() ? mAdoptedStyleSheets
: mStyleSheets
;
525 int32_t index
= sheetList
.LastIndexOf(&aSheet
);
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
532 MOZ_DIAGNOSTIC_ASSERT(aSheet
.GetParentSheet(),
533 "It'd better be an @import sheet");
536 if (aSheet
.IsApplicable()) {
537 InsertSheetIntoAuthorData(size_t(index
), aSheet
, sheetList
);
539 MOZ_ASSERT(mServoStyles
);
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
);
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
);
561 entry
->AddIdElement(aElement
);
565 void ShadowRoot::RemoveFromIdTable(Element
* aElement
, nsAtom
* aId
) {
566 IdentifierMapEntry
* entry
= mIdentifierMap
.GetEntry(aId
);
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
590 nsCOMPtr
<nsPIDOMWindowOuter
> win
= OwnerDoc()->GetWindow();
591 EventTarget
* parentTarget
= win
&& aVisitor
.mEvent
->mMessage
!= eLoad
592 ? win
->GetParentTarget()
595 aVisitor
.SetParentTarget(parentTarget
, true);
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.
621 // Note that if slot attribute is missing, assign it to the first default
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) {
638 nsAutoString slotName
;
639 GetSlotNameFor(aContent
, slotName
);
641 SlotArray
* slots
= mSlotMap
.Get(slotName
);
645 slot
= (*slots
)->ElementAt(0);
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()};
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
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
)};
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.
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
);
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();
726 oldSlot
->RemoveManuallyAssignedNode(aElementOrText
);
730 if (assignment
.mSlot
) {
731 if (assignment
.mIndex
) {
732 assignment
.mSlot
->InsertAssignedNode(*assignment
.mIndex
, aElementOrText
);
734 assignment
.mSlot
->AppendAssignedNode(aElementOrText
);
736 // Similar as above, HTMLSlotElement::Assign handles enqueuing
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));
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
);
781 RefPtr
<nsINode
> node
= OwnerDoc()->ImportNode(aNode
, aDeep
, rv
);
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
);
799 // This option is not exposed to UA Widgets
800 ElementCreationOptionsOrString options
;
802 RefPtr
<nsINode
> node
= OwnerDoc()->CreateElement(aTagName
, options
, rv
);
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();
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
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
840 if (aChild
.IsRootOfNativeAnonymousSubtree()) {
844 if (!aChild
.IsSlotable()) {
848 if (mIsDetailsShadowTree
&& aChild
.IsHTMLElement(nsGkAtoms::summary
)) {
849 MaybeReassignMainSummary(SummaryChangeReason::Insertion
);
852 SlotInsertionPoint assignment
= SlotInsertionPointFor(aChild
);
853 if (!assignment
.mSlot
) {
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
);
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 {
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
);