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