Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/painting/RetainedDisplayListBuilder.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
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
 */
7
8
#include "RetainedDisplayListBuilder.h"
9
10
#include "DisplayListChecker.h"
11
#include "gfxPrefs.h"
12
#include "nsPlaceholderFrame.h"
13
#include "nsSubDocumentFrame.h"
14
#include "nsViewManager.h"
15
#include "nsCanvasFrame.h"
16
17
/**
18
 * Code for doing display list building for a modified subset of the window,
19
 * and then merging it into the existing display list (for the full window).
20
 *
21
 * The approach primarily hinges on the observation that the ‘true’ ordering of
22
 * display items is represented by a DAG (only items that intersect in 2d space
23
 * have a defined ordering). Our display list is just one of a many possible
24
 * linear representations of this ordering.
25
 *
26
 * Each time a frame changes (gets a new ComputedStyle, or has a size/position
27
 * change), we schedule a paint (as we do currently), but also reord the frame
28
 * that changed.
29
 *
30
 * When the next paint occurs we union the overflow areas (in screen space) of
31
 * the changed frames, and compute a rect/region that contains all changed
32
 * items. We then build a display list just for this subset of the screen and
33
 * merge it into the display list from last paint.
34
 *
35
 * Any items that exist in one list and not the other must not have a defined
36
 * ordering in the DAG, since they need to intersect to have an ordering and
37
 * we would have built both in the new list if they intersected. Given that, we
38
 * can align items that appear in both lists, and any items that appear between
39
 * matched items can be inserted into the merged list in any order.
40
 */
41
42
using namespace mozilla;
43
44
RetainedDisplayListData*
45
GetRetainedDisplayListData(nsIFrame* aRootFrame)
46
0
{
47
0
  RetainedDisplayListData* data =
48
0
    aRootFrame->GetProperty(RetainedDisplayListData::DisplayListData());
49
0
50
0
  return data;
51
0
}
52
53
RetainedDisplayListData*
54
GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame)
55
0
{
56
0
  RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame);
57
0
58
0
  if (!data) {
59
0
    data = new RetainedDisplayListData();
60
0
    aRootFrame->SetProperty(RetainedDisplayListData::DisplayListData(), data);
61
0
  }
62
0
63
0
  MOZ_ASSERT(data);
64
0
  return data;
65
0
}
66
67
static void
68
MarkFramesWithItemsAndImagesModified(nsDisplayList* aList)
69
0
{
70
0
  for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
71
0
    if (!i->HasDeletedFrame() && i->CanBeReused() &&
72
0
        !i->Frame()->IsFrameModified()) {
73
0
      // If we have existing cached geometry for this item, then check that for
74
0
      // whether we need to invalidate for a sync decode. If we don't, then
75
0
      // use the item's flags.
76
0
      DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
77
0
      // XXX: handle webrender case
78
0
      bool invalidate = false;
79
0
      if (data && data->GetGeometry()) {
80
0
        invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages();
81
0
      } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
82
0
        invalidate = true;
83
0
      }
84
0
85
0
      if (invalidate) {
86
0
        i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
87
0
        if (i->GetDependentFrame()) {
88
0
          i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
89
0
        }
90
0
      }
91
0
    }
92
0
    if (i->GetChildren()) {
93
0
      MarkFramesWithItemsAndImagesModified(i->GetChildren());
94
0
    }
95
0
  }
96
0
}
97
98
static AnimatedGeometryRoot*
99
SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR)
100
0
{
101
0
  if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) {
102
0
    return aParentAGR;
103
0
  }
104
0
105
0
  if (!aFrame->HasOverrideDirtyRegion()) {
106
0
    return nullptr;
107
0
  }
108
0
109
0
  nsDisplayListBuilder::DisplayListBuildingData* data =
110
0
    aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
111
0
112
0
  return data && data->mModifiedAGR ? data->mModifiedAGR.get() : nullptr;
113
0
}
114
115
// Removes any display items that belonged to a frame that was deleted,
116
// and mark frames that belong to a different AGR so that get their
117
// items built again.
118
// TODO: We currently descend into all children even if we don't have an AGR
119
// to mark, as child stacking contexts might. It would be nice if we could
120
// jump into those immediately rather than walking the entire thing.
121
bool
122
RetainedDisplayListBuilder::PreProcessDisplayList(RetainedDisplayList* aList,
123
                                                  AnimatedGeometryRoot* aAGR,
124
                                                  uint32_t aCallerKey,
125
                                                  uint32_t aNestingDepth)
126
0
{
127
0
  // The DAG merging algorithm does not have strong mechanisms in place to keep
128
0
  // the complexity of the resulting DAG under control. In some cases we can
129
0
  // build up edges very quickly. Detect those cases and force a full display
130
0
  // list build if we hit them.
131
0
  static const uint32_t kMaxEdgeRatio = 5;
132
0
  bool initializeDAG = !aList->mDAG.Length();
133
0
  if (!initializeDAG && aList->mDAG.mDirectPredecessorList.Length() >
134
0
                          (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) {
135
0
    return false;
136
0
  }
137
0
138
0
  MOZ_RELEASE_ASSERT(initializeDAG || aList->mDAG.Length() == aList->Count());
139
0
140
0
  nsDisplayList saved;
141
0
  aList->mOldItems.SetCapacity(aList->Count());
142
0
  MOZ_RELEASE_ASSERT(aList->mOldItems.IsEmpty());
143
0
  while (nsDisplayItem* item = aList->RemoveBottom()) {
144
0
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
145
0
    item->mMergedItem = false;
146
0
    item->mPreProcessedItem = true;
147
0
#endif
148
0
149
0
    if (item->HasDeletedFrame() || !item->CanBeReused()) {
150
0
      size_t i = aList->mOldItems.Length();
151
0
      aList->mOldItems.AppendElement(OldItemInfo(nullptr));
152
0
      item->Destroy(&mBuilder);
153
0
154
0
      if (initializeDAG) {
155
0
        if (i == 0) {
156
0
          aList->mDAG.AddNode(Span<const MergedListIndex>());
157
0
        } else {
158
0
          MergedListIndex previous(i - 1);
159
0
          aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
160
0
        }
161
0
      }
162
0
      continue;
163
0
    }
164
0
165
0
    size_t i = aList->mOldItems.Length();
166
0
    aList->mOldItems.AppendElement(OldItemInfo(item));
167
0
    item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth);
168
0
    if (initializeDAG) {
169
0
      if (i == 0) {
170
0
        aList->mDAG.AddNode(Span<const MergedListIndex>());
171
0
      } else {
172
0
        MergedListIndex previous(i - 1);
173
0
        aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
174
0
      }
175
0
    }
176
0
177
0
    nsIFrame* f = item->Frame();
178
0
179
0
    if (item->GetChildren()) {
180
0
      if (!PreProcessDisplayList(item->GetChildren(),
181
0
                                 SelectAGRForFrame(f, aAGR),
182
0
                                 item->GetPerFrameKey(),
183
0
                                 aNestingDepth + 1)) {
184
0
        return false;
185
0
      }
186
0
    }
187
0
188
0
    // TODO: We should be able to check the clipped bounds relative
189
0
    // to the common AGR (of both the existing item and the invalidated
190
0
    // frame) and determine if they can ever intersect.
191
0
    if (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
192
0
      mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
193
0
    }
194
0
195
0
    // TODO: This is here because we sometimes reuse the previous display list
196
0
    // completely. For optimization, we could only restore the state for reused
197
0
    // display items.
198
0
    item->RestoreState();
199
0
  }
200
0
  MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length());
