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"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsLayoutUtils.h"
17 namespace mozilla::dom
{
19 ExplicitChildIterator::ExplicitChildIterator(const nsIContent
* aParent
,
20 bool aStartAtBeginning
)
23 mDefaultChild(nullptr),
24 mIsFirst(aStartAtBeginning
),
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
) {
33 MOZ_ASSERT(!mDefaultChild
);
36 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
37 mParentAsSlot
->AssignedNodes();
39 mChild
= (mIndexInInserted
< assignedNodes
.Length())
40 ? assignedNodes
[mIndexInInserted
++]->AsContent()
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
53 mDefaultChild
= mDefaultChild
->GetNextSibling();
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).
63 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
64 mParentAsSlot
->AssignedNodes();
65 if (!assignedNodes
.IsEmpty()) {
67 mChild
= assignedNodes
[0]->AsContent();
73 mChild
= mParent
->GetFirstChild();
75 } else if (mChild
) { // in the middle of the child list
76 mChild
= mChild
->GetNextSibling();
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.
88 if (ShadowRoot
* shadow
= mParent
->AsElement()->GetShadowRoot()) {
90 mShadowDOMInvolved
= true;
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;
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.
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
) {
137 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
138 mParentAsSlot
->AssignedNodes();
140 mChild
= (--mIndexInInserted
)
141 ? assignedNodes
[mIndexInInserted
- 1]->AsContent()
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();
155 return mDefaultChild
;
158 mChild
= mChild
->GetPreviousSibling();
159 } else if (mIsFirst
) { // at the beginning of the child list
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).
167 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
168 mParentAsSlot
->AssignedNodes();
169 if (!assignedNodes
.IsEmpty()) {
170 mIndexInInserted
= assignedNodes
.Length();
171 mChild
= assignedNodes
[mIndexInInserted
- 1]->AsContent();
176 mChild
= mParent
->GetLastChild();
186 nsIContent
* AllChildrenIterator::Get() const {
189 Element
* marker
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
190 MOZ_ASSERT(marker
, "No content marker frame at eAtMarkerKid phase");
195 Element
* before
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
196 MOZ_ASSERT(before
, "No content before frame at eAtBeforeKid phase");
200 case eAtExplicitKids
:
201 return ExplicitChildIterator::Get();
204 return mAnonKids
[mAnonKidsIdx
];
207 Element
* after
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
208 MOZ_ASSERT(after
, "No content after frame at eAtAfterKid phase");
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
;
226 if (mPhase
== eAtBeforeKid
) {
227 mPhase
= eAtExplicitKids
;
228 Element
* beforePseudo
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
229 if (beforePseudo
&& beforePseudo
== aChildToFind
) {
230 mPhase
= eAtBeforeKid
;
235 if (mPhase
== eAtExplicitKids
) {
236 if (ExplicitChildIterator::Seek(aChildToFind
)) {
239 mPhase
= eAtAnonKids
;
242 nsIContent
* child
= nullptr;
244 child
= GetNextChild();
245 } while (child
&& child
!= aChildToFind
);
247 return child
== aChildToFind
;
250 void AllChildrenIterator::AppendNativeAnonymousChildren() {
251 nsContentUtils::AppendNativeAnonymousChildren(mOriginalContent
, mAnonKids
,
255 nsIContent
* AllChildrenIterator::GetNextChild() {
256 if (mPhase
== eAtBegin
) {
257 Element
* markerContent
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
259 mPhase
= eAtMarkerKid
;
260 return markerContent
;
264 if (mPhase
== eAtBegin
|| mPhase
== eAtMarkerKid
) {
265 mPhase
= eAtExplicitKids
;
266 Element
* beforeContent
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
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();
283 mPhase
= eAtAnonKids
;
286 if (mPhase
== eAtAnonKids
) {
287 if (mAnonKids
.IsEmpty()) {
288 MOZ_ASSERT(mAnonKidsIdx
== UINT32_MAX
);
289 AppendNativeAnonymousChildren();
292 if (mAnonKidsIdx
== UINT32_MAX
) {
299 if (mAnonKidsIdx
< mAnonKids
.Length()) {
300 return mAnonKids
[mAnonKidsIdx
];
303 Element
* afterContent
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
305 mPhase
= eAtAfterKid
;
314 nsIContent
* AllChildrenIterator::GetPreviousChild() {
315 if (mPhase
== eAtEnd
) {
316 MOZ_ASSERT(mAnonKidsIdx
== mAnonKids
.Length());
317 mPhase
= eAtAnonKids
;
318 Element
* afterContent
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
320 mPhase
= eAtAfterKid
;
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.
338 if (mAnonKidsIdx
< mAnonKids
.Length()) {
339 return mAnonKids
[mAnonKidsIdx
];
341 mPhase
= eAtExplicitKids
;
344 if (mPhase
== eAtExplicitKids
) {
345 nsIContent
* kid
= ExplicitChildIterator::GetPreviousChild();
350 Element
* beforeContent
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
352 mPhase
= eAtBeforeKid
;
353 return beforeContent
;
357 if (mPhase
== eAtExplicitKids
|| mPhase
== eAtBeforeKid
) {
358 Element
* markerContent
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
360 mPhase
= eAtMarkerKid
;
361 return markerContent
;
369 } // namespace mozilla::dom