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/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 "nsContentUtils.h"
15 #include "nsGkAtoms.h"
16 
NS_NewHTMLSlotElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,mozilla::dom::FromParser aFromParser)17 nsGenericHTMLElement* NS_NewHTMLSlotElement(
18     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
19     mozilla::dom::FromParser aFromParser) {
20   RefPtr<mozilla::dom::NodeInfo> nodeInfo(std::move(aNodeInfo));
21   auto* nim = nodeInfo->NodeInfoManager();
22   return new (nim) mozilla::dom::HTMLSlotElement(nodeInfo.forget());
23 }
24 
25 namespace mozilla::dom {
26 
HTMLSlotElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)27 HTMLSlotElement::HTMLSlotElement(
28     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
29     : nsGenericHTMLElement(std::move(aNodeInfo)) {}
30 
~HTMLSlotElement()31 HTMLSlotElement::~HTMLSlotElement() {
32   for (const auto& node : mManuallyAssignedNodes) {
33     MOZ_ASSERT(node->AsContent()->GetManualSlotAssignment() == this);
34     node->AsContent()->SetManualSlotAssignment(nullptr);
35   }
36 }
37 
NS_IMPL_ADDREF_INHERITED(HTMLSlotElement,nsGenericHTMLElement)38 NS_IMPL_ADDREF_INHERITED(HTMLSlotElement, nsGenericHTMLElement)
39 NS_IMPL_RELEASE_INHERITED(HTMLSlotElement, nsGenericHTMLElement)
40 
41 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSlotElement, nsGenericHTMLElement,
42                                    mAssignedNodes)
43 
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLSlotElement)
45 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
46 
47 NS_IMPL_ELEMENT_CLONE(HTMLSlotElement)
48 
49 nsresult HTMLSlotElement::BindToTree(BindContext& aContext, nsINode& aParent) {
50   RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
51 
52   nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
53   NS_ENSURE_SUCCESS(rv, rv);
54 
55   ShadowRoot* containingShadow = GetContainingShadow();
56   mInManualShadowRoot =
57       containingShadow &&
58       containingShadow->SlotAssignment() == SlotAssignmentMode::Manual;
59   if (containingShadow && !oldContainingShadow) {
60     containingShadow->AddSlot(this);
61   }
62 
63   return NS_OK;
64 }
65 
UnbindFromTree(bool aNullParent)66 void HTMLSlotElement::UnbindFromTree(bool aNullParent) {
67   RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
68 
69   nsGenericHTMLElement::UnbindFromTree(aNullParent);
70 
71   if (oldContainingShadow && !GetContainingShadow()) {
72     oldContainingShadow->RemoveSlot(this);
73   }
74 }
75 
BeforeSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)76 nsresult HTMLSlotElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
77                                         const nsAttrValueOrString* aValue,
78                                         bool aNotify) {
79   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) {
80     if (ShadowRoot* containingShadow = GetContainingShadow()) {
81       containingShadow->RemoveSlot(this);
82     }
83   }
84 
85   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
86                                              aNotify);
87 }
88 
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)89 nsresult HTMLSlotElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
90                                        const nsAttrValue* aValue,
91                                        const nsAttrValue* aOldValue,
92                                        nsIPrincipal* aSubjectPrincipal,
93                                        bool aNotify) {
94   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) {
95     if (ShadowRoot* containingShadow = GetContainingShadow()) {
96       containingShadow->AddSlot(this);
97     }
98   }
99 
100   return nsGenericHTMLElement::AfterSetAttr(
101       aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
102 }
103 
104 /**
105  * Flatten assigned nodes given a slot, as in:
106  * https://dom.spec.whatwg.org/#find-flattened-slotables
107  */
FlattenAssignedNodes(HTMLSlotElement * aSlot,nsTArray<RefPtr<nsINode>> & aNodes)108 static void FlattenAssignedNodes(HTMLSlotElement* aSlot,
109                                  nsTArray<RefPtr<nsINode>>& aNodes) {
110   if (!aSlot->GetContainingShadow()) {
111     return;
112   }
113 
114   const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
115 
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()) {
121         continue;
122       }
123 
124       if (auto* slot = HTMLSlotElement::FromNode(child)) {
125         FlattenAssignedNodes(slot, aNodes);
126       } else {
127         aNodes.AppendElement(child);
128       }
129     }
130     return;
131   }
132 
133   for (const RefPtr<nsINode>& assignedNode : assignedNodes) {
134     auto* slot = HTMLSlotElement::FromNode(assignedNode);
135     if (slot && slot->GetContainingShadow()) {
136       FlattenAssignedNodes(slot, aNodes);
137     } else {
138       aNodes.AppendElement(assignedNode);
139     }
140   }
141 }
142 
AssignedNodes(const AssignedNodesOptions & aOptions,nsTArray<RefPtr<nsINode>> & aNodes)143 void HTMLSlotElement::AssignedNodes(const AssignedNodesOptions& aOptions,
144                                     nsTArray<RefPtr<nsINode>>& aNodes) {
145   if (aOptions.mFlatten) {
146     return FlattenAssignedNodes(this, aNodes);
147   }
148 
149   aNodes = mAssignedNodes.Clone();
150 }
151 
AssignedElements(const AssignedNodesOptions & aOptions,nsTArray<RefPtr<Element>> & aElements)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());
159     }
160   }
161 }
162 
AssignedNodes() const163 const nsTArray<RefPtr<nsINode>>& HTMLSlotElement::AssignedNodes() const {
164   return mAssignedNodes;
165 }
166 
ManuallyAssignedNodes() const167 const nsTArray<nsINode*>& HTMLSlotElement::ManuallyAssignedNodes() const {
168   return mManuallyAssignedNodes;
169 }
170 
Assign(const Sequence<OwningElementOrText> & aNodes)171 void HTMLSlotElement::Assign(const Sequence<OwningElementOrText>& aNodes) {
172   MOZ_ASSERT(StaticPrefs::dom_shadowdom_slot_assign_enabled());
173   nsAutoScriptBlocker scriptBlocker;
174 
175   // no-op if the input nodes and the assigned nodes are identical
176   // This also works if the two 'assign' calls are like
177   //   > slot.assign(node1, node2);
178   //   > slot.assign(node1, node2, node1, node2);
179   if (!mAssignedNodes.IsEmpty() && aNodes.Length() >= mAssignedNodes.Length()) {
180     nsTHashMap<nsPtrHashKey<nsIContent>, size_t> nodeIndexMap;
181     for (size_t i = 0; i < aNodes.Length(); ++i) {
182       nsIContent* content;
183       if (aNodes[i].IsElement()) {
184         content = aNodes[i].GetAsElement();
185       } else {
186         content = aNodes[i].GetAsText();
187       }
188       MOZ_ASSERT(content);
189       // We only care about the first index this content appears
190       // in the array
191       nodeIndexMap.LookupOrInsert(content, i);
192     }
193 
194     if (nodeIndexMap.Count() == mAssignedNodes.Length()) {
195       bool isIdentical = true;
196       for (size_t i = 0; i < mAssignedNodes.Length(); ++i) {
197         size_t indexInInputNodes;
198         if (!nodeIndexMap.Get(mAssignedNodes[i]->AsContent(),
199                               &indexInInputNodes) ||
200             indexInInputNodes != i) {
201           isIdentical = false;
202           break;
203         }
204       }
205       if (isIdentical) {
206         return;
207       }
208     }
209   }
210 
211   // 1. For each node of this's manually assigned nodes, set node's manual slot
212   // assignment to null.
213   for (nsINode* node : mManuallyAssignedNodes) {
214     MOZ_ASSERT(node->AsContent()->GetManualSlotAssignment() == this);
215     node->AsContent()->SetManualSlotAssignment(nullptr);
216   }
217 
218   // 2. Let nodesSet be a new ordered set.
219   mManuallyAssignedNodes.Clear();
220 
221   nsIContent* host = nullptr;
222   ShadowRoot* root = GetContainingShadow();
223 
224   // An optimization to keep track which slots need to enqueue
225   // slotchange event, such that they can be enqueued later in
226   // tree order.
227   nsTHashSet<RefPtr<HTMLSlotElement>> changedSlots;
228 
229   // Clear out existing assigned nodes
230   if (mInManualShadowRoot) {
231     if (!mAssignedNodes.IsEmpty()) {
232       changedSlots.EnsureInserted(this);
233       if (root) {
234         root->InvalidateStyleAndLayoutOnSubtree(this);
235       }
236       ClearAssignedNodes();
237     }
238 
239     MOZ_ASSERT(mAssignedNodes.IsEmpty());
240     host = GetContainingShadowHost();
241   }
242 
243   for (const OwningElementOrText& elementOrText : aNodes) {
244     nsIContent* content;
245     if (elementOrText.IsElement()) {
246       content = elementOrText.GetAsElement();
247     } else {
248       content = elementOrText.GetAsText();
249     }
250 
251     MOZ_ASSERT(content);
252     // XXXsmaug Should we have a helper for
253     //         https://infra.spec.whatwg.org/#ordered-set?
254     if (content->GetManualSlotAssignment() != this) {
255       if (HTMLSlotElement* oldSlot = content->GetAssignedSlot()) {
256         if (changedSlots.EnsureInserted(oldSlot)) {
257           if (root) {
258             MOZ_ASSERT(oldSlot->GetContainingShadow() == root);
259             root->InvalidateStyleAndLayoutOnSubtree(oldSlot);
260           }
261         }
262       }
263 
264       if (changedSlots.EnsureInserted(this)) {
265         if (root) {
266           root->InvalidateStyleAndLayoutOnSubtree(this);
267         }
268       }
269       // 3.1 (HTML Spec) If content's manual slot assignment refers to a slot,
270       // then remove node from that slot's manually assigned nodes. 3.2 (HTML
271       // Spec) Set content's manual slot assignment to this.
272       if (HTMLSlotElement* oldSlot = content->GetManualSlotAssignment()) {
273         oldSlot->RemoveManuallyAssignedNode(*content);
274       }
275       content->SetManualSlotAssignment(this);
276       mManuallyAssignedNodes.AppendElement(content);
277 
278       if (root && host && content->GetParent() == host) {
279         // Equivalent to 4.2.2.4.3 (DOM Spec) `Set slot's assigned nodes to
280         // slottables`
281         root->MaybeReassignContent(*content);
282       }
283     }
284   }
285 
286   // The `assign slottables` step is completed already at this point,
287   // however we haven't fired the `slotchange` event yet because this
288   // needs to be done in tree order.
289   if (root) {
290     for (nsIContent* child = root->GetFirstChild(); child;
291          child = child->GetNextNode()) {
292       if (HTMLSlotElement* slot = HTMLSlotElement::FromNode(child)) {
293         if (changedSlots.EnsureRemoved(slot)) {
294           slot->EnqueueSlotChangeEvent();
295         }
296       }
297     }
298     MOZ_ASSERT(changedSlots.IsEmpty());
299   }
300 }
301 
InsertAssignedNode(uint32_t aIndex,nsIContent & aNode)302 void HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsIContent& aNode) {
303   MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
304   mAssignedNodes.InsertElementAt(aIndex, &aNode);
305   aNode.SetAssignedSlot(this);
306   SlotAssignedNodeChanged(this, aNode);
307 }
308 
AppendAssignedNode(nsIContent & aNode)309 void HTMLSlotElement::AppendAssignedNode(nsIContent& aNode) {
310   MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
311   mAssignedNodes.AppendElement(&aNode);
312   aNode.SetAssignedSlot(this);
313   SlotAssignedNodeChanged(this, aNode);
314 }
315 
RemoveAssignedNode(nsIContent & aNode)316 void HTMLSlotElement::RemoveAssignedNode(nsIContent& aNode) {
317   // This one runs from unlinking, so we can't guarantee that the slot pointer
318   // hasn't been cleared.
319   MOZ_ASSERT(!aNode.GetAssignedSlot() || aNode.GetAssignedSlot() == this,
320              "How exactly?");
321   mAssignedNodes.RemoveElement(&aNode);
322   aNode.SetAssignedSlot(nullptr);
323   SlotAssignedNodeChanged(this, aNode);
324 }
325 
ClearAssignedNodes()326 void HTMLSlotElement::ClearAssignedNodes() {
327   for (RefPtr<nsINode>& node : mAssignedNodes) {
328     MOZ_ASSERT(!node->AsContent()->GetAssignedSlot() ||
329                    node->AsContent()->GetAssignedSlot() == this,
330                "How exactly?");
331     node->AsContent()->SetAssignedSlot(nullptr);
332   }
333 
334   mAssignedNodes.Clear();
335 }
336 
EnqueueSlotChangeEvent()337 void HTMLSlotElement::EnqueueSlotChangeEvent() {
338   if (mInSignalSlotList) {
339     return;
340   }
341 
342   // FIXME(bug 1459704): Need to figure out how to deal with microtasks posted
343   // during shutdown.
344   if (gXPCOMThreadsShutDown) {
345     return;
346   }
347 
348   DocGroup* docGroup = OwnerDoc()->GetDocGroup();
349   if (!docGroup) {
350     return;
351   }
352 
353   mInSignalSlotList = true;
354   docGroup->SignalSlotChange(*this);
355 }
356 
FireSlotChangeEvent()357 void HTMLSlotElement::FireSlotChangeEvent() {
358   nsContentUtils::DispatchTrustedEvent(
359       OwnerDoc(), static_cast<nsIContent*>(this), u"slotchange"_ns,
360       CanBubble::eYes, Cancelable::eNo);
361 }
362 
RemoveManuallyAssignedNode(nsIContent & aNode)363 void HTMLSlotElement::RemoveManuallyAssignedNode(nsIContent& aNode) {
364   mManuallyAssignedNodes.RemoveElement(&aNode);
365   RemoveAssignedNode(aNode);
366 }
367 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)368 JSObject* HTMLSlotElement::WrapNode(JSContext* aCx,
369                                     JS::Handle<JSObject*> aGivenProto) {
370   return HTMLSlotElement_Binding::Wrap(aCx, this, aGivenProto);
371 }
372 
373 }  // namespace mozilla::dom
374