201
0
  aList->RestoreState();
202
0
  return true;
203
0
}
204
205
void
206
RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(
207
  nsDisplayItem* aItem)
208
0
{
209
0
  MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
210
0
211
0
  nsSubDocumentFrame* subDocFrame =
212
0
    static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
213
0
  MOZ_ASSERT(subDocFrame);
214
0
215
0
  nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
216
0
  MOZ_ASSERT(presShell);
217
0
218
0
  mBuilder.IncrementPresShellPaintCount(presShell);
219
0
}
220
221
static bool
222
AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame = nullptr)
223
0
{
224
0
  for (nsIFrame* f = aFrame; f;
225
0
       f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
226
0
    if (f->IsFrameModified()) {
227
0
      return true;
228
0
    }
229
0
230
0
    if (aStopAtFrame && f == aStopAtFrame) {
231
0
      break;
232
0
    }
233
0
  }
234
0
235
0
  return false;
236
0
}
237
238
static void
239
UpdateASR(nsDisplayItem* aItem, Maybe<const ActiveScrolledRoot*>& aContainerASR)
240
0
{
241
0
  if (!aContainerASR) {
242
0
    return;
243
0
  }
244
0
245
0
  nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
246
0
  if (!wrapList) {
247
0
    aItem->SetActiveScrolledRoot(aContainerASR.value());
248
0
    return;
249
0
  }
250
0
251
0
  wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor(
252
0
    wrapList->GetFrameActiveScrolledRoot(), aContainerASR.value()));
253
0
}
254
255
void
256
OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
257
                                    MergedListIndex aIndex)
258
0
{
259
0
  AddedToMergedList(aIndex);
260
0
}
261
262
void
263
OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder,
264
                     nsTArray<MergedListIndex>&& aDirectPredecessors)
265
0
{
266
0
  MOZ_ASSERT(!IsUsed());
267
0
  mUsed = mDiscarded = true;
268
0
  mDirectPredecessors = std::move(aDirectPredecessors);
269
0
  if (mItem) {
270
0
    mItem->Destroy(aBuilder->Builder());
271
0
  }
272
0
  mItem = nullptr;
273
0
}
274
275
bool
276
OldItemInfo::IsChanged()
277
0
{
278
0
  return !mItem || mItem->HasDeletedFrame() || !mItem->CanBeReused();
279
0
}
280
281
/**
282
 * A C++ implementation of Markus Stange's merge-dags algorithm.
283
 * https://github.com/mstange/merge-dags
284
 *
285
 * MergeState handles combining a new list of display items into an existing
286
 * DAG and computes the new DAG in a single pass.
287
 * Each time we add a new item, we resolve all dependencies for it, so that the
288
 * resulting list and DAG are built in topological ordering.
289
 */
290
class MergeState
291
{
292
public:
293
  MergeState(RetainedDisplayListBuilder* aBuilder,
294
             RetainedDisplayList& aOldList,
295
             uint32_t aOuterKey)
296
    : mBuilder(aBuilder)
297
    , mOldList(&aOldList)
298
    , mOldItems(std::move(aOldList.mOldItems))
299
    , mOldDAG(std::move(
300
        *reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(&aOldList.mDAG)))
301
    , mOuterKey(aOuterKey)
302
    , mResultIsModified(false)
303
0
  {
304
0
    mMergedDAG.EnsureCapacityFor(mOldDAG);
305
0
    MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length());
306
0
  }
307
308
  MergedListIndex ProcessItemFromNewList(
309
    nsDisplayItem* aNewItem,
310
    const Maybe<MergedListIndex>& aPreviousItem)
311
0
  {
312
0
    OldListIndex oldIndex;
313
0
    if (!HasModifiedFrame(aNewItem) &&
314
0
        HasMatchingItemInOldList(aNewItem, &oldIndex)) {
315
0
      nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
316
0
      MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() ==
317
0
                              aNewItem->GetPerFrameKey() &&
318
0
                            oldItem->Frame() == aNewItem->Frame());
319
0
      if (!mOldItems[oldIndex.val].IsChanged()) {
320
0
        MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
321
0
        nsDisplayItem* destItem;
322
0
        if (ShouldUseNewItem(aNewItem)) {
323
0
          destItem = aNewItem;
324
0
        } else {
325
0
          destItem = oldItem;
326
0
          // The building rect can depend on the overflow rect (when the parent
327
0
          // frame is position:fixed), which can change without invalidating
328
0
          // the frame/items. If we're using the old item, copy the building
329
0
          // rect across from the new item.
330
0
          oldItem->SetBuildingRect(aNewItem->GetBuildingRect());
331
0
        }
332
0
333
0
        if (aNewItem->GetChildren()) {
334
0
          Maybe<const ActiveScrolledRoot*> containerASRForChildren;
335
0
          if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(),
336
0
                                          oldItem->GetChildren(),
337
0
                                          destItem->GetChildren(),
338
0
                                          containerASRForChildren,
339
0
                                          aNewItem->GetPerFrameKey())) {
340
0
            destItem->InvalidateCachedChildInfo();
341
0
            mResultIsModified = true;
342
0
          }
343
0
          UpdateASR(destItem, containerASRForChildren);
344
0
          destItem->UpdateBounds(mBuilder->Builder());
345
0
        }
346
0
347
0
        AutoTArray<MergedListIndex, 2> directPredecessors =
348
0
          ProcessPredecessorsOfOldNode(oldIndex);
349
0
        MergedListIndex newIndex = AddNewNode(
350
0
          destItem, Some(oldIndex), directPredecessors, aPreviousItem);
351
0
        mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex);
352
0
        if (destItem == aNewItem) {
353
0
          oldItem->Destroy(mBuilder->Builder());
354
0
        } else {
355
0
          aNewItem->Destroy(mBuilder->Builder());
356
0
        }
357
0
        return newIndex;
358
0
      }
359
0
    }
360
0
    mResultIsModified = true;
361
0
    return AddNewNode(
362
0
      aNewItem, Nothing(), Span<MergedListIndex>(), aPreviousItem);
363
0
  }
364
365
  bool ShouldUseNewItem(nsDisplayItem* aNewItem)
366
0
  {
367
0
    // Generally we want to use the old item when the frame isn't marked as
368
0
    // modified so that any cached information on the item (or referencing the
369
0
    // item) gets retained. Quite a few FrameLayerBuilder performance
370
0
    // improvements benefit by this. Sometimes, however, we can end up where the
371
0
    // new item paints something different from the old item, even though we
372
0
    // haven't modified the frame, and it's hard to fix. In these cases we just
373
0
    // always use the new item to be safe.
374
0
    DisplayItemType type = aNewItem->GetType();
375
0
    if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR ||
376
0
        type == DisplayItemType::TYPE_SOLID_COLOR) {
377
0
      // The canvas background color item can paint the color from another
378
0
      // frame, and even though we schedule a paint, we don't mark the canvas
379
0
      // frame as invalid.
380
0
      return true;
381
0
    }
