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 #ifndef ChildIterator_h
8 #define ChildIterator_h
9 
10 #include "nsIContent.h"
11 #include "nsIContentInlines.h"
12 #include <stdint.h>
13 
14 /**
15  * Iterates over the children on a node. If a child is an insertion point,
16  * iterates over the children inserted there instead, or the default content
17  * if no children are inserted there.
18  *
19  * The FlattenedChildIterator expands any anonymous content bound from an XBL
20  * binding's <xbl:content> element.
21  */
22 
23 class nsIContent;
24 
25 namespace mozilla {
26 namespace dom {
27 
28 // This class iterates normal DOM child nodes of a given DOM node with
29 // <xbl:children> nodes replaced by the elements that have been filtered into
30 // that insertion point. Any bindings on the given element are ignored for
31 // purposes of determining which insertion point children are filtered into. The
32 // iterator can be initialized to start at the end by providing false for
33 // aStartAtBeginning in order to start iterating in reverse from the last child.
34 class ExplicitChildIterator {
35  public:
36   explicit ExplicitChildIterator(const nsIContent* aParent,
37                                  bool aStartAtBeginning = true);
38 
39   nsIContent* GetNextChild();
40 
41   // Looks for aChildToFind respecting insertion points until aChildToFind is
42   // found.  This version can take shortcuts that the two-argument version
43   // can't, so can be faster (and in fact can be O(1) instead of O(N) in many
44   // cases).
45   bool Seek(const nsIContent* aChildToFind);
46 
47   // Looks for aChildToFind respecting insertion points until aChildToFind is
48   // found. or aBound is found. If aBound is nullptr then the seek is unbounded.
49   // Returns whether aChildToFind was found as an explicit child prior to
50   // encountering aBound.
Seek(const nsIContent * aChildToFind,nsIContent * aBound)51   bool Seek(const nsIContent* aChildToFind, nsIContent* aBound) {
52     // It would be nice to assert that we find aChildToFind, but bz thinks that
53     // we might not find aChildToFind when called from ContentInserted
54     // if first-letter frames are about.
55 
56     // We can't easily take shortcuts here because we'd have to have a way to
57     // compare aChildToFind to aBound.
58     nsIContent* child;
59     do {
60       child = GetNextChild();
61     } while (child && child != aChildToFind && child != aBound);
62 
63     return child == aChildToFind;
64   }
65 
66   // Returns the current target of this iterator (which might be an explicit
67   // child of the node, fallback content of an insertion point or
68   // a node distributed to an insertion point.
69   nsIContent* Get() const;
70 
71   // The inverse of GetNextChild. Properly steps in and out of insertion
72   // points.
73   nsIContent* GetPreviousChild();
74 
75  protected:
76   // The parent of the children being iterated. For the FlattenedChildIterator,
77   // if there is a binding attached to the original parent, mParent points to
78   // the <xbl:content> element for the binding.
79   const nsIContent* mParent;
80 
81   // If parent is a slot element, this points to the parent as HTMLSlotElement,
82   // otherwise, it's null.
83   const HTMLSlotElement* mParentAsSlot;
84 
85   // The current child. When we encounter an insertion point,
86   // mChild remains as the insertion point whose content we're iterating (and
87   // our state is controled by mDefaultChild or mIndexInInserted depending on
88   // whether the insertion point expands to its default content or not).
89   nsIContent* mChild;
90 
91   // If non-null, this points to the current default content for the current
92   // insertion point that we're iterating (i.e. mChild, which must be an
93   // nsXBLChildrenElement or HTMLContentElement). Once this transitions back
94   // to null, we continue iterating at mChild's next sibling.
95   nsIContent* mDefaultChild;
96 
97   // A flag to let us know that we haven't started iterating yet.
98   bool mIsFirst;
99 
100   // If not zero, we're iterating inserted children for an insertion point. This
101   // is an index into mChild's inserted children array (mChild must be an
102   // nsXBLChildrenElement). The index is one past the "current" child (as
103   // opposed to mChild which represents the "current" child).
104   uint32_t mIndexInInserted;
105 };
106 
107 // Iterates over the flattened children of a node, which accounts for anonymous
108 // children and nodes moved by insertion points. If a node has anonymous
109 // children, those are iterated over.  The iterator can be initialized to start
110 // at the end by providing false for aStartAtBeginning in order to start
111 // iterating in reverse from the last child.
112 class FlattenedChildIterator : public ExplicitChildIterator {
113  public:
114   explicit FlattenedChildIterator(const nsIContent* aParent,
115                                   bool aStartAtBeginning = true)
ExplicitChildIterator(aParent,aStartAtBeginning)116       : ExplicitChildIterator(aParent, aStartAtBeginning),
117         mOriginalContent(aParent) {
118     Init(false);
119   }
120 
ShadowDOMInvolved()121   bool ShadowDOMInvolved() { return mShadowDOMInvolved; }
122 
Parent()123   const nsIContent* Parent() const { return mOriginalContent; }
124 
125  private:
126   void Init(bool aIgnoreXBL);
127 
128  protected:
129   /**
130    * This constructor is a hack to help AllChildrenIterator which sometimes
131    * doesn't want to consider XBL.
132    */
133   FlattenedChildIterator(const nsIContent* aParent, uint32_t aFlags,
134                          bool aStartAtBeginning = true)
ExplicitChildIterator(aParent,aStartAtBeginning)135       : ExplicitChildIterator(aParent, aStartAtBeginning),
136         mOriginalContent(aParent) {
137     bool ignoreXBL = aFlags & nsIContent::eAllButXBL;
138     Init(ignoreXBL);
139   }
140 
141   const nsIContent* mOriginalContent;
142 
143  private:
144   // For certain optimizations, nsCSSFrameConstructor needs to know if the child
145   // list of the element that we're iterating matches its .childNodes.
146   bool mShadowDOMInvolved = false;
147 };
148 
149 /**
150  * AllChildrenIterator traverses the children of an element including before /
151  * after content and optionally XBL children.  The iterator can be initialized
152  * to start at the end by providing false for aStartAtBeginning in order to
153  * start iterating in reverse from the last child.
154  *
155  * Note: it assumes that no mutation of the DOM or frame tree takes place during
156  * iteration, and will break horribly if that is not true.
157  */
158 class AllChildrenIterator : private FlattenedChildIterator {
159  public:
160   AllChildrenIterator(const nsIContent* aNode, uint32_t aFlags,
161                       bool aStartAtBeginning = true)
FlattenedChildIterator(aNode,aFlags,aStartAtBeginning)162       : FlattenedChildIterator(aNode, aFlags, aStartAtBeginning),
163         mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0),
164         mFlags(aFlags),
165         mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) {}
166 
167 #ifdef DEBUG
168   AllChildrenIterator(AllChildrenIterator&&) = default;
169 
170   AllChildrenIterator& operator=(AllChildrenIterator&& aOther) {
171     // Explicitly call the destructor to ensure the assertion in the destructor
172     // is checked.
173     this->~AllChildrenIterator();
174     new (this) AllChildrenIterator(std::move(aOther));
175     return *this;
176   }
177 
~AllChildrenIterator()178   ~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
179 #endif
180 
181   // Returns the current target the iterator is at, or null if the iterator
182   // doesn't point to any child node (either eAtBegin or eAtEnd phase).
183   nsIContent* Get() const;
184 
185   // Seeks the given node in children of a parent element, starting from
186   // the current iterator's position, and sets the iterator at the given child
187   // node if it was found.
188   bool Seek(const nsIContent* aChildToFind);
189 
190   nsIContent* GetNextChild();
191   nsIContent* GetPreviousChild();
192 
193   enum IteratorPhase {
194     eAtBegin,
195     eAtMarkerKid,
196     eAtBeforeKid,
197     eAtExplicitKids,
198     eAtAnonKids,
199     eAtAfterKid,
200     eAtEnd
201   };
Phase()202   IteratorPhase Phase() const { return mPhase; }
203 
204  private:
205   // Helpers.
206   void AppendNativeAnonymousChildren();
207 
208   // mAnonKids is an array of native anonymous children, mAnonKidsIdx is index
209   // in the array. If mAnonKidsIdx < mAnonKids.Length() and mPhase is
210   // eAtAnonKids then the iterator points at a child at mAnonKidsIdx index. If
211   // mAnonKidsIdx == mAnonKids.Length() then the iterator is somewhere after
212   // the last native anon child. If mAnonKidsIdx == UINT32_MAX then the iterator
213   // is somewhere before the first native anon child.
214   nsTArray<nsIContent*> mAnonKids;
215   uint32_t mAnonKidsIdx;
216 
217   uint32_t mFlags;
218   IteratorPhase mPhase;
219 #ifdef DEBUG
220   // XXX we should really assert there are no frame tree changes as well, but
221   // there's no easy way to do that.
222   nsMutationGuard mMutationGuard;
223 #endif
224 };
225 
226 /**
227  * StyleChildrenIterator traverses the children of the element from the
228  * perspective of the style system, particularly the children we need to
229  * traverse during restyle.
230  *
231  * At present, this is identical to AllChildrenIterator with
232  * (eAllChildren | eSkipDocumentLevelNativeAnonymousContent). We used to have
233  * detect and skip any native anonymous children that are used to implement some
234  * special magic in here that went away, but we keep the separate class so
235  * we can reintroduce special magic back if needed.
236  *
237  * Note: it assumes that no mutation of the DOM or frame tree takes place during
238  * iteration, and will break horribly if that is not true.
239  *
240  * We require this to be memmovable since Rust code can create and move
241  * StyleChildrenIterators.
242  */
243 class MOZ_NEEDS_MEMMOVABLE_MEMBERS StyleChildrenIterator
244     : private AllChildrenIterator {
245  public:
GetParent(const nsIContent & aContent)246   static nsIContent* GetParent(const nsIContent& aContent) {
247     nsINode* node = aContent.GetFlattenedTreeParentNodeForStyle();
248     return node && node->IsContent() ? node->AsContent() : nullptr;
249   }
250 
251   explicit StyleChildrenIterator(const nsIContent* aContent,
252                                  bool aStartAtBeginning = true)
253       : AllChildrenIterator(
254             aContent,
255             nsIContent::eAllChildren |
256                 nsIContent::eSkipDocumentLevelNativeAnonymousContent,
257             aStartAtBeginning) {
258     MOZ_COUNT_CTOR(StyleChildrenIterator);
259   }
260 
StyleChildrenIterator(StyleChildrenIterator && aOther)261   StyleChildrenIterator(StyleChildrenIterator&& aOther)
262       : AllChildrenIterator(std::move(aOther)) {
263     MOZ_COUNT_CTOR(StyleChildrenIterator);
264   }
265 
266   StyleChildrenIterator& operator=(StyleChildrenIterator&& aOther) = default;
267 
268   MOZ_COUNTED_DTOR(StyleChildrenIterator)
269 
270   using AllChildrenIterator::GetNextChild;
271   using AllChildrenIterator::GetPreviousChild;
272   using AllChildrenIterator::Seek;
273 };
274 
275 }  // namespace dom
276 }  // namespace mozilla
277 
278 #endif
279