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