382
0
383
0
    if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) {
384
0
      // We intentionally don't mark the root table frame as modified when a
385
0
      // subframe changes, even though the border collapse item for the root
386
0
      // frame is what paints the changed border. Marking the root frame as
387
0
      // modified would rebuild display items for the whole table area, and we
388
0
      // don't want that.
389
0
      return true;
390
0
    }
391
0
392
0
    if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) {
393
0
      // Text overflow marker items are created with the wrapping block as their
394
0
      // frame, and have an index value to note which line they are created for.
395
0
      // Their rendering can change if the items on that line change, which may
396
0
      // not mark the block as modified. We rebuild them if we build any item on
397
0
      // the line, so we should always get new items if they might have changed
398
0
      // rendering, and it's easier to just use the new items rather than
399
0
      // computing if we actually need them.
400
0
      return true;
401
0
    }
402
0
403
0
    if (type == DisplayItemType::TYPE_SUBDOCUMENT) {
404
0
      // nsDisplaySubDocument::mShouldFlatten can change without an invalidation
405
0
      // (and is the reason we unconditionally build the subdocument item), so
406
0
      // always use the new one to make sure we get the right value.
407
0
      return true;
408
0
    }
409
0
410
0
    if (type == DisplayItemType::TYPE_CARET) {
411
0
      // The caret can change position while still being owned by the same frame
412
0
      // and we don't invalidate in that case. Use the new version since the
413
0
      // changed bounds are needed for DLBI.
414
0
      return true;
415
0
    }
416
0
417
0
    return false;
418
0
  }
419
420
  RetainedDisplayList Finalize()
421
0
  {
422
0
    for (size_t i = 0; i < mOldDAG.Length(); i++) {
423
0
      if (mOldItems[i].IsUsed()) {
424
0
        continue;
425
0
      }
426
0
427
0
      AutoTArray<MergedListIndex, 2> directPredecessors =
428
0
        ResolveNodeIndexesOldToMerged(
429
0
          mOldDAG.GetDirectPredecessors(OldListIndex(i)));
430
0
      ProcessOldNode(OldListIndex(i), std::move(directPredecessors));
431
0
    }
432
0
433
0
    RetainedDisplayList result;
434
0
    result.AppendToTop(&mMergedItems);
435
0
    result.mDAG = std::move(mMergedDAG);
436
0
    MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Count());
437
0
    return result;
438
0
  }
439
440
  bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex)
441
0
  {
442
0
    nsIFrame::DisplayItemArray* items =
443
0
      aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
444
0
    // Look for an item that matches aItem's frame and per-frame-key, but isn't
445
0
    // the same item.
446
0
    for (nsDisplayItem* i : *items) {
447
0
      if (i != aItem && i->Frame() == aItem->Frame() &&
448
0
          i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
449
0
        if (i->GetOldListIndex(mOldList, mOuterKey, aOutIndex)) {
450
0
          return true;
451
0
        }
452
0
      }
453
0
    }
454
0
    return false;
455
0
  }
456
457
  bool HasModifiedFrame(nsDisplayItem* aItem)
458
0
  {
459
0
    return AnyContentAncestorModified(aItem->FrameForInvalidation());
460
0
  }
461
462
  void UpdateContainerASR(nsDisplayItem* aItem)
463
0
  {
464
0
    const ActiveScrolledRoot* itemClipASR =
465
0
      aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr;
466
0
467
0
    const ActiveScrolledRoot* finiteBoundsASR =
468
0
      ActiveScrolledRoot::PickDescendant(itemClipASR,
469
0
                                         aItem->GetActiveScrolledRoot());
470
0
    if (!mContainerASR) {
471
0
      mContainerASR = Some(finiteBoundsASR);
472
0
    } else {
473
0
      mContainerASR = Some(ActiveScrolledRoot::PickAncestor(
474
0
        mContainerASR.value(), finiteBoundsASR));
475
0
    }
476
0
  }
477
478
  MergedListIndex AddNewNode(
479
    nsDisplayItem* aItem,
480
    const Maybe<OldListIndex>& aOldIndex,
481
    Span<const MergedListIndex> aDirectPredecessors,
482
    const Maybe<MergedListIndex>& aExtraDirectPredecessor)
483
0
  {
484
0
    UpdateContainerASR(aItem);
485
0
486
0
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
487
0
    nsIFrame::DisplayItemArray* items =
488
0
      aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
489
0
    for (nsDisplayItem* i : *items) {
490
0
      if (i->Frame() == aItem->Frame() &&
491
0
          i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
492
0
        MOZ_DIAGNOSTIC_ASSERT(!i->mMergedItem);
493
0
      }
494
0
    }
495
0
496
0
    aItem->mMergedItem = true;
497
0
    aItem->mPreProcessedItem = false;
498
0
#endif
499
0
500
0
    mMergedItems.AppendToTop(aItem);
501
0
    MergedListIndex newIndex =
502
0
      mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
503
0
    return newIndex;
504
0
  }
505
506
  void ProcessOldNode(OldListIndex aNode,
507
                      nsTArray<MergedListIndex>&& aDirectPredecessors)
508
0
  {
509
0
    nsDisplayItem* item = mOldItems[aNode.val].mItem;
510
0
    if (mOldItems[aNode.val].IsChanged() || HasModifiedFrame(item)) {
511
0
      mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors));
512
0
      mResultIsModified = true;
513
0
    } else {
514
0
      if (item->GetChildren()) {
515
0
        Maybe<const ActiveScrolledRoot*> containerASRForChildren;
516
0
        nsDisplayList empty;
517
0
        if (mBuilder->MergeDisplayLists(&empty,
518
0
                                        item->GetChildren(),
519
0
                                        item->GetChildren(),
520
0
                                        containerASRForChildren,
521
0
                                        item->GetPerFrameKey())) {
522
0
          item->InvalidateCachedChildInfo();
523
0
          mResultIsModified = true;
524
0
        }
525
0
        UpdateASR(item, containerASRForChildren);
526
0
        item->UpdateBounds(mBuilder->Builder());
527
0
      }
528
0
      if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
529
0
        mBuilder->IncrementSubDocPresShellPaintCount(item);
530
0
      }
531
0
      item->SetReused(true);
532
0
      mOldItems[aNode.val].AddedToMergedList(
533
0
        AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing()));
534
0
    }
535
0
  }
536
537
  struct PredecessorStackItem
538
  {
539
    PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors)
540
      : mNode(aNode)
541
      , mDirectPredecessors(aPredecessors)
542
      , mCurrentPredecessorIndex(0)
543
0
    {
544
0
    }
545
546
    bool IsFinished()
547
0
    {
548
0
      return mCurrentPredecessorIndex == mDirectPredecessors.Length();
549
0
    }
550
551
    OldListIndex GetAndIncrementCurrentPredecessor()
552
0
    {
553
0
      return mDirectPredecessors[mCurrentPredecessorIndex++];
554
0
    }
555
556
    OldListIndex mNode;
557
    Span<OldListIndex> mDirectPredecessors;
558
    size_t mCurrentPredecessorIndex;
559
  };
560
561
  AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode(
562
    OldListIndex aNode)
