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