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/. */
6
7 #include "ChildIterator.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/HTMLSlotElement.h"
10 #include "mozilla/dom/XBLChildrenElement.h"
11 #include "mozilla/dom/ShadowRoot.h"
12 #include "nsIAnonymousContentCreator.h"
13 #include "nsIFrame.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsDocument.h"
16
17 namespace mozilla {
18 namespace dom {
19
ExplicitChildIterator(const nsIContent * aParent,bool aStartAtBeginning)20 ExplicitChildIterator::ExplicitChildIterator(const nsIContent* aParent,
21 bool aStartAtBeginning)
22 : mParent(aParent),
23 mChild(nullptr),
24 mDefaultChild(nullptr),
25 mIsFirst(aStartAtBeginning),
26 mIndexInInserted(0) {
27 mParentAsSlot = nsDocument::IsShadowDOMEnabled(mParent)
28 ? HTMLSlotElement::FromContent(mParent)
29 : nullptr;
30 }
31
GetNextChild()32 nsIContent* ExplicitChildIterator::GetNextChild() {
33 // If we're already in the inserted-children array, look there first
34 if (mIndexInInserted) {
35 MOZ_ASSERT(mChild);
36 MOZ_ASSERT(!mDefaultChild);
37
38 if (mParentAsSlot) {
39 const nsTArray<RefPtr<nsINode>>& assignedNodes =
40 mParentAsSlot->AssignedNodes();
41
42 mChild = (mIndexInInserted < assignedNodes.Length())
43 ? assignedNodes[mIndexInInserted++]->AsContent()
44 : nullptr;
45 return mChild;
46 }
47
48 MOZ_ASSERT(mChild->IsActiveChildrenElement());
49 auto* childrenElement = static_cast<XBLChildrenElement*>(mChild);
50 if (mIndexInInserted < childrenElement->InsertedChildrenLength()) {
51 return childrenElement->InsertedChild(mIndexInInserted++);
52 }
53 mIndexInInserted = 0;
54 mChild = mChild->GetNextSibling();
55 } else if (mDefaultChild) {
56 // If we're already in default content, check if there are more nodes there
57 MOZ_ASSERT(mChild);
58 MOZ_ASSERT(mChild->IsActiveChildrenElement());
59
60 mDefaultChild = mDefaultChild->GetNextSibling();
61 if (mDefaultChild) {
62 return mDefaultChild;
63 }
64
65 mChild = mChild->GetNextSibling();
66 } else if (mIsFirst) { // at the beginning of the child list
67 // For slot parent, iterate over assigned nodes if not empty, otherwise
68 // fall through and iterate over direct children (fallback content).
69 if (mParentAsSlot) {
70 const nsTArray<RefPtr<nsINode>>& assignedNodes =
71 mParentAsSlot->AssignedNodes();
72 if (!assignedNodes.IsEmpty()) {
73 mIndexInInserted = 1;
74 mChild = assignedNodes[0]->AsContent();
75 mIsFirst = false;
76 return mChild;
77 }
78 }
79
80 mChild = mParent->GetFirstChild();
81 mIsFirst = false;
82 } else if (mChild) { // in the middle of the child list
83 mChild = mChild->GetNextSibling();
84 }
85
86 // Iterate until we find a non-insertion point, or an insertion point with
87 // content.
88 while (mChild) {
89 if (mChild->IsActiveChildrenElement()) {
90 // If the current child being iterated is a content insertion point
91 // then the iterator needs to return the nodes distributed into
92 // the content insertion point.
93 auto* childrenElement = static_cast<XBLChildrenElement*>(mChild);
94 if (childrenElement->HasInsertedChildren()) {
95 // Iterate through elements projected on insertion point.
96 mIndexInInserted = 1;
97 return childrenElement->InsertedChild(0);
98 }
99
100 // Insertion points inside fallback/default content
101 // are considered inactive and do not get assigned nodes.
102 mDefaultChild = mChild->GetFirstChild();
103 if (mDefaultChild) {
104 return mDefaultChild;
105 }
106
107 // If we have an insertion point with no assigned nodes and
108 // no default content, move on to the next node.
109 mChild = mChild->GetNextSibling();
110 } else {
111 // mChild is not an insertion point, thus it is the next node to
112 // return from this iterator.
113 break;
114 }
115 }
116
117 return mChild;
118 }
119
Init(bool aIgnoreXBL)120 void FlattenedChildIterator::Init(bool aIgnoreXBL) {
121 if (aIgnoreXBL) {
122 mXBLInvolved = Some(false);
123 return;
124 }
125
126 // TODO(emilio): I think it probably makes sense to only allow constructing
127 // FlattenedChildIterators with Element.
128 if (mParent->IsElement()) {
129 if (ShadowRoot* shadow = mParent->AsElement()->GetShadowRoot()) {
130 mParent = shadow;
131 mXBLInvolved = Some(true);
132 return;
133 }
134 }
135
136 nsXBLBinding* binding =
137 mParent->OwnerDoc()->BindingManager()->GetBindingWithContent(mParent);
138
139 if (binding) {
140 MOZ_ASSERT(binding->GetAnonymousContent());
141 mParent = binding->GetAnonymousContent();
142 mXBLInvolved = Some(true);
143 }
144 }
145
ComputeWhetherXBLIsInvolved() const146 bool FlattenedChildIterator::ComputeWhetherXBLIsInvolved() const {
147 MOZ_ASSERT(mXBLInvolved.isNothing());
148 // We set mXBLInvolved to true if either the node we're iterating has a
149 // binding with content attached to it (in which case it is handled in Init),
150 // or the node is generated XBL content and has an <xbl:children> child.
151 if (!mParent->GetBindingParent()) {
152 return false;
153 }
154
155 for (nsIContent* child = mParent->GetFirstChild(); child;
156 child = child->GetNextSibling()) {
157 if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
158 MOZ_ASSERT(child->GetBindingParent());
159 return true;
160 }
161 }
162
163 return false;
164 }
165
Seek(const nsIContent * aChildToFind)166 bool ExplicitChildIterator::Seek(const nsIContent* aChildToFind) {
167 if (aChildToFind->GetParent() == mParent &&
168 !aChildToFind->IsRootOfAnonymousSubtree()) {
169 // Fast path: just point ourselves to aChildToFind, which is a
170 // normal DOM child of ours.
171 mChild = const_cast<nsIContent*>(aChildToFind);
172 mIndexInInserted = 0;
173 mDefaultChild = nullptr;
174 mIsFirst = false;
175 MOZ_ASSERT(!mChild->IsActiveChildrenElement());
176 return true;
177 }
178
179 // Can we add more fast paths here based on whether the parent of aChildToFind
180 // is a shadow insertion point or content insertion point?
181
182 // Slow path: just walk all our kids.
183 return Seek(aChildToFind, nullptr);
184 }
185
Get() const186 nsIContent* ExplicitChildIterator::Get() const {
187 MOZ_ASSERT(!mIsFirst);
188
189 // When mParentAsSlot is set, mChild is always set to the current child. It
190 // does not matter whether mChild is an assigned node or a fallback content.
191 if (mParentAsSlot) {
192 return mChild;
193 }
194
195 if (mIndexInInserted) {
196 MOZ_ASSERT(mChild->IsActiveChildrenElement());
197 auto* childrenElement = static_cast<XBLChildrenElement*>(mChild);
198 return childrenElement->InsertedChild(mIndexInInserted - 1);
199 }
200
201 return mDefaultChild ? mDefaultChild : mChild;
202 }
203
GetPreviousChild()204 nsIContent* ExplicitChildIterator::GetPreviousChild() {
205 // If we're already in the inserted-children array, look there first
206 if (mIndexInInserted) {
207 if (mParentAsSlot) {
208 const nsTArray<RefPtr<nsINode>>& assignedNodes =
209 mParentAsSlot->AssignedNodes();
210
211 mChild = (--mIndexInInserted)
212 ? assignedNodes[mIndexInInserted - 1]->AsContent()
213 : nullptr;
214
215 if (!mChild) {
216 mIsFirst = true;
217 }
218 return mChild;
219 }
220
221 // NB: mIndexInInserted points one past the last returned child so we need
222 // to look *two* indices back in order to return the previous child.
223 MOZ_ASSERT(mChild->IsActiveChildrenElement());
224 auto* childrenElement = static_cast<XBLChildrenElement*>(mChild);
225 if (--mIndexInInserted) {
226 return childrenElement->InsertedChild(mIndexInInserted - 1);
227 }
228 mChild = mChild->GetPreviousSibling();
229 } else if (mDefaultChild) {
230 // If we're already in default content, check if there are more nodes there
231 mDefaultChild = mDefaultChild->GetPreviousSibling();
232 if (mDefaultChild) {
233 return mDefaultChild;
234 }
235
236 mChild = mChild->GetPreviousSibling();
237 } else if (mIsFirst) { // at the beginning of the child list
238 return nullptr;
239 } else if (mChild) { // in the middle of the child list
240 mChild = mChild->GetPreviousSibling();
241 } else { // at the end of the child list
242 // For slot parent, iterate over assigned nodes if not empty, otherwise
243 // fall through and iterate over direct children (fallback content).
244 if (mParentAsSlot) {
245 const nsTArray<RefPtr<nsINode>>& assignedNodes =
246 mParentAsSlot->AssignedNodes();
247 if (!assignedNodes.IsEmpty()) {
248 mIndexInInserted = assignedNodes.Length();
249 mChild = assignedNodes[mIndexInInserted - 1]->AsContent();
250 return mChild;
251 }
252 }
253
254 mChild = mParent->GetLastChild();
255 }
256
257 // Iterate until we find a non-insertion point, or an insertion point with
258 // content.
259 while (mChild) {
260 if (mChild->IsActiveChildrenElement()) {
261 // If the current child being iterated is a content insertion point
262 // then the iterator needs to return the nodes distributed into
263 // the content insertion point.
264 auto* childrenElement = static_cast<XBLChildrenElement*>(mChild);
265 if (childrenElement->HasInsertedChildren()) {
266 mIndexInInserted = childrenElement->InsertedChildrenLength();
267 return childrenElement->InsertedChild(mIndexInInserted - 1);
268 }
269
270 mDefaultChild = mChild->GetLastChild();
271 if (mDefaultChild) {
272 return mDefaultChild;
273 }
274
275 mChild = mChild->GetPreviousSibling();
276 } else {
277 // mChild is not an insertion point, thus it is the next node to
278 // return from this iterator.
279 break;
280 }
281 }
282
283 if (!mChild) {
284 mIsFirst = true;
285 }
286
287 return mChild;
288 }
289
Get() const290 nsIContent* AllChildrenIterator::Get() const {
291 switch (mPhase) {
292 case eAtBeforeKid: {
293 Element* before = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
294 MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
295 return before;
296 }
297
298 case eAtExplicitKids:
299 return ExplicitChildIterator::Get();
300
301 case eAtAnonKids:
302 return mAnonKids[mAnonKidsIdx];
303
304 case eAtAfterKid: {
305 Element* after = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
306 MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
307 return after;
308 }
309
310 default:
311 return nullptr;
312 }
313 }
314
Seek(const nsIContent * aChildToFind)315 bool AllChildrenIterator::Seek(const nsIContent* aChildToFind) {
316 if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
317 mPhase = eAtExplicitKids;
318 Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
319 if (beforePseudo && beforePseudo == aChildToFind) {
320 mPhase = eAtBeforeKid;
321 return true;
322 }
323 }
324
325 if (mPhase == eAtExplicitKids) {
326 if (ExplicitChildIterator::Seek(aChildToFind)) {
327 return true;
328 }
329 mPhase = eAtAnonKids;
330 }
331
332 nsIContent* child = nullptr;
333 do {
334 child = GetNextChild();
335 } while (child && child != aChildToFind);
336
337 return child == aChildToFind;
338 }
339
AppendNativeAnonymousChildren()340 void AllChildrenIterator::AppendNativeAnonymousChildren() {
341 nsContentUtils::AppendNativeAnonymousChildren(mOriginalContent, mAnonKids,
342 mFlags);
343 }
344
GetNextChild()345 nsIContent* AllChildrenIterator::GetNextChild() {
346 if (mPhase == eAtBegin) {
347 mPhase = eAtExplicitKids;
348 Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
349 if (beforeContent) {
350 mPhase = eAtBeforeKid;
351 return beforeContent;
352 }
353 }
354
355 if (mPhase == eAtBeforeKid) {
356 // Advance into our explicit kids.
357 mPhase = eAtExplicitKids;
358 }
359
360 if (mPhase == eAtExplicitKids) {
361 nsIContent* kid = ExplicitChildIterator::GetNextChild();
362 if (kid) {
363 return kid;
364 }
365 mPhase = eAtAnonKids;
366 }
367
368 if (mPhase == eAtAnonKids) {
369 if (mAnonKids.IsEmpty()) {
370 MOZ_ASSERT(mAnonKidsIdx == UINT32_MAX);
371 AppendNativeAnonymousChildren();
372 mAnonKidsIdx = 0;
373 } else {
374 if (mAnonKidsIdx == UINT32_MAX) {
375 mAnonKidsIdx = 0;
376 } else {
377 mAnonKidsIdx++;
378 }
379 }
380
381 if (mAnonKidsIdx < mAnonKids.Length()) {
382 return mAnonKids[mAnonKidsIdx];
383 }
384
385 Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
386 if (afterContent) {
387 mPhase = eAtAfterKid;
388 return afterContent;
389 }
390 }
391
392 mPhase = eAtEnd;
393 return nullptr;
394 }
395
GetPreviousChild()396 nsIContent* AllChildrenIterator::GetPreviousChild() {
397 if (mPhase == eAtEnd) {
398 MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
399 mPhase = eAtAnonKids;
400 Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
401 if (afterContent) {
402 mPhase = eAtAfterKid;
403 return afterContent;
404 }
405 }
406
407 if (mPhase == eAtAfterKid) {
408 mPhase = eAtAnonKids;
409 }
410
411 if (mPhase == eAtAnonKids) {
412 if (mAnonKids.IsEmpty()) {
413 AppendNativeAnonymousChildren();
414 mAnonKidsIdx = mAnonKids.Length();
415 }
416
417 // If 0 then it turns into UINT32_MAX, which indicates the iterator is
418 // before the anonymous children.
419 --mAnonKidsIdx;
420 if (mAnonKidsIdx < mAnonKids.Length()) {
421 return mAnonKids[mAnonKidsIdx];
422 }
423 mPhase = eAtExplicitKids;
424 }
425
426 if (mPhase == eAtExplicitKids) {
427 nsIContent* kid = ExplicitChildIterator::GetPreviousChild();
428 if (kid) {
429 return kid;
430 }
431
432 Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
433 if (beforeContent) {
434 mPhase = eAtBeforeKid;
435 return beforeContent;
436 }
437 }
438
439 mPhase = eAtBegin;
440 return nullptr;
441 }
442
GetNextChild()443 nsIContent* StyleChildrenIterator::GetNextChild() {
444 return AllChildrenIterator::GetNextChild();
445 }
446
447 } // namespace dom
448 } // namespace mozilla
449