563
0
  {
564
0
    AutoTArray<PredecessorStackItem, 256> mStack;
565
0
    mStack.AppendElement(
566
0
      PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode)));
567
0
568
0
    while (true) {
569
0
      if (mStack.LastElement().IsFinished()) {
570
0
        // If we've finished processing all the entries in the current set, then
571
0
        // pop it off the processing stack and process it.
572
0
        PredecessorStackItem item = mStack.PopLastElement();
573
0
        AutoTArray<MergedListIndex, 2> result =
574
0
          ResolveNodeIndexesOldToMerged(item.mDirectPredecessors);
575
0
576
0
        if (mStack.IsEmpty()) {
577
0
          return result;
578
0
        }
579
0
580
0
        ProcessOldNode(item.mNode, std::move(result));
581
0
      } else {
582
0
        // Grab the current predecessor, push predecessors of that onto the
583
0
        // processing stack (if it hasn't already been processed), and then
584
0
        // advance to the next entry.
585
0
        OldListIndex currentIndex =
586
0
          mStack.LastElement().GetAndIncrementCurrentPredecessor();
587
0
        if (!mOldItems[currentIndex.val].IsUsed()) {
588
0
          mStack.AppendElement(PredecessorStackItem(
589
0
            currentIndex, mOldDAG.GetDirectPredecessors(currentIndex)));
590
0
        }
591
0
      }
592
0
    }
593
0
  }
594
595
  AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged(
596
    Span<OldListIndex> aDirectPredecessors)
597
0
  {
598
0
    AutoTArray<MergedListIndex, 2> result;
599
0
    result.SetCapacity(aDirectPredecessors.Length());
600
0
    for (OldListIndex index : aDirectPredecessors) {
601
0
      OldItemInfo& oldItem = mOldItems[index.val];
602
0
      if (oldItem.IsDiscarded()) {
603
0
        for (MergedListIndex inner : oldItem.mDirectPredecessors) {
604
0
          if (!result.Contains(inner)) {
605
0
            result.AppendElement(inner);
606
0
          }
607
0
        }
608
0
      } else {
609
0
        result.AppendElement(oldItem.mIndex);
610
0
      }
611
0
    }
612
0
    return result;
613
0
  }
614
615
  RetainedDisplayListBuilder* mBuilder;
616
  RetainedDisplayList* mOldList;
617
  Maybe<const ActiveScrolledRoot*> mContainerASR;
618
  nsTArray<OldItemInfo> mOldItems;
619
  DirectedAcyclicGraph<OldListUnits> mOldDAG;
620
  // Unfortunately we can't use strong typing for the hashtables
621
  // since they internally encode the type with the mOps pointer,
622
  // and assert when we try swap the contents
623
  nsDisplayList mMergedItems;
624
  DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
625
  uint32_t mOuterKey;
626
  bool mResultIsModified;
627
};
628
629
/**
630
 * Takes two display lists and merges them into an output list.
631
 *
632
 * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a
633
 * maximum of one direct predecessor and one direct successor per node). We add
634
 * the two DAGs together, and then output the topological sorted ordering as the
635
 * final display list.
636
 *
637
 * Once we've merged a list, we then retain the DAG (as part of the
638
 * RetainedDisplayList object) to use for future merges.
639
 */
640
bool
641
RetainedDisplayListBuilder::MergeDisplayLists(
642
  nsDisplayList* aNewList,
643
  RetainedDisplayList* aOldList,
644
  RetainedDisplayList* aOutList,
645
  mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
646
  uint32_t aOuterKey)
647
0
{
648
0
  MergeState merge(this, *aOldList, aOuterKey);
649
0
650
0
  Maybe<MergedListIndex> previousItemIndex;
651
0
  while (nsDisplayItem* item = aNewList->RemoveBottom()) {
652
0
    previousItemIndex =
653
0
      Some(merge.ProcessItemFromNewList(item, previousItemIndex));
654
0
  }
655
0
656
0
  *aOutList = merge.Finalize();
657
0
  aOutContainerASR = merge.mContainerASR;
658
0
  return merge.mResultIsModified;
659
0
}
660
661
static void
662
TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
663
  nsDisplayListBuilder* aBuilder,
664
  nsTArray<nsIFrame*>* aModifiedFrames,
665
  nsTArray<nsIFrame*>* aFramesWithProps,
666
  nsIFrame* aRootFrame)
667
0
{
668
0
  MOZ_ASSERT(aRootFrame);
669
0
670
0
  RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame);
671
0
672
0
  if (!data) {
673
0
    return;
674
0
  }
675
0
676
0
  for (auto it = data->Iterator(); !it.Done(); it.Next()) {
677
0
    nsIFrame* frame = it.Key();
678
0
    const RetainedDisplayListData::FrameFlags& flags = it.Data();
679
0
680
0
    if (flags & RetainedDisplayListData::FrameFlags::Modified) {
681
0
      aModifiedFrames->AppendElement(frame);
682
0
    }
683
0
684
0
    if (flags & RetainedDisplayListData::FrameFlags::HasProps) {
685
0
      aFramesWithProps->AppendElement(frame);
686
0
    }
687
0
688
0
    if (flags & RetainedDisplayListData::FrameFlags::HadWillChange) {
689
0
      aBuilder->RemoveFromWillChangeBudget(frame);
690
0
    }
691
0
  }
692
0
693
0
  data->Clear();
694
0
}
695
696
struct CbData
697
{
698
  nsDisplayListBuilder* builder;
699
  nsTArray<nsIFrame*>* modifiedFrames;
700
  nsTArray<nsIFrame*>* framesWithProps;
701
};
702
703
static nsIFrame*
704
GetRootFrameForPainting(nsDisplayListBuilder* aBuilder, nsIDocument* aDocument)
705
0
{
706
0
  // Although this is the actual subdocument, it might not be
707
0
  // what painting uses. Walk up to the nsSubDocumentFrame owning
708
0
  // us, and then ask that which subdoc it's going to paint.
709
0
710
0
  nsIPresShell* presShell = aDocument->GetShell();
711
0
  if (!presShell) {
712
0
    return nullptr;
713
0
  }
714
0
  nsView* rootView = presShell->GetViewManager()->GetRootView();
715
0
  if (!rootView) {
716
0
    return nullptr;
717
0
  }
718
0
719
0
  // There should be an anonymous inner view between the root view
720
0
  // of the subdoc, and the view for the nsSubDocumentFrame.
721
0
  nsView* innerView = rootView->GetParent();
722
0
  if (!innerView) {
723
0
    return nullptr;
724
0
  }
725
0
726
0
  nsView* subDocView = innerView->GetParent();
727
0
  if (!subDocView) {
728
0
    return nullptr;
729
0
  }
730
0
731
0
  nsIFrame* subDocFrame = subDocView->GetFrame();
732
0
  if (!subDocFrame) {
733
0
    return nullptr;
734
0
  }
735
0
736
0
  nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame);
737
0
  MOZ_ASSERT(subdocumentFrame);
738
0
  presShell = subdocumentFrame->GetSubdocumentPresShellForPainting(
739
0
    aBuilder->IsIgnoringPaintSuppression()
740
0
      ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION
741
0
      : 0);
742
0
  return presShell ? presShell->GetRootFrame() : nullptr;
