|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et 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/XBLChildrenElement.h" |
|
10 #include "mozilla/dom/HTMLContentElement.h" |
|
11 #include "mozilla/dom/HTMLShadowElement.h" |
|
12 #include "mozilla/dom/ShadowRoot.h" |
|
13 |
|
14 namespace mozilla { |
|
15 namespace dom { |
|
16 |
|
17 class MatchedNodes { |
|
18 public: |
|
19 MatchedNodes(HTMLContentElement* aInsertionPoint) |
|
20 : mIsContentElement(true), mContentElement(aInsertionPoint) {} |
|
21 |
|
22 MatchedNodes(XBLChildrenElement* aInsertionPoint) |
|
23 : mIsContentElement(false), mChildrenElement(aInsertionPoint) {} |
|
24 |
|
25 uint32_t Length() const |
|
26 { |
|
27 return mIsContentElement ? mContentElement->MatchedNodes().Length() |
|
28 : mChildrenElement->mInsertedChildren.Length(); |
|
29 } |
|
30 |
|
31 nsIContent* operator[](int32_t aIndex) const |
|
32 { |
|
33 return mIsContentElement ? mContentElement->MatchedNodes()[aIndex] |
|
34 : mChildrenElement->mInsertedChildren[aIndex]; |
|
35 } |
|
36 |
|
37 bool IsEmpty() const |
|
38 { |
|
39 return mIsContentElement ? mContentElement->MatchedNodes().IsEmpty() |
|
40 : mChildrenElement->mInsertedChildren.IsEmpty(); |
|
41 } |
|
42 protected: |
|
43 bool mIsContentElement; |
|
44 union { |
|
45 HTMLContentElement* mContentElement; |
|
46 XBLChildrenElement* mChildrenElement; |
|
47 }; |
|
48 }; |
|
49 |
|
50 static inline MatchedNodes |
|
51 GetMatchedNodesForPoint(nsIContent* aContent) |
|
52 { |
|
53 if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { |
|
54 // XBL case |
|
55 return MatchedNodes(static_cast<XBLChildrenElement*>(aContent)); |
|
56 } |
|
57 |
|
58 // Web components case |
|
59 MOZ_ASSERT(aContent->IsHTML(nsGkAtoms::content)); |
|
60 return MatchedNodes(static_cast<HTMLContentElement*>(aContent)); |
|
61 } |
|
62 |
|
63 nsIContent* |
|
64 ExplicitChildIterator::GetNextChild() |
|
65 { |
|
66 // If we're already in the inserted-children array, look there first |
|
67 if (mIndexInInserted) { |
|
68 MOZ_ASSERT(mChild); |
|
69 MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); |
|
70 MOZ_ASSERT(!mDefaultChild); |
|
71 |
|
72 MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); |
|
73 if (mIndexInInserted < assignedChildren.Length()) { |
|
74 return assignedChildren[mIndexInInserted++]; |
|
75 } |
|
76 mIndexInInserted = 0; |
|
77 mChild = mChild->GetNextSibling(); |
|
78 } else if (mShadowIterator) { |
|
79 // If we're inside of a <shadow> element, look through the |
|
80 // explicit children of the projected ShadowRoot via |
|
81 // the mShadowIterator. |
|
82 nsIContent* nextChild = mShadowIterator->GetNextChild(); |
|
83 if (nextChild) { |
|
84 return nextChild; |
|
85 } |
|
86 |
|
87 mShadowIterator = nullptr; |
|
88 mChild = mChild->GetNextSibling(); |
|
89 } else if (mDefaultChild) { |
|
90 // If we're already in default content, check if there are more nodes there |
|
91 MOZ_ASSERT(mChild); |
|
92 MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); |
|
93 |
|
94 mDefaultChild = mDefaultChild->GetNextSibling(); |
|
95 if (mDefaultChild) { |
|
96 return mDefaultChild; |
|
97 } |
|
98 |
|
99 mChild = mChild->GetNextSibling(); |
|
100 } else if (mIsFirst) { // at the beginning of the child list |
|
101 mChild = mParent->GetFirstChild(); |
|
102 mIsFirst = false; |
|
103 } else if (mChild) { // in the middle of the child list |
|
104 mChild = mChild->GetNextSibling(); |
|
105 } |
|
106 |
|
107 // Iterate until we find a non-insertion point, or an insertion point with |
|
108 // content. |
|
109 while (mChild) { |
|
110 // If the current child being iterated is a shadow insertion point then |
|
111 // the iterator needs to go into the projected ShadowRoot. |
|
112 if (ShadowRoot::IsShadowInsertionPoint(mChild)) { |
|
113 // Look for the next child in the projected ShadowRoot for the <shadow> |
|
114 // element. |
|
115 HTMLShadowElement* shadowElem = static_cast<HTMLShadowElement*>(mChild); |
|
116 ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); |
|
117 if (projectedShadow) { |
|
118 mShadowIterator = new ExplicitChildIterator(projectedShadow); |
|
119 nsIContent* nextChild = mShadowIterator->GetNextChild(); |
|
120 if (nextChild) { |
|
121 return nextChild; |
|
122 } |
|
123 mShadowIterator = nullptr; |
|
124 } |
|
125 mChild = mChild->GetNextSibling(); |
|
126 } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { |
|
127 // If the current child being iterated is a content insertion point |
|
128 // then the iterator needs to return the nodes distributed into |
|
129 // the content insertion point. |
|
130 MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); |
|
131 if (!assignedChildren.IsEmpty()) { |
|
132 // Iterate through elements projected on insertion point. |
|
133 mIndexInInserted = 1; |
|
134 return assignedChildren[0]; |
|
135 } |
|
136 |
|
137 // Insertion points inside fallback/default content |
|
138 // are considered inactive and do not get assigned nodes. |
|
139 mDefaultChild = mChild->GetFirstChild(); |
|
140 if (mDefaultChild) { |
|
141 return mDefaultChild; |
|
142 } |
|
143 |
|
144 // If we have an insertion point with no assigned nodes and |
|
145 // no default content, move on to the next node. |
|
146 mChild = mChild->GetNextSibling(); |
|
147 } else { |
|
148 // mChild is not an insertion point, thus it is the next node to |
|
149 // return from this iterator. |
|
150 break; |
|
151 } |
|
152 } |
|
153 |
|
154 return mChild; |
|
155 } |
|
156 |
|
157 FlattenedChildIterator::FlattenedChildIterator(nsIContent* aParent) |
|
158 : ExplicitChildIterator(aParent), mXBLInvolved(false) |
|
159 { |
|
160 nsXBLBinding* binding = |
|
161 aParent->OwnerDoc()->BindingManager()->GetBindingWithContent(aParent); |
|
162 |
|
163 if (binding) { |
|
164 nsIContent* anon = binding->GetAnonymousContent(); |
|
165 if (anon) { |
|
166 mParent = anon; |
|
167 mXBLInvolved = true; |
|
168 } |
|
169 } |
|
170 |
|
171 // We set mXBLInvolved to true if either: |
|
172 // - The node we're iterating has a binding with content attached to it. |
|
173 // - The node is generated XBL content and has an <xbl:children> child. |
|
174 if (!mXBLInvolved && aParent->GetBindingParent()) { |
|
175 for (nsIContent* child = aParent->GetFirstChild(); |
|
176 child; |
|
177 child = child->GetNextSibling()) { |
|
178 if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { |
|
179 MOZ_ASSERT(child->GetBindingParent()); |
|
180 mXBLInvolved = true; |
|
181 break; |
|
182 } |
|
183 } |
|
184 } |
|
185 } |
|
186 |
|
187 nsIContent* |
|
188 ExplicitChildIterator::Get() |
|
189 { |
|
190 MOZ_ASSERT(!mIsFirst); |
|
191 |
|
192 if (mIndexInInserted) { |
|
193 XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild); |
|
194 return point->mInsertedChildren[mIndexInInserted - 1]; |
|
195 } else if (mShadowIterator) { |
|
196 return mShadowIterator->Get(); |
|
197 } |
|
198 return mDefaultChild ? mDefaultChild : mChild; |
|
199 } |
|
200 |
|
201 nsIContent* |
|
202 ExplicitChildIterator::GetPreviousChild() |
|
203 { |
|
204 // If we're already in the inserted-children array, look there first |
|
205 if (mIndexInInserted) { |
|
206 // NB: mIndexInInserted points one past the last returned child so we need |
|
207 // to look *two* indices back in order to return the previous child. |
|
208 MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); |
|
209 if (--mIndexInInserted) { |
|
210 return assignedChildren[mIndexInInserted - 1]; |
|
211 } |
|
212 mChild = mChild->GetPreviousSibling(); |
|
213 } else if (mShadowIterator) { |
|
214 nsIContent* previousChild = mShadowIterator->GetPreviousChild(); |
|
215 if (previousChild) { |
|
216 return previousChild; |
|
217 } |
|
218 mShadowIterator = nullptr; |
|
219 mChild = mChild->GetPreviousSibling(); |
|
220 } else if (mDefaultChild) { |
|
221 // If we're already in default content, check if there are more nodes there |
|
222 mDefaultChild = mDefaultChild->GetPreviousSibling(); |
|
223 if (mDefaultChild) { |
|
224 return mDefaultChild; |
|
225 } |
|
226 |
|
227 mChild = mChild->GetPreviousSibling(); |
|
228 } else if (mIsFirst) { // at the beginning of the child list |
|
229 return nullptr; |
|
230 } else if (mChild) { // in the middle of the child list |
|
231 mChild = mChild->GetPreviousSibling(); |
|
232 } else { // at the end of the child list |
|
233 mChild = mParent->GetLastChild(); |
|
234 } |
|
235 |
|
236 // Iterate until we find a non-insertion point, or an insertion point with |
|
237 // content. |
|
238 while (mChild) { |
|
239 if (ShadowRoot::IsShadowInsertionPoint(mChild)) { |
|
240 // If the current child being iterated is a shadow insertion point then |
|
241 // the iterator needs to go into the projected ShadowRoot. |
|
242 HTMLShadowElement* shadowElem = static_cast<HTMLShadowElement*>(mChild); |
|
243 ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); |
|
244 if (projectedShadow) { |
|
245 // Create a ExplicitChildIterator that begins iterating from the end. |
|
246 mShadowIterator = new ExplicitChildIterator(projectedShadow, false); |
|
247 nsIContent* previousChild = mShadowIterator->GetPreviousChild(); |
|
248 if (previousChild) { |
|
249 return previousChild; |
|
250 } |
|
251 mShadowIterator = nullptr; |
|
252 } |
|
253 mChild = mChild->GetPreviousSibling(); |
|
254 } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { |
|
255 // If the current child being iterated is a content insertion point |
|
256 // then the iterator needs to return the nodes distributed into |
|
257 // the content insertion point. |
|
258 MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); |
|
259 if (!assignedChildren.IsEmpty()) { |
|
260 mIndexInInserted = assignedChildren.Length(); |
|
261 return assignedChildren[mIndexInInserted - 1]; |
|
262 } |
|
263 |
|
264 mDefaultChild = mChild->GetLastChild(); |
|
265 if (mDefaultChild) { |
|
266 return mDefaultChild; |
|
267 } |
|
268 |
|
269 mChild = mChild->GetPreviousSibling(); |
|
270 } else { |
|
271 // mChild is not an insertion point, thus it is the next node to |
|
272 // return from this iterator. |
|
273 break; |
|
274 } |
|
275 } |
|
276 |
|
277 if (!mChild) { |
|
278 mIsFirst = true; |
|
279 } |
|
280 |
|
281 return mChild; |
|
282 } |
|
283 |
|
284 } // namespace dom |
|
285 } // namespace mozilla |