Bug 1750871 - run mochitest-remote on fission everywhere. r=releng-reviewers,aki
[gecko.git] / dom / base / ChildIterator.cpp
blobdc3e67191cf62bd1942f1eb0c92f86ed8c743796
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ChildIterator.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/HTMLSlotElement.h"
11 #include "mozilla/dom/ShadowRoot.h"
12 #include "nsIAnonymousContentCreator.h"
13 #include "nsIFrame.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsLayoutUtils.h"
17 namespace mozilla::dom {
19 ExplicitChildIterator::ExplicitChildIterator(const nsIContent* aParent,
20 bool aStartAtBeginning)
21 : mParent(aParent),
22 mChild(nullptr),
23 mDefaultChild(nullptr),
24 mIsFirst(aStartAtBeginning),
25 mIndexInInserted(0) {
26 mParentAsSlot = HTMLSlotElement::FromNode(mParent);
29 nsIContent* ExplicitChildIterator::GetNextChild() {
30 // If we're already in the inserted-children array, look there first
31 if (mIndexInInserted) {
32 MOZ_ASSERT(mChild);
33 MOZ_ASSERT(!mDefaultChild);
35 if (mParentAsSlot) {
36 const nsTArray<RefPtr<nsINode>>& assignedNodes =
37 mParentAsSlot->AssignedNodes();
39 mChild = (mIndexInInserted < assignedNodes.Length())
40 ? assignedNodes[mIndexInInserted++]->AsContent()
41 : nullptr;
42 if (!mChild) {
43 mIndexInInserted = 0;
45 return mChild;
48 MOZ_ASSERT_UNREACHABLE("This needs to be revisited");
49 } else if (mDefaultChild) {
50 // If we're already in default content, check if there are more nodes there
51 MOZ_ASSERT(mChild);
53 mDefaultChild = mDefaultChild->GetNextSibling();
54 if (mDefaultChild) {
55 return mDefaultChild;
58 mChild = mChild->GetNextSibling();
59 } else if (mIsFirst) { // at the beginning of the child list
60 // For slot parent, iterate over assigned nodes if not empty, otherwise
61 // fall through and iterate over direct children (fallback content).
62 if (mParentAsSlot) {
63 const nsTArray<RefPtr<nsINode>>& assignedNodes =
64 mParentAsSlot->AssignedNodes();
65 if (!assignedNodes.IsEmpty()) {
66 mIndexInInserted = 1;
67 mChild = assignedNodes[0]->AsContent();
68 mIsFirst = false;
69 return mChild;
73 mChild = mParent->GetFirstChild();
74 mIsFirst = false;
75 } else if (mChild) { // in the middle of the child list
76 mChild = mChild->GetNextSibling();
79 return mChild;
82 void FlattenedChildIterator::Init() {
83 if (!mParent->IsElement()) {
84 // TODO(emilio): I think it probably makes sense to only allow constructing
85 // FlattenedChildIterators with Element.
86 return;
88 if (ShadowRoot* shadow = mParent->AsElement()->GetShadowRoot()) {
89 mParent = shadow;
90 mShadowDOMInvolved = true;
91 return;
93 if (mParentAsSlot) {
94 mShadowDOMInvolved = true;
98 bool ExplicitChildIterator::Seek(const nsIContent* aChildToFind) {
99 if (aChildToFind->GetParent() == mParent &&
100 !aChildToFind->IsRootOfNativeAnonymousSubtree()) {
101 // Fast path: just point ourselves to aChildToFind, which is a
102 // normal DOM child of ours.
103 mChild = const_cast<nsIContent*>(aChildToFind);
104 mIndexInInserted = 0;
105 mDefaultChild = nullptr;
106 mIsFirst = false;
107 return true;
110 // Can we add more fast paths here based on whether the parent of aChildToFind
111 // is a shadow insertion point or content insertion point?
113 // Slow path: just walk all our kids.
114 return Seek(aChildToFind, nullptr);
117 nsIContent* ExplicitChildIterator::Get() const {
118 MOZ_ASSERT(!mIsFirst);
120 // When mParentAsSlot is set, mChild is always set to the current child. It
121 // does not matter whether mChild is an assigned node or a fallback content.
122 if (mParentAsSlot) {
123 return mChild;
126 if (mIndexInInserted) {
127 MOZ_ASSERT_UNREACHABLE("This needs to be revisited");
130 return mDefaultChild ? mDefaultChild : mChild;
133 nsIContent* ExplicitChildIterator::GetPreviousChild() {
134 // If we're already in the inserted-children array, look there first
135 if (mIndexInInserted) {
136 if (mParentAsSlot) {
137 const nsTArray<RefPtr<nsINode>>& assignedNodes =
138 mParentAsSlot->AssignedNodes();
140 mChild = (--mIndexInInserted)
141 ? assignedNodes[mIndexInInserted - 1]->AsContent()
142 : nullptr;
144 if (!mChild) {
145 mIsFirst = true;
147 return mChild;
150 MOZ_ASSERT_UNREACHABLE("This needs to be revisited");
151 } else if (mDefaultChild) {
152 // If we're already in default content, check if there are more nodes there
153 mDefaultChild = mDefaultChild->GetPreviousSibling();
154 if (mDefaultChild) {
155 return mDefaultChild;
158 mChild = mChild->GetPreviousSibling();
159 } else if (mIsFirst) { // at the beginning of the child list
160 return nullptr;
161 } else if (mChild) { // in the middle of the child list
162 mChild = mChild->GetPreviousSibling();
163 } else { // at the end of the child list
164 // For slot parent, iterate over assigned nodes if not empty, otherwise
165 // fall through and iterate over direct children (fallback content).
166 if (mParentAsSlot) {
167 const nsTArray<RefPtr<nsINode>>& assignedNodes =
168 mParentAsSlot->AssignedNodes();
169 if (!assignedNodes.IsEmpty()) {
170 mIndexInInserted = assignedNodes.Length();
171 mChild = assignedNodes[mIndexInInserted - 1]->AsContent();
172 return mChild;
176 mChild = mParent->GetLastChild();
179 if (!mChild) {
180 mIsFirst = true;
183 return mChild;
186 nsIContent* AllChildrenIterator::Get() const {
187 switch (mPhase) {
188 case eAtMarkerKid: {
189 Element* marker = nsLayoutUtils::GetMarkerPseudo(mOriginalContent);
190 MOZ_ASSERT(marker, "No content marker frame at eAtMarkerKid phase");
191 return marker;
194 case eAtBeforeKid: {
195 Element* before = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
196 MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
197 return before;
200 case eAtExplicitKids:
201 return ExplicitChildIterator::Get();
203 case eAtAnonKids:
204 return mAnonKids[mAnonKidsIdx];
206 case eAtAfterKid: {
207 Element* after = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
208 MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
209 return after;
212 default:
213 return nullptr;
217 bool AllChildrenIterator::Seek(const nsIContent* aChildToFind) {
218 if (mPhase == eAtBegin || mPhase == eAtMarkerKid) {
219 mPhase = eAtBeforeKid;
220 Element* markerPseudo = nsLayoutUtils::GetMarkerPseudo(mOriginalContent);
221 if (markerPseudo && markerPseudo == aChildToFind) {
222 mPhase = eAtMarkerKid;
223 return true;
226 if (mPhase == eAtBeforeKid) {
227 mPhase = eAtExplicitKids;
228 Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
229 if (beforePseudo && beforePseudo == aChildToFind) {
230 mPhase = eAtBeforeKid;
231 return true;
235 if (mPhase == eAtExplicitKids) {
236 if (ExplicitChildIterator::Seek(aChildToFind)) {
237 return true;
239 mPhase = eAtAnonKids;
242 nsIContent* child = nullptr;
243 do {
244 child = GetNextChild();
245 } while (child && child != aChildToFind);
247 return child == aChildToFind;
250 void AllChildrenIterator::AppendNativeAnonymousChildren() {
251 nsContentUtils::AppendNativeAnonymousChildren(mOriginalContent, mAnonKids,
252 mFlags);
255 nsIContent* AllChildrenIterator::GetNextChild() {
256 if (mPhase == eAtBegin) {
257 Element* markerContent = nsLayoutUtils::GetMarkerPseudo(mOriginalContent);
258 if (markerContent) {
259 mPhase = eAtMarkerKid;
260 return markerContent;
264 if (mPhase == eAtBegin || mPhase == eAtMarkerKid) {
265 mPhase = eAtExplicitKids;
266 Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
267 if (beforeContent) {
268 mPhase = eAtBeforeKid;
269 return beforeContent;
273 if (mPhase == eAtBeforeKid) {
274 // Advance into our explicit kids.
275 mPhase = eAtExplicitKids;
278 if (mPhase == eAtExplicitKids) {
279 nsIContent* kid = ExplicitChildIterator::GetNextChild();
280 if (kid) {
281 return kid;
283 mPhase = eAtAnonKids;
286 if (mPhase == eAtAnonKids) {
287 if (mAnonKids.IsEmpty()) {
288 MOZ_ASSERT(mAnonKidsIdx == UINT32_MAX);
289 AppendNativeAnonymousChildren();
290 mAnonKidsIdx = 0;
291 } else {
292 if (mAnonKidsIdx == UINT32_MAX) {
293 mAnonKidsIdx = 0;
294 } else {
295 mAnonKidsIdx++;
299 if (mAnonKidsIdx < mAnonKids.Length()) {
300 return mAnonKids[mAnonKidsIdx];
303 Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
304 if (afterContent) {
305 mPhase = eAtAfterKid;
306 return afterContent;
310 mPhase = eAtEnd;
311 return nullptr;
314 nsIContent* AllChildrenIterator::GetPreviousChild() {
315 if (mPhase == eAtEnd) {
316 MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
317 mPhase = eAtAnonKids;
318 Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
319 if (afterContent) {
320 mPhase = eAtAfterKid;
321 return afterContent;
325 if (mPhase == eAtAfterKid) {
326 mPhase = eAtAnonKids;
329 if (mPhase == eAtAnonKids) {
330 if (mAnonKids.IsEmpty()) {
331 AppendNativeAnonymousChildren();
332 mAnonKidsIdx = mAnonKids.Length();
335 // If 0 then it turns into UINT32_MAX, which indicates the iterator is
336 // before the anonymous children.
337 --mAnonKidsIdx;
338 if (mAnonKidsIdx < mAnonKids.Length()) {
339 return mAnonKids[mAnonKidsIdx];
341 mPhase = eAtExplicitKids;
344 if (mPhase == eAtExplicitKids) {
345 nsIContent* kid = ExplicitChildIterator::GetPreviousChild();
346 if (kid) {
347 return kid;
350 Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
351 if (beforeContent) {
352 mPhase = eAtBeforeKid;
353 return beforeContent;
357 if (mPhase == eAtExplicitKids || mPhase == eAtBeforeKid) {
358 Element* markerContent = nsLayoutUtils::GetMarkerPseudo(mOriginalContent);
359 if (markerContent) {
360 mPhase = eAtMarkerKid;
361 return markerContent;
365 mPhase = eAtBegin;
366 return nullptr;
369 } // namespace mozilla::dom