743
0
}
744
745
static bool
746
SubDocEnumCb(nsIDocument* aDocument, void* aData)
747
0
{
748
0
  MOZ_ASSERT(aDocument);
749
0
  MOZ_ASSERT(aData);
750
0
751
0
  CbData* data = static_cast<CbData*>(aData);
752
0
753
0
  nsIFrame* rootFrame = GetRootFrameForPainting(data->builder, aDocument);
754
0
  if (rootFrame) {
755
0
    TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
756
0
      data->builder, data->modifiedFrames, data->framesWithProps, rootFrame);
757
0
758
0
    nsIDocument* innerDoc = rootFrame->PresShell()->GetDocument();
759
0
    if (innerDoc) {
760
0
      innerDoc->EnumerateSubDocuments(SubDocEnumCb, aData);
761
0
    }
762
0
  }
763
0
  return true;
764
0
}
765
766
static void
767
GetModifiedAndFramesWithProps(nsDisplayListBuilder* aBuilder,
768
                              nsTArray<nsIFrame*>* aOutModifiedFrames,
769
                              nsTArray<nsIFrame*>* aOutFramesWithProps)
770
0
{
771
0
  nsIFrame* rootFrame = aBuilder->RootReferenceFrame();
772
0
  MOZ_ASSERT(rootFrame);
773
0
774
0
  TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
775
0
    aBuilder, aOutModifiedFrames, aOutFramesWithProps, rootFrame);
776
0
777
0
  nsIDocument* rootdoc = rootFrame->PresContext()->Document();
778
0
  if (rootdoc) {
779
0
    CbData data = { aBuilder, aOutModifiedFrames, aOutFramesWithProps };
780
0
781
0
    rootdoc->EnumerateSubDocuments(SubDocEnumCb, &data);
782
0
  }
783
0
}
784
785
// ComputeRebuildRegion  debugging
786
// #define CRR_DEBUG 1
787
#if CRR_DEBUG
788
#define CRR_LOG(...) printf_stderr(__VA_ARGS__)
789
#else
790
#define CRR_LOG(...)
791
#endif
792
793
static nsDisplayItem*
794
GetFirstDisplayItemWithChildren(nsIFrame* aFrame)
795
0
{
796
0
  nsIFrame::DisplayItemArray* items =
797
0
    aFrame->GetProperty(nsIFrame::DisplayItems());
798
0
  if (!items) {
799
0
    return nullptr;
800
0
  }
801
0
802
0
  for (nsDisplayItem* i : *items) {
803
0
    if (i->GetChildren()) {
804
0
      return i;
805
0
    }
806
0
  }
807
0
  return nullptr;
808
0
}
809
810
static bool
811
IsInPreserve3DContext(const nsIFrame* aFrame)
812
0
{
813
0
  return aFrame->Extend3DContext() ||
814
0
         aFrame->Combines3DTransformWithAncestors();
815
0
}
816
817
static bool
818
ProcessFrameInternal(nsIFrame* aFrame,
819
                     nsDisplayListBuilder& aBuilder,
820
                     AnimatedGeometryRoot** aAGR,
821
                     nsRect& aOverflow,
822
                     nsIFrame* aStopAtFrame,
823
                     nsTArray<nsIFrame*>& aOutFramesWithProps,
824
                     const bool aStopAtStackingContext)
825
0
{
826
0
  nsIFrame* currentFrame = aFrame;
827
0
828
0
  while (currentFrame != aStopAtFrame) {
829
0
    CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
830
0
            currentFrame,
831
0
            !aStopAtStackingContext,
832
0
            aOverflow.x,
833
0
            aOverflow.y,
834
0
            aOverflow.width,
835
0
            aOverflow.height);
836
0
837
0
    // If the current frame is an OOF frame, DisplayListBuildingData needs to be
838
0
    // set on all the ancestor stacking contexts of the  placeholder frame, up
839
0
    // to the containing block of the OOF frame. This is done to ensure that the
840
0
    // content that might be behind the OOF frame is built for merging.
841
0
    nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
842
0
                              ? currentFrame->GetPlaceholderFrame()
843
0
                              : nullptr;
844
0
845
0
    if (placeholder) {
846
0
      // The rect aOverflow is in the coordinate space of the containing block.
847
0
      // Convert it to a coordinate space of the placeholder frame.
848
0
      nsRect placeholderOverflow =
849
0
        aOverflow + currentFrame->GetOffsetTo(placeholder);
850
0
851
0
      CRR_LOG("Processing placeholder %p for OOF frame %p\n",
852
0
              placeholder,
853
0
              currentFrame);
854
0
855
0
      CRR_LOG("OOF frame draw area: %d %d %d %d\n",
856
0
              placeholderOverflow.x,
857
0
              placeholderOverflow.y,
858
0
              placeholderOverflow.width,
859
0
              placeholderOverflow.height);
860
0
861
0
      // Tracking AGRs for the placeholder processing is not necessary, as the
862
0
      // goal is to only modify the DisplayListBuildingData rect.
863
0
      AnimatedGeometryRoot* dummyAGR = nullptr;
864
0
865
0
      // Find a common ancestor frame to handle frame continuations.
866
0
      // TODO: It might be possible to write a more specific and efficient
867
0
      // function for this.
868
0
      nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame(
869
0
        currentFrame->GetParent(), placeholder->GetParent());
870
0
871
0
      if (!ProcessFrameInternal(placeholder,
872
0
                                aBuilder,
873
0
                                &dummyAGR,
874
0
                                placeholderOverflow,
875
0
                                ancestor,
876
0
                                aOutFramesWithProps,
877
0
                                false)) {
878
0
        return false;
879
0
      }
880
0
    }
881
0
882
0
    // Convert 'aOverflow' into the coordinate space of the nearest stacking
883
0
    // context or display port ancestor and update 'currentFrame' to point to
884
0
    // that frame.
885
0
    aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(
886
0
      currentFrame,
887
0
      aOverflow,
888
0
      aStopAtFrame,
889
0
      nullptr,
890
0
      nullptr,
891
0
      /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
892
0
      &currentFrame);
893
0
    if (IsInPreserve3DContext(currentFrame)) {
894
0
      return false;
895
0
    }
896
0
897
0
    MOZ_ASSERT(currentFrame);
898
0
899
0
    if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
900
0
      CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
901
0
      nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
902
0
      MOZ_ASSERT(sf);
903
0
      nsRect displayPort;
904
0
      DebugOnly<bool> hasDisplayPort = nsLayoutUtils::GetDisplayPort(
905
0
        currentFrame->GetContent(), &displayPort, RelativeTo::ScrollPort);
906
0
      MOZ_ASSERT(hasDisplayPort);
907
0
      // get it relative to the scrollport (from the scrollframe)
908
0
      nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft();
909
0
      r.IntersectRect(r, displayPort);
910
0
      if (!r.IsEmpty()) {
911
0
        nsRect* rect = currentFrame->GetProperty(
912
0
          nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
913
0
        if (!rect) {
914
0
          rect = new nsRect();
915
0
          currentFrame->SetProperty(
916
0
            nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
917
0
          currentFrame->SetHasOverrideDirtyRegion(true);
918
0
          aOutFramesWithProps.AppendElement(currentFrame);
919
0
        }
920
0
        rect->UnionRect(*rect, r);
921
0
        CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n",
922
0
                r.x,
923
0
                r.y,
924
0
                r.width,
925
0
                r.height);
926
0
927
0
        // TODO: Can we just use MarkFrameForDisplayIfVisible, plus
928
0
        // MarkFramesForDifferentAGR to ensure that this displayport, plus any
929
0
        // items that move relative to it get rebuilt, and then not contribute
930
0
        // to the root dirty area?
931
0
        aOverflow = sf->GetScrollPortRect();
932
0
      } else {
933
0
        // Don't contribute to the root dirty area at all.
934
0
        aOverflow.SetEmpty();
935
0
      }
936
0
    } else {
937
0
      aOverflow.IntersectRect(
938
0
        aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf());
939
0
    }
940
0
941
0
    if (aOverflow.IsEmpty()) {
942
0
      break;
943
0
    }
944
0
945
0
    if (currentFrame != aBuilder.RootReferenceFrame() &&
946
0
        currentFrame->IsStackingContext() &&
947
0
        currentFrame->IsFixedPosContainingBlock()) {
948
0
      CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
949
0
      // If we found an intermediate stacking context with an existing display
950
0
      // item then we can store the dirty rect there and stop. If we couldn't
951
0
      // find one then we need to keep bubbling up to the next stacking context.
952
0
      nsDisplayItem* wrapperItem =
953
0
        GetFirstDisplayItemWithChildren(currentFrame);
954
0
      if (!wrapperItem) {
955
0
        continue;
956
0
      }
957
0
958
0
      // Store the stacking context relative dirty area such
959
0
      // that display list building will pick it up when it
960
0
      // gets to it.
961
0
      nsDisplayListBuilder::DisplayListBuildingData* data =
962
0
        currentFrame->GetProperty(
963
0
          nsDisplayListBuilder::DisplayListBuildingRect());
964
0
      if (!data) {
965
0
        data = new nsDisplayListBuilder::DisplayListBuildingData();
966
0
        currentFrame->SetProperty(
967
0
          nsDisplayListBuilder::DisplayListBuildingRect(), data);
968
0
        currentFrame->SetHasOverrideDirtyRegion(true);
969
0
        aOutFramesWithProps.AppendElement(currentFrame);
970
0
      }
971
0
      CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
972
0
              aOverflow.x,
973
0
              aOverflow.y,
974
0
              aOverflow.width,
975
0
              aOverflow.height);
976
0
      data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
977
0
978
0
      if (!aStopAtStackingContext) {
979
0
        // Continue ascending the frame tree until we reach aStopAtFrame.
980
0
        continue;
981
0
      }
982
0
983
0
      // Grab the visible (display list building) rect for children of this
984
0
      // wrapper item and convert into into coordinate relative to the current
985
0
      // frame.
986
0
      nsRect previousVisible = wrapperItem->GetBuildingRectForChildren();
987
0
      if (wrapperItem->ReferenceFrameForChildren() ==
988
0
          wrapperItem->ReferenceFrame()) {
989
0
        previousVisible -= wrapperItem->ToReferenceFrame();
990
0
      } else {
991
0
        MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() ==
992
0
                   wrapperItem->Frame());
993
0
      }
