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/PresShell.h"
8 #include "mozilla/dom/DocGroup.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/HTMLSlotElement.h"
11 #include "mozilla/dom/HTMLUnknownElement.h"
12 #include "mozilla/dom/ShadowRoot.h"
13 #include "mozilla/dom/Text.h"
14 #include "mozilla/AppShutdown.h"
15 #include "nsContentUtils.h"
16 #include "nsGkAtoms.h"
18 nsGenericHTMLElement
* NS_NewHTMLSlotElement(
19 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
20 mozilla::dom::FromParser aFromParser
) {
21 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(std::move(aNodeInfo
));
22 auto* nim
= nodeInfo
->NodeInfoManager();
23 return new (nim
) mozilla::dom::HTMLSlotElement(nodeInfo
.forget());
26 namespace mozilla::dom
{
28 HTMLSlotElement::HTMLSlotElement(
29 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
30 : nsGenericHTMLElement(std::move(aNodeInfo
)) {}
32 HTMLSlotElement::~HTMLSlotElement() {
33 for (const auto& node
: mManuallyAssignedNodes
) {
34 MOZ_ASSERT(node
->AsContent()->GetManualSlotAssignment() == this);
35 node
->AsContent()->SetManualSlotAssignment(nullptr);
39 NS_IMPL_ADDREF_INHERITED(HTMLSlotElement
, nsGenericHTMLElement
)
40 NS_IMPL_RELEASE_INHERITED(HTMLSlotElement
, nsGenericHTMLElement
)
42 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSlotElement
, nsGenericHTMLElement
,
45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLSlotElement
)
46 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement
)
48 NS_IMPL_ELEMENT_CLONE(HTMLSlotElement
)
50 nsresult
HTMLSlotElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
51 RefPtr
<ShadowRoot
> oldContainingShadow
= GetContainingShadow();
53 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
54 NS_ENSURE_SUCCESS(rv
, rv
);
56 ShadowRoot
* containingShadow
= GetContainingShadow();
59 containingShadow
->SlotAssignment() == SlotAssignmentMode::Manual
;
60 if (containingShadow
&& !oldContainingShadow
) {
61 containingShadow
->AddSlot(this);
67 void HTMLSlotElement::UnbindFromTree(UnbindContext
& aContext
) {
68 RefPtr
<ShadowRoot
> oldContainingShadow
= GetContainingShadow();
70 nsGenericHTMLElement::UnbindFromTree(aContext
);
72 if (oldContainingShadow
&& !GetContainingShadow()) {
73 oldContainingShadow
->RemoveSlot(this);
77 void HTMLSlotElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
78 const nsAttrValue
* aValue
, bool aNotify
) {
79 if (aNameSpaceID
== kNameSpaceID_None
&& aName
== nsGkAtoms::name
) {
80 if (ShadowRoot
* containingShadow
= GetContainingShadow()) {
81 containingShadow
->RemoveSlot(this);
85 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID
, aName
, aValue
,
89 void HTMLSlotElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
90 const nsAttrValue
* aValue
,
91 const nsAttrValue
* aOldValue
,
92 nsIPrincipal
* aSubjectPrincipal
,
94 if (aNameSpaceID
== kNameSpaceID_None
&& aName
== nsGkAtoms::name
) {
95 if (ShadowRoot
* containingShadow
= GetContainingShadow()) {
96 containingShadow
->AddSlot(this);
100 return nsGenericHTMLElement::AfterSetAttr(
101 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
105 * Flatten assigned nodes given a slot, as in:
106 * https://dom.spec.whatwg.org/#find-flattened-slotables
108 static void FlattenAssignedNodes(HTMLSlotElement
* aSlot
,
109 nsTArray
<RefPtr
<nsINode
>>& aNodes
) {
110 if (!aSlot
->GetContainingShadow()) {
114 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
= aSlot
->AssignedNodes();
116 // If assignedNodes is empty, use children of slot as fallback content.
117 if (assignedNodes
.IsEmpty()) {
118 for (nsIContent
* child
= aSlot
->GetFirstChild(); child
;
119 child
= child
->GetNextSibling()) {
120 if (!child
->IsSlotable()) {
124 if (auto* slot
= HTMLSlotElement::FromNode(child
)) {
125 FlattenAssignedNodes(slot
, aNodes
);
127 aNodes
.AppendElement(child
);
133 for (const RefPtr
<nsINode
>& assignedNode
: assignedNodes
) {
134 auto* slot
= HTMLSlotElement::FromNode(assignedNode
);
135 if (slot
&& slot
->GetContainingShadow()) {
136 FlattenAssignedNodes(slot
, aNodes
);
138 aNodes
.AppendElement(assignedNode
);
143 void HTMLSlotElement::AssignedNodes(const AssignedNodesOptions
& aOptions
,
144 nsTArray
<RefPtr
<nsINode
>>& aNodes
) {
145 if (aOptions
.mFlatten
) {
146 return FlattenAssignedNodes(this, aNodes
);
149 aNodes
= mAssignedNodes
.Clone();
152 void HTMLSlotElement::AssignedElements(const AssignedNodesOptions
& aOptions
,
153 nsTArray
<RefPtr
<Element
>>& aElements
) {
154 AutoTArray
<RefPtr
<nsINode
>, 128> assignedNodes
;
155 AssignedNodes(aOptions
, assignedNodes
);
156 for (const RefPtr
<nsINode
>& assignedNode
: assignedNodes
) {
157 if (assignedNode
->IsElement()) {
158 aElements
.AppendElement(assignedNode
->AsElement());
163 const nsTArray
<RefPtr
<nsINode
>>& HTMLSlotElement::AssignedNodes() const {
164 return mAssignedNodes
;
167 const nsTArray
<nsINode
*>& HTMLSlotElement::ManuallyAssignedNodes() const {
168 return mManuallyAssignedNodes
;
171 void HTMLSlotElement::Assign(const Sequence
<OwningElementOrText
>& aNodes
) {
172 nsAutoScriptBlocker scriptBlocker
;
174 // no-op if the input nodes and the assigned nodes are identical
175 // This also works if the two 'assign' calls are like
176 // > slot.assign(node1, node2);
177 // > slot.assign(node1, node2, node1, node2);
178 if (!mAssignedNodes
.IsEmpty() && aNodes
.Length() >= mAssignedNodes
.Length()) {
179 nsTHashMap
<nsPtrHashKey
<nsIContent
>, size_t> nodeIndexMap
;
180 for (size_t i
= 0; i
< aNodes
.Length(); ++i
) {
182 if (aNodes
[i
].IsElement()) {
183 content
= aNodes
[i
].GetAsElement();
185 content
= aNodes
[i
].GetAsText();
188 // We only care about the first index this content appears
190 nodeIndexMap
.LookupOrInsert(content
, i
);
193 if (nodeIndexMap
.Count() == mAssignedNodes
.Length()) {
194 bool isIdentical
= true;
195 for (size_t i
= 0; i
< mAssignedNodes
.Length(); ++i
) {
196 size_t indexInInputNodes
;
197 if (!nodeIndexMap
.Get(mAssignedNodes
[i
]->AsContent(),
198 &indexInInputNodes
) ||
199 indexInInputNodes
!= i
) {
210 // 1. For each node of this's manually assigned nodes, set node's manual slot
211 // assignment to null.
212 for (nsINode
* node
: mManuallyAssignedNodes
) {
213 MOZ_ASSERT(node
->AsContent()->GetManualSlotAssignment() == this);
214 node
->AsContent()->SetManualSlotAssignment(nullptr);
217 // 2. Let nodesSet be a new ordered set.
218 mManuallyAssignedNodes
.Clear();
220 nsIContent
* host
= nullptr;
221 ShadowRoot
* root
= GetContainingShadow();
223 // An optimization to keep track which slots need to enqueue
224 // slotchange event, such that they can be enqueued later in
226 nsTHashSet
<RefPtr
<HTMLSlotElement
>> changedSlots
;
228 // Clear out existing assigned nodes
229 if (mInManualShadowRoot
) {
230 if (!mAssignedNodes
.IsEmpty()) {
231 changedSlots
.EnsureInserted(this);
233 root
->InvalidateStyleAndLayoutOnSubtree(this);
235 ClearAssignedNodes();
238 MOZ_ASSERT(mAssignedNodes
.IsEmpty());
239 host
= GetContainingShadowHost();
242 for (const OwningElementOrText
& elementOrText
: aNodes
) {
244 if (elementOrText
.IsElement()) {
245 content
= elementOrText
.GetAsElement();
247 content
= elementOrText
.GetAsText();
251 // XXXsmaug Should we have a helper for
252 // https://infra.spec.whatwg.org/#ordered-set?
253 if (content
->GetManualSlotAssignment() != this) {
254 if (HTMLSlotElement
* oldSlot
= content
->GetAssignedSlot()) {
255 if (changedSlots
.EnsureInserted(oldSlot
)) {
257 MOZ_ASSERT(oldSlot
->GetContainingShadow() == root
);
258 root
->InvalidateStyleAndLayoutOnSubtree(oldSlot
);
263 if (changedSlots
.EnsureInserted(this)) {
265 root
->InvalidateStyleAndLayoutOnSubtree(this);
268 // 3.1 (HTML Spec) If content's manual slot assignment refers to a slot,
269 // then remove node from that slot's manually assigned nodes. 3.2 (HTML
270 // Spec) Set content's manual slot assignment to this.
271 if (HTMLSlotElement
* oldSlot
= content
->GetManualSlotAssignment()) {
272 oldSlot
->RemoveManuallyAssignedNode(*content
);
274 content
->SetManualSlotAssignment(this);
275 mManuallyAssignedNodes
.AppendElement(content
);
277 if (root
&& host
&& content
->GetParent() == host
) {
278 // Equivalent to 4.2.2.4.3 (DOM Spec) `Set slot's assigned nodes to
280 root
->MaybeReassignContent(*content
);
285 // The `assign slottables` step is completed already at this point,
286 // however we haven't fired the `slotchange` event yet because this
287 // needs to be done in tree order.
289 for (nsIContent
* child
= root
->GetFirstChild(); child
;
290 child
= child
->GetNextNode()) {
291 if (HTMLSlotElement
* slot
= HTMLSlotElement::FromNode(child
)) {
292 if (changedSlots
.EnsureRemoved(slot
)) {
293 slot
->EnqueueSlotChangeEvent();
297 MOZ_ASSERT(changedSlots
.IsEmpty());
301 void HTMLSlotElement::InsertAssignedNode(uint32_t aIndex
, nsIContent
& aNode
) {
302 MOZ_ASSERT(!aNode
.GetAssignedSlot(), "Losing track of a slot");
303 mAssignedNodes
.InsertElementAt(aIndex
, &aNode
);
304 aNode
.SetAssignedSlot(this);
305 SlotAssignedNodeChanged(this, aNode
);
308 void HTMLSlotElement::AppendAssignedNode(nsIContent
& aNode
) {
309 MOZ_ASSERT(!aNode
.GetAssignedSlot(), "Losing track of a slot");
310 mAssignedNodes
.AppendElement(&aNode
);
311 aNode
.SetAssignedSlot(this);
312 SlotAssignedNodeChanged(this, aNode
);
315 void HTMLSlotElement::RemoveAssignedNode(nsIContent
& aNode
) {
316 // This one runs from unlinking, so we can't guarantee that the slot pointer
317 // hasn't been cleared.
318 MOZ_ASSERT(!aNode
.GetAssignedSlot() || aNode
.GetAssignedSlot() == this,
320 mAssignedNodes
.RemoveElement(&aNode
);
321 aNode
.SetAssignedSlot(nullptr);
322 SlotAssignedNodeChanged(this, aNode
);
325 void HTMLSlotElement::ClearAssignedNodes() {
326 for (RefPtr
<nsINode
>& node
: mAssignedNodes
) {
327 MOZ_ASSERT(!node
->AsContent()->GetAssignedSlot() ||
328 node
->AsContent()->GetAssignedSlot() == this,
330 node
->AsContent()->SetAssignedSlot(nullptr);
333 mAssignedNodes
.Clear();
336 void HTMLSlotElement::EnqueueSlotChangeEvent() {
337 if (mInSignalSlotList
) {
341 // FIXME(bug 1459704): Need to figure out how to deal with microtasks posted
343 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
347 DocGroup
* docGroup
= OwnerDoc()->GetDocGroup();
352 mInSignalSlotList
= true;
353 docGroup
->SignalSlotChange(*this);
356 void HTMLSlotElement::FireSlotChangeEvent() {
357 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u
"slotchange"_ns
,
358 CanBubble::eYes
, Cancelable::eNo
);
361 void HTMLSlotElement::RemoveManuallyAssignedNode(nsIContent
& aNode
) {
362 mManuallyAssignedNodes
.RemoveElement(&aNode
);
363 RemoveAssignedNode(aNode
);
366 JSObject
* HTMLSlotElement::WrapNode(JSContext
* aCx
,
367 JS::Handle
<JSObject
*> aGivenProto
) {
368 return HTMLSlotElement_Binding::Wrap(aCx
, this, aGivenProto
);
371 } // namespace mozilla::dom