Coverage Report

Created: 2018-09-25 14:53

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