994
0
995
0
      if (!previousVisible.Contains(aOverflow)) {
996
0
        // If the overflow area of the changed frame isn't contained within the
997
0
        // old item, then we might change the size of the item and need to
998
0
        // update its sorting accordingly. Keep propagating the overflow area up
999
0
        // so that we build intersecting items for sorting.
1000
0
        continue;
1001
0
      }
1002
0
1003
0
      if (!data->mModifiedAGR) {
1004
0
        data->mModifiedAGR = *aAGR;
1005
0
      } else if (data->mModifiedAGR != *aAGR) {
1006
0
        data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf();
1007
0
        CRR_LOG("Found multiple modified AGRs within this stacking context, "
1008
0
                "giving up\n");
1009
0
      }
1010
0
1011
0
      // Don't contribute to the root dirty area at all.
1012
0
      aOverflow.SetEmpty();
1013
0
      *aAGR = nullptr;
1014
0
1015
0
      break;
1016
0
    }
1017
0
  }
1018
0
  return true;
1019
0
}
1020
1021
bool
1022
RetainedDisplayListBuilder::ProcessFrame(
1023
  nsIFrame* aFrame,
1024
  nsDisplayListBuilder& aBuilder,
1025
  nsIFrame* aStopAtFrame,
1026
  nsTArray<nsIFrame*>& aOutFramesWithProps,
1027
  const bool aStopAtStackingContext,
1028
  nsRect* aOutDirty,
1029
  AnimatedGeometryRoot** aOutModifiedAGR)
1030
0
{
1031
0
  if (aFrame->HasOverrideDirtyRegion()) {
1032
0
    aOutFramesWithProps.AppendElement(aFrame);
1033
0
  }
1034
0
1035
0
  if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
1036
0
    return true;
1037
0
  }
1038
0
1039
0
  // TODO: There is almost certainly a faster way of doing this, probably can be
1040
0
  // combined with the ancestor walk for TransformFrameRectToAncestor.
1041
0
  AnimatedGeometryRoot* agr =
1042
0
    aBuilder.FindAnimatedGeometryRootFor(aFrame)->GetAsyncAGR();
1043
0
1044
0
  CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame);
1045
0
1046
0
  // Convert the frame's overflow rect into the coordinate space
1047
0
  // of the nearest stacking context that has an existing display item.
1048
0
  // We store that as a dirty rect on that stacking context so that we build
1049
0
  // all items that intersect the changed frame within the stacking context,
1050
0
  // and then we use MarkFrameForDisplayIfVisible to make sure the stacking
1051
0
  // context itself gets built. We don't need to build items that intersect
1052
0
  // outside of the stacking context, since we know the stacking context item
1053
0
  // exists in the old list, so we can trivially merge without needing other
1054
0
  // items.
1055
0
  nsRect overflow = aFrame->GetVisualOverflowRectRelativeToSelf();
1056
0
1057
0
  // If the modified frame is also a caret frame, include the caret area.
1058
0
  // This is needed because some frames (for example text frames without text)
1059
0
  // might have an empty overflow rect.
1060
0
  if (aFrame == aBuilder.GetCaretFrame()) {
1061
0
    overflow.UnionRect(overflow, aBuilder.GetCaretRect());
1062
0
  }
1063
0
1064
0
  if (!ProcessFrameInternal(aFrame,
1065
0
                            aBuilder,
1066
0
                            &agr,
1067
0
                            overflow,
1068
0
                            aStopAtFrame,
1069
0
                            aOutFramesWithProps,
1070
0
                            aStopAtStackingContext)) {
1071
0
    return false;
1072
0
  }
1073
0
1074
0
  if (!overflow.IsEmpty()) {
1075
0
    aOutDirty->UnionRect(*aOutDirty, overflow);
1076
0
    CRR_LOG("Adding area to root draw area: %d %d %d %d\n",
1077
0
            overflow.x,
1078
0
            overflow.y,
1079
0
            overflow.width,
1080
0
            overflow.height);
1081
0
1082
0
    // If we get changed frames from multiple AGRS, then just give up as it gets
1083
0
    // really complex to track which items would need to be marked in
1084
0
    // MarkFramesForDifferentAGR.
1085
0
    if (!*aOutModifiedAGR) {
1086
0
      CRR_LOG("Setting %p as root stacking context AGR\n", agr);
1087
0
      *aOutModifiedAGR = agr;
1088
0
    } else if (agr && *aOutModifiedAGR != agr) {
1089
0
      CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
1090
0
      return false;
1091
0
    }
1092
0
  }
