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(bool aIgnoreXBL
) {
87 // TODO(emilio): I think it probably makes sense to only allow constructing
88 // FlattenedChildIterators with Element.
89 if (mParent
->IsElement()) {
90 if (ShadowRoot
* shadow
= mParent
->AsElement()->GetShadowRoot()) {
92 mShadowDOMInvolved
= true;
96 mShadowDOMInvolved
= true;
102 bool ExplicitChildIterator::Seek(const nsIContent
* aChildToFind
) {
103 if (aChildToFind
->GetParent() == mParent
&&
104 !aChildToFind
->IsRootOfNativeAnonymousSubtree()) {
105 // Fast path: just point ourselves to aChildToFind, which is a
106 // normal DOM child of ours.
107 mChild
= const_cast<nsIContent
*>(aChildToFind
);
108 mIndexInInserted
= 0;
109 mDefaultChild
= nullptr;
114 // Can we add more fast paths here based on whether the parent of aChildToFind
115 // is a shadow insertion point or content insertion point?
117 // Slow path: just walk all our kids.
118 return Seek(aChildToFind
, nullptr);
121 nsIContent
* ExplicitChildIterator::Get() const {
122 MOZ_ASSERT(!mIsFirst
);
124 // When mParentAsSlot is set, mChild is always set to the current child. It
125 // does not matter whether mChild is an assigned node or a fallback content.
130 if (mIndexInInserted
) {
131 MOZ_ASSERT_UNREACHABLE("This needs to be revisited");
134 return mDefaultChild
? mDefaultChild
: mChild
;
137 nsIContent
* ExplicitChildIterator::GetPreviousChild() {
138 // If we're already in the inserted-children array, look there first
139 if (mIndexInInserted
) {
141 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
142 mParentAsSlot
->AssignedNodes();
144 mChild
= (--mIndexInInserted
)
145 ? assignedNodes
[mIndexInInserted
- 1]->AsContent()
154 MOZ_ASSERT_UNREACHABLE("This needs to be revisited");
155 } else if (mDefaultChild
) {
156 // If we're already in default content, check if there are more nodes there
157 mDefaultChild
= mDefaultChild
->GetPreviousSibling();
159 return mDefaultChild
;
162 mChild
= mChild
->GetPreviousSibling();
163 } else if (mIsFirst
) { // at the beginning of the child list
165 } else if (mChild
) { // in the middle of the child list
166 mChild
= mChild
->GetPreviousSibling();
167 } else { // at the end of the child list
168 // For slot parent, iterate over assigned nodes if not empty, otherwise
169 // fall through and iterate over direct children (fallback content).
171 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
=
172 mParentAsSlot
->AssignedNodes();
173 if (!assignedNodes
.IsEmpty()) {
174 mIndexInInserted
= assignedNodes
.Length();
175 mChild
= assignedNodes
[mIndexInInserted
- 1]->AsContent();
180 mChild
= mParent
->GetLastChild();
190 nsIContent
* AllChildrenIterator::Get() const {
193 Element
* marker
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
194 MOZ_ASSERT(marker
, "No content marker frame at eAtMarkerKid phase");
199 Element
* before
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
200 MOZ_ASSERT(before
, "No content before frame at eAtBeforeKid phase");
204 case eAtExplicitKids
:
205 return ExplicitChildIterator::Get();
208 return mAnonKids
[mAnonKidsIdx
];
211 Element
* after
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
212 MOZ_ASSERT(after
, "No content after frame at eAtAfterKid phase");
221 bool AllChildrenIterator::Seek(const nsIContent
* aChildToFind
) {
222 if (mPhase
== eAtBegin
|| mPhase
== eAtMarkerKid
) {
223 mPhase
= eAtBeforeKid
;
224 Element
* markerPseudo
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
225 if (markerPseudo
&& markerPseudo
== aChildToFind
) {
226 mPhase
= eAtMarkerKid
;
230 if (mPhase
== eAtBeforeKid
) {
231 mPhase
= eAtExplicitKids
;
232 Element
* beforePseudo
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
233 if (beforePseudo
&& beforePseudo
== aChildToFind
) {
234 mPhase
= eAtBeforeKid
;
239 if (mPhase
== eAtExplicitKids
) {
240 if (ExplicitChildIterator::Seek(aChildToFind
)) {
243 mPhase
= eAtAnonKids
;
246 nsIContent
* child
= nullptr;
248 child
= GetNextChild();
249 } while (child
&& child
!= aChildToFind
);
251 return child
== aChildToFind
;
254 void AllChildrenIterator::AppendNativeAnonymousChildren() {
255 nsContentUtils::AppendNativeAnonymousChildren(mOriginalContent
, mAnonKids
,
259 nsIContent
* AllChildrenIterator::GetNextChild() {
260 if (mPhase
== eAtBegin
) {
261 Element
* markerContent
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
263 mPhase
= eAtMarkerKid
;
264 return markerContent
;
268 if (mPhase
== eAtBegin
|| mPhase
== eAtMarkerKid
) {
269 mPhase
= eAtExplicitKids
;
270 Element
* beforeContent
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
272 mPhase
= eAtBeforeKid
;
273 return beforeContent
;
277 if (mPhase
== eAtBeforeKid
) {
278 // Advance into our explicit kids.
279 mPhase
= eAtExplicitKids
;
282 if (mPhase
== eAtExplicitKids
) {
283 nsIContent
* kid
= ExplicitChildIterator::GetNextChild();
287 mPhase
= eAtAnonKids
;
290 if (mPhase
== eAtAnonKids
) {
291 if (mAnonKids
.IsEmpty()) {
292 MOZ_ASSERT(mAnonKidsIdx
== UINT32_MAX
);
293 AppendNativeAnonymousChildren();
296 if (mAnonKidsIdx
== UINT32_MAX
) {
303 if (mAnonKidsIdx
< mAnonKids
.Length()) {
304 return mAnonKids
[mAnonKidsIdx
];
307 Element
* afterContent
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
309 mPhase
= eAtAfterKid
;
318 nsIContent
* AllChildrenIterator::GetPreviousChild() {
319 if (mPhase
== eAtEnd
) {
320 MOZ_ASSERT(mAnonKidsIdx
== mAnonKids
.Length());
321 mPhase
= eAtAnonKids
;
322 Element
* afterContent
= nsLayoutUtils::GetAfterPseudo(mOriginalContent
);
324 mPhase
= eAtAfterKid
;
329 if (mPhase
== eAtAfterKid
) {
330 mPhase
= eAtAnonKids
;
333 if (mPhase
== eAtAnonKids
) {
334 if (mAnonKids
.IsEmpty()) {
335 AppendNativeAnonymousChildren();
336 mAnonKidsIdx
= mAnonKids
.Length();
339 // If 0 then it turns into UINT32_MAX, which indicates the iterator is
340 // before the anonymous children.
342 if (mAnonKidsIdx
< mAnonKids
.Length()) {
343 return mAnonKids
[mAnonKidsIdx
];
345 mPhase
= eAtExplicitKids
;
348 if (mPhase
== eAtExplicitKids
) {
349 nsIContent
* kid
= ExplicitChildIterator::GetPreviousChild();
354 Element
* beforeContent
= nsLayoutUtils::GetBeforePseudo(mOriginalContent
);
356 mPhase
= eAtBeforeKid
;
357 return beforeContent
;
361 if (mPhase
== eAtExplicitKids
|| mPhase
== eAtBeforeKid
) {
362 Element
* markerContent
= nsLayoutUtils::GetMarkerPseudo(mOriginalContent
);
364 mPhase
= eAtMarkerKid
;
365 return markerContent
;
373 } // namespace mozilla::dom