no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / html / HTMLSlotElement.cpp
blob18d65c2c8a69fa1376f64545ab7b97999fd54c09
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,
43 mAssignedNodes)
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();
57 mInManualShadowRoot =
58 containingShadow &&
59 containingShadow->SlotAssignment() == SlotAssignmentMode::Manual;
60 if (containingShadow && !oldContainingShadow) {
61 containingShadow->AddSlot(this);
64 return NS_OK;
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,
86 aNotify);
89 void 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);
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()) {
111 return;
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()) {
121 continue;
124 if (auto* slot = HTMLSlotElement::FromNode(child)) {
125 FlattenAssignedNodes(slot, aNodes);
126 } else {
127 aNodes.AppendElement(child);
130 return;
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);
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) {
181 nsIContent* content;
182 if (aNodes[i].IsElement()) {
183 content = aNodes[i].GetAsElement();
184 } else {
185 content = aNodes[i].GetAsText();
187 MOZ_ASSERT(content);
188 // We only care about the first index this content appears
189 // in the array
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) {
200 isIdentical = false;
201 break;
204 if (isIdentical) {
205 return;
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
225 // tree order.
226 nsTHashSet<RefPtr<HTMLSlotElement>> changedSlots;
228 // Clear out existing assigned nodes
229 if (mInManualShadowRoot) {
230 if (!mAssignedNodes.IsEmpty()) {
231 changedSlots.EnsureInserted(this);
232 if (root) {
233 root->InvalidateStyleAndLayoutOnSubtree(this);
235 ClearAssignedNodes();
238 MOZ_ASSERT(mAssignedNodes.IsEmpty());
239 host = GetContainingShadowHost();
242 for (const OwningElementOrText& elementOrText : aNodes) {
243 nsIContent* content;
244 if (elementOrText.IsElement()) {
245 content = elementOrText.GetAsElement();
246 } else {
247 content = elementOrText.GetAsText();
250 MOZ_ASSERT(content);
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)) {
256 if (root) {
257 MOZ_ASSERT(oldSlot->GetContainingShadow() == root);
258 root->InvalidateStyleAndLayoutOnSubtree(oldSlot);
263 if (changedSlots.EnsureInserted(this)) {
264 if (root) {
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
279 // slottables`
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.
288 if (root) {
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,
319 "How exactly?");
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,
329 "How exactly?");
330 node->AsContent()->SetAssignedSlot(nullptr);
333 mAssignedNodes.Clear();
336 void HTMLSlotElement::EnqueueSlotChangeEvent() {
337 if (mInSignalSlotList) {
338 return;
341 // FIXME(bug 1459704): Need to figure out how to deal with microtasks posted
342 // during shutdown.
343 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
344 return;
347 DocGroup* docGroup = OwnerDoc()->GetDocGroup();
348 if (!docGroup) {
349 return;
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