1093
0
  return true;
1094
0
}
1095
1096
static void
1097
AddFramesForContainingBlock(nsIFrame* aBlock,
1098
                            const nsFrameList& aFrames,
1099
                            nsTArray<nsIFrame*>& aExtraFrames)
1100
0
{
1101
0
  for (nsIFrame* f : aFrames) {
1102
0
    if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) {
1103
0
      CRR_LOG("Adding invalid OOF %p\n", f);
1104
0
      aExtraFrames.AppendElement(f);
1105
0
    }
1106
0
  }
1107
0
}
1108
1109
// Placeholder descendants of aFrame don't contribute to aFrame's overflow area.
1110
// Find all the containing blocks that might own placeholders under us, walk
1111
// their OOF frames list, and manually invalidate any frames that are
1112
// descendants of a modified frame (us, or another frame we'll get to soon).
1113
// This is combined with the work required for MarkFrameForDisplayIfVisible,
1114
// so that we can avoid an extra ancestor walk, and we can reuse the flag
1115
// to detect when we've already visited an ancestor (and thus all further
1116
// ancestors must also be visited).
1117
void
1118
FindContainingBlocks(nsIFrame* aFrame, nsTArray<nsIFrame*>& aExtraFrames)
1119
0
{
1120
0
  for (nsIFrame* f = aFrame; f;
1121
0
       f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
1122
0
    if (f->ForceDescendIntoIfVisible())
1123
0
      return;
1124
0
    f->SetForceDescendIntoIfVisible(true);
1125
0
    CRR_LOG("Considering OOFs for %p\n", f);
1126
0
1127
0
    AddFramesForContainingBlock(
1128
0
      f, f->GetChildList(nsIFrame::kFloatList), aExtraFrames);
1129
0
    AddFramesForContainingBlock(
1130
0
      f, f->GetChildList(f->GetAbsoluteListID()), aExtraFrames);
1131
0
  }
1132
0
}
1133
1134
/**
1135
 * Given a list of frames that has been modified, computes the region that we
1136
 * need to do display list building for in order to build all modified display
1137
 * items.
1138
 *
1139
 * When a modified frame is within a stacking context (with an existing display
1140
 * item), then we only contribute to the build area within the stacking context,
1141
 * as well as forcing display list building to descend to the stacking context.
1142
 * We don't need to add build area outside of the stacking context (and force
1143
 * items above/below the stacking context container item to be built), since
1144
 * just matching the position of the stacking context container item is
1145
 * sufficient to ensure correct ordering during merging.
1146
 *
1147
 * We need to rebuild all items that might intersect with the modified frame,
1148
 * both now and during async changes on the compositor. We do this by rebuilding
1149
 * the area covered by the changed frame, as well as rebuilding all items that
1150
 * have a different (async) AGR to the changed frame. If we have changes to
1151
 * multiple AGRs (within a stacking context), then we rebuild that stacking
1152
 * context entirely.
1153
 *
1154
 * @param aModifiedFrames The list of modified frames.
1155
 * @param aOutDirty The result region to use for display list building.
1156
 * @param aOutModifiedAGR The modified AGR for the root stacking context.
1157
 * @param aOutFramesWithProps The list of frames to which we attached partial
1158
 * build data so that it can be cleaned up.
1159
 *
1160
 * @return true if we succesfully computed a partial rebuild region, false if a
1161
 * full build is required.
1162
 */
1163
bool
1164
RetainedDisplayListBuilder::ComputeRebuildRegion(
1165
  nsTArray<nsIFrame*>& aModifiedFrames,
1166
  nsRect* aOutDirty,
1167
  AnimatedGeometryRoot** aOutModifiedAGR,
1168
  nsTArray<nsIFrame*>& aOutFramesWithProps)
1169
0
{
1170
0
  CRR_LOG("Computing rebuild regions for %zu frames:\n",
1171
0
          aModifiedFrames.Length());
1172
0
  nsTArray<nsIFrame*> extraFrames;
1173
0
  for (nsIFrame* f : aModifiedFrames) {
1174
0
    MOZ_ASSERT(f);
1175
0
1176
0
    mBuilder.AddFrameMarkedForDisplayIfVisible(f);
1177
0
    FindContainingBlocks(f, extraFrames);
1178
0
1179
0
    if (!ProcessFrame(f,
1180
0
                      mBuilder,
1181
0
                      mBuilder.RootReferenceFrame(),
1182
0
                      aOutFramesWithProps,
1183
0
                      true,
1184
0
                      aOutDirty,
1185
0
                      aOutModifiedAGR)) {
1186
0
      return false;
1187
0
    }
1188
0
  }
1189
0
1190
0
  for (nsIFrame* f : extraFrames) {
1191
0
    mBuilder.MarkFrameModifiedDuringBuilding(f);
1192
0
1193
0
    if (!ProcessFrame(f,
1194
0
                      mBuilder,
1195
0
                      mBuilder.RootReferenceFrame(),
1196
0
                      aOutFramesWithProps,
1197
0
                      true,
1198
0
                      aOutDirty,
1199
0
                      aOutModifiedAGR)) {
1200
0
      return false;
1201
0
    }
1202
0
  }
1203
0
1204
0
  return true;
1205
0
}
1206
1207
/*
1208
 * A simple early exit heuristic to avoid slow partial display list rebuilds.
1209
 */
1210
static bool
1211
ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames)
1212
0
{
1213
0
  if (aModifiedFrames.Length() > gfxPrefs::LayoutRebuildFrameLimit()) {
1214
0
    return false;
1215
0
  }
1216
0
1217
0
  for (nsIFrame* f : aModifiedFrames) {
1218
0
    MOZ_ASSERT(f);
1219
0
1220
0
    const LayoutFrameType type = f->Type();
1221
0
1222
0
    // If we have any modified frames of the following types, it is likely that
1223
0
    // doing a partial rebuild of the display list will be slower than doing a
1224
0
    // full rebuild.
1225
0
    // This is because these frames either intersect or may intersect with most
1226
0
    // of the page content. This is either due to display port size or different
1227
0
    // async AGR.
1228
0
    if (type == LayoutFrameType::Viewport ||
1229
0
        type == LayoutFrameType::PageContent ||
1230
0
        type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) {
1231
0
      return false;
1232
0
    }
1233
0
  }
1234
0
1235
0
  return true;
1236
0
}
1237
1238
static void
1239
ClearFrameProps(nsTArray<nsIFrame*>& aFrames)
1240
0
{
1241
0
  for (nsIFrame* f : aFrames) {
1242
0
    if (f->HasOverrideDirtyRegion()) {
1243
0
      f->SetHasOverrideDirtyRegion(false);
1244
0
      f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect());
1245
0
      f->DeleteProperty(
1246
0
        nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
1247
0
    }
1248
0
1249
0
    f->SetFrameIsModified(false);
1250
0
  }
1251
0
}
1252
1253
class AutoClearFramePropsArray
1254
{
1255
public:
1256
  explicit AutoClearFramePropsArray(size_t aCapacity)
1257
    : mFrames(aCapacity)
1258
0
  {
1259
0
  }
1260
1261
0
  AutoClearFramePropsArray() = default;
1262
1263
0
  ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); }
