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