1264
1265
0
  nsTArray<nsIFrame*>& Frames() { return mFrames; }
1266
1267
0
  bool IsEmpty() const { return mFrames.IsEmpty(); }
1268
1269
private:
1270
  nsTArray<nsIFrame*> mFrames;
1271
};
1272
1273
void
1274
RetainedDisplayListBuilder::ClearFramesWithProps()
1275
0
{
1276
0
  AutoClearFramePropsArray modifiedFrames;
1277
0
  AutoClearFramePropsArray framesWithProps;
1278
0
  GetModifiedAndFramesWithProps(
1279
0
    &mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
1280
0
}
1281
1282
auto
1283
RetainedDisplayListBuilder::AttemptPartialUpdate(
1284
  nscolor aBackstop,
1285
  mozilla::DisplayListChecker* aChecker) -> PartialUpdateResult
1286
0
{
1287
0
  mBuilder.RemoveModifiedWindowRegions();
1288
0
  mBuilder.ClearWindowOpaqueRegion();
1289
0
1290
0
  if (mBuilder.ShouldSyncDecodeImages()) {
1291
0
    MarkFramesWithItemsAndImagesModified(&mList);
1292
0
  }
1293
0
1294
0
  mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
1295
0
1296
0
  // We set the override dirty regions during ComputeRebuildRegion or in
1297
0
  // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also
1298
0
  // marks the frame modified, so those regions are cleared here as well.
1299
0
  AutoClearFramePropsArray modifiedFrames(64);
1300
0
  AutoClearFramePropsArray framesWithProps;
1301
0
  GetModifiedAndFramesWithProps(
1302
0
    &mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
1303
0
1304
0
  // Do not allow partial builds if the retained display list is empty, or if
1305
0
  // ShouldBuildPartial heuristic fails.
1306
0
  bool shouldBuildPartial =
1307
0
    !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames());
1308
0
1309
0
  // We don't support retaining with overlay scrollbars, since they require
1310
0
  // us to look at the display list and pick the highest z-index, which
1311
0
  // we can't do during partial building.
1312
0
  if (mBuilder.DisablePartialUpdates()) {
1313
0
    shouldBuildPartial = false;
1314
0
    mBuilder.SetDisablePartialUpdates(false);
1315
0
  }
1316
0
1317
0
  if (mPreviousCaret != mBuilder.GetCaretFrame()) {
1318
0
    if (mPreviousCaret) {
1319
0
      if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) {
1320
0
        modifiedFrames.Frames().AppendElement(mPreviousCaret);
1321
0
      }
1322
0
    }
1323
0
1324
0
    if (mBuilder.GetCaretFrame()) {
1325
0
      if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) {
1326
0
        modifiedFrames.Frames().AppendElement(mBuilder.GetCaretFrame());
1327
0
      }
1328
0
    }
1329
0
1330
0
    mPreviousCaret = mBuilder.GetCaretFrame();
1331
0
  }
1332
0
1333
0
  nsRect modifiedDirty;
1334
0
  AnimatedGeometryRoot* modifiedAGR = nullptr;
1335
0
  if (!shouldBuildPartial ||
1336
0
      !ComputeRebuildRegion(modifiedFrames.Frames(),
1337
0
                            &modifiedDirty,
1338
0
                            &modifiedAGR,
1339
0
                            framesWithProps.Frames()) ||
1340
0
      !PreProcessDisplayList(&mList, modifiedAGR)) {
1341
0
    mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
1342
0
    mList.DeleteAll(&mBuilder);
1343
0
    return PartialUpdateResult::Failed;
1344
0
  }
1345
0
1346
0
  // This is normally handled by EnterPresShell, but we skipped it so that we
1347
0
  // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
1348
0
  nsIScrollableFrame* sf = mBuilder.RootReferenceFrame()
1349
0
                             ->PresShell()
1350
0
                             ->GetRootScrollFrameAsScrollable();
1351
0
  if (sf) {
1352
0
    nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
1353
0
    if (canvasFrame) {
1354
0
      mBuilder.MarkFrameForDisplayIfVisible(canvasFrame,
1355
0
                                            mBuilder.RootReferenceFrame());
1356
0
    }
1357
0
  }
1358
0
1359
0
  modifiedDirty.IntersectRect(
1360
0
    modifiedDirty,
1361
0
    mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
1362
0
1363
0
  PartialUpdateResult result = PartialUpdateResult::NoChange;
1364
0
  if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) {
1365
0
    result = PartialUpdateResult::Updated;
1366
0
  }
1367
0
1368
0
  mBuilder.SetDirtyRect(modifiedDirty);
1369
0
  mBuilder.SetPartialUpdate(true);
1370
0
1371
0
  nsDisplayList modifiedDL;
1372
0
  mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(
1373
0
    &mBuilder, &modifiedDL);
1374
0
  if (!modifiedDL.IsEmpty()) {
1375
0
    nsLayoutUtils::AddExtraBackgroundItems(
1376
0
      mBuilder,
1377
0
      modifiedDL,
1378
0
      mBuilder.RootReferenceFrame(),
1379
0
      nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
1380
0
      mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
1381
0
      aBackstop);
1382
0
  }
1383
0
  mBuilder.SetPartialUpdate(false);
1384
0
1385
0
  if (mBuilder.PartialBuildFailed()) {
1386
0
    mBuilder.SetPartialBuildFailed(false);
1387
0
    mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
1388
0
    mList.DeleteAll(&mBuilder);
1389
0
    modifiedDL.DeleteAll(&mBuilder);
1390
0
    return PartialUpdateResult::Failed;
1391
0
  }
1392
0
1393
0
  if (aChecker) {
1394
0
    aChecker->Set(&modifiedDL, "TM");
1395
0
  }
1396
0
1397
0
  // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
1398
0
  //              modifiedDirty.x, modifiedDirty.y, modifiedDirty.width,
1399
0
  //              modifiedDirty.height);
1400
0
  // nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
1401
0
1402
0
  // |modifiedDL| can sometimes be empty here. We still perform the
1403
0
  // display list merging to prune unused items (for example, items that
1404
0
  // are not visible anymore) from the old list.
1405
0
  // TODO: Optimization opportunity. In this case, MergeDisplayLists()
1406
0
  // unnecessarily creates a hashtable of the old items.
1407
0
  // TODO: Ideally we could skip this if result is NoChange, but currently when
1408
0
  // we call RestoreState on nsDisplayWrapList it resets the clip to the base
1409
0
  // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
1410
0
  // move it to the correct inner clip.
1411
0
  Maybe<const ActiveScrolledRoot*> dummy;
1412
0
  if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
1413
0
    result = PartialUpdateResult::Updated;
1414
0
  }
1415
0
1416
0
  // printf_stderr("Painting --- Merged list:\n");
1417
0
  // nsFrame::PrintDisplayList(&mBuilder, mList);
1418
0
1419
0
  mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
1420
0
  return result;
1421
0
}