tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RetainedDisplayListBuilder.cpp (65055B)


      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 "mozilla/Attributes.h"
     11 #include "mozilla/AutoRestore.h"
     12 #include "mozilla/DisplayPortUtils.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/ProfilerLabels.h"
     15 #include "mozilla/ScrollContainerFrame.h"
     16 #include "mozilla/StaticPrefs_layout.h"
     17 #include "nsCanvasFrame.h"
     18 #include "nsIFrame.h"
     19 #include "nsIFrameInlines.h"
     20 #include "nsPlaceholderFrame.h"
     21 #include "nsSubDocumentFrame.h"
     22 
     23 /**
     24 * Code for doing display list building for a modified subset of the window,
     25 * and then merging it into the existing display list (for the full window).
     26 *
     27 * The approach primarily hinges on the observation that the 'true' ordering
     28 * of display items is represented by a DAG (only items that intersect in 2d
     29 * space have a defined ordering). Our display list is just one of a many
     30 * possible linear representations of this ordering.
     31 *
     32 * Each time a frame changes (gets a new ComputedStyle, or has a size/position
     33 * change), we schedule a paint (as we do currently), but also reord the frame
     34 * that changed.
     35 *
     36 * When the next paint occurs we union the overflow areas (in screen space) of
     37 * the changed frames, and compute a rect/region that contains all changed
     38 * items. We then build a display list just for this subset of the screen and
     39 * merge it into the display list from last paint.
     40 *
     41 * Any items that exist in one list and not the other must not have a defined
     42 * ordering in the DAG, since they need to intersect to have an ordering and
     43 * we would have built both in the new list if they intersected. Given that, we
     44 * can align items that appear in both lists, and any items that appear between
     45 * matched items can be inserted into the merged list in any order.
     46 *
     47 * Frames that are a stacking context, containing blocks for position:fixed
     48 * descendants, and don't have any continuations (see
     49 * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with
     50 * separate retaining decisions made.
     51 *
     52 * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest
     53 * ancestor frame which can be moved asynchronously on the compositor thread.
     54 * These are currently nsDisplayItems which return true from CanMoveAsync
     55 * (animated nsDisplayTransform and nsDisplayStickyPosition) and
     56 * ActiveScrolledRoots.
     57 *
     58 * For each context that we run the retaining algorithm, there can only be
     59 * mutations to one AnimatedGeometryRoot. This is because we are unable to
     60 * reason about intersections of items that might then move relative to each
     61 * other without RDL running again. If there are mutations to multiple
     62 * AnimatedGeometryRoots, then we bail out and rebuild all the items in the
     63 * context.
     64 *
     65 * Otherwise, when mutations are restricted to a single AGR, we pre-process the
     66 * old display list and mark the frames for all existing (unmodified!) items
     67 * that belong to a different AGR and ensure that we rebuild those items for
     68 * correct sorting with the modified ones.
     69 */
     70 
     71 namespace mozilla {
     72 
     73 RetainedDisplayListData::RetainedDisplayListData()
     74    : mModifiedFrameLimit(
     75          StaticPrefs::layout_display_list_rebuild_frame_limit()) {}
     76 
     77 void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) {
     78  MOZ_ASSERT(!aFrame->IsFrameModified());
     79  Flags(aFrame) += RetainedDisplayListData::FrameFlag::Modified;
     80  aFrame->SetFrameIsModified(true);
     81  mModifiedFrameCount++;
     82 }
     83 
     84 static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) {
     85  for (nsDisplayItem* i : *aList) {
     86    if (!i->HasDeletedFrame() && i->CanBeReused() &&
     87        !i->Frame()->IsFrameModified()) {
     88      // If we have existing cached geometry for this item, then check that for
     89      // whether we need to invalidate for a sync decode. If we don't, then
     90      // use the item's flags.
     91      // XXX: handle webrender case by looking up retained data for the item
     92      // and checking InvalidateForSyncDecodeImages
     93      bool invalidate = false;
     94      if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
     95        invalidate = true;
     96      }
     97 
     98      if (invalidate) {
     99        DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name());
    100        i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
    101        if (i->GetDependentFrame()) {
    102          i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
    103        }
    104      }
    105    }
    106    if (i->GetChildren()) {
    107      MarkFramesWithItemsAndImagesModified(i->GetChildren());
    108    }
    109  }
    110 }
    111 
    112 static nsIFrame* SelectAGRForFrame(nsIFrame* aFrame, nsIFrame* aParentAGR) {
    113  if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) {
    114    return aParentAGR;
    115  }
    116 
    117  if (!aFrame->HasOverrideDirtyRegion()) {
    118    return nullptr;
    119  }
    120 
    121  nsDisplayListBuilder::DisplayListBuildingData* data =
    122      aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
    123 
    124  return data && data->mModifiedAGR ? data->mModifiedAGR : nullptr;
    125 }
    126 
    127 void RetainedDisplayListBuilder::AddSizeOfIncludingThis(
    128    nsWindowSizes& aSizes) const {
    129  aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this);
    130  mBuilder.AddSizeOfExcludingThis(aSizes);
    131  mList.AddSizeOfExcludingThis(aSizes);
    132 }
    133 
    134 bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
    135  nsIFrame* f = aFrame;
    136  while (f) {
    137    if (f->IsFrameModified()) {
    138      return true;
    139    }
    140 
    141    if (aStopAtFrame && f == aStopAtFrame) {
    142      break;
    143    }
    144 
    145    f = nsLayoutUtils::GetDisplayListParent(f);
    146  }
    147 
    148  return false;
    149 }
    150 
    151 // Removes any display items that belonged to a frame that was deleted,
    152 // and mark frames that belong to a different AGR so that get their
    153 // items built again.
    154 // TODO: We currently descend into all children even if we don't have an AGR
    155 // to mark, as child stacking contexts might. It would be nice if we could
    156 // jump into those immediately rather than walking the entire thing.
    157 bool RetainedDisplayListBuilder::PreProcessDisplayList(
    158    RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated,
    159    nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR,
    160    nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth,
    161    bool aKeepLinked) {
    162  // The DAG merging algorithm does not have strong mechanisms in place to keep
    163  // the complexity of the resulting DAG under control. In some cases we can
    164  // build up edges very quickly. Detect those cases and force a full display
    165  // list build if we hit them.
    166  static const uint32_t kMaxEdgeRatio = 5;
    167  const bool initializeDAG = !aList->mDAG.Length();
    168  if (!aKeepLinked && !initializeDAG &&
    169      aList->mDAG.mDirectPredecessorList.Length() >
    170          (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) {
    171    return false;
    172  }
    173 
    174  // If we had aKeepLinked=true for this list on the previous paint, then
    175  // mOldItems will already be initialized as it won't have been consumed during
    176  // a merge.
    177  const bool initializeOldItems = aList->mOldItems.IsEmpty();
    178  if (initializeOldItems) {
    179    aList->mOldItems.SetCapacity(aList->Length());
    180  } else {
    181    MOZ_RELEASE_ASSERT(!initializeDAG);
    182  }
    183 
    184  MOZ_RELEASE_ASSERT(
    185      initializeDAG ||
    186      aList->mDAG.Length() ==
    187          (initializeOldItems ? aList->Length() : aList->mOldItems.Length()));
    188 
    189  nsDisplayList out(Builder());
    190 
    191  size_t i = 0;
    192  while (nsDisplayItem* item = aList->RemoveBottom()) {
    193 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    194    item->SetMergedPreProcessed(false, true);
    195 #endif
    196 
    197    // If we have a previously initialized old items list, then it can differ
    198    // from the current list due to items removed for having a deleted frame.
    199    // We can't easily remove these, since the DAG has entries for those indices
    200    // and it's hard to rewrite in-place.
    201    // Skip over entries with no current item to keep the iterations in sync.
    202    if (!initializeOldItems) {
    203      while (!aList->mOldItems[i].mItem) {
    204        i++;
    205      }
    206    }
    207 
    208    if (initializeDAG) {
    209      if (i == 0) {
    210        aList->mDAG.AddNode(Span<const MergedListIndex>());
    211      } else {
    212        MergedListIndex previous(i - 1);
    213        aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
    214      }
    215    }
    216 
    217    if (!item->CanBeReused() || item->HasDeletedFrame() ||
    218        AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) {
    219      if (initializeOldItems) {
    220        aList->mOldItems.AppendElement(OldItemInfo(nullptr));
    221      } else {
    222        MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item);
    223        aList->mOldItems[i].mItem = nullptr;
    224      }
    225 
    226      item->Destroy(&mBuilder);
    227      Metrics()->mRemovedItems++;
    228 
    229      i++;
    230      aUpdated = PartialUpdateResult::Updated;
    231      continue;
    232    }
    233 
    234    if (initializeOldItems) {
    235      aList->mOldItems.AppendElement(OldItemInfo(item));
    236    }
    237 
    238    // If we're not going to keep the list linked, then this old item entry
    239    // is the only pointer to the item. Let it know that it now strongly
    240    // owns the item, so it can destroy it if it goes away.
    241    aList->mOldItems[i].mOwnsItem = !aKeepLinked;
    242 
    243    item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth);
    244 
    245    nsIFrame* f = item->Frame();
    246 
    247    if (item->GetChildren()) {
    248      // If children inside this list were invalid, then we'd have walked the
    249      // ancestors and set ForceDescendIntoVisible on the current frame. If an
    250      // ancestor is modified, then we'll throw this away entirely. Either way,
    251      // we won't need to run merging on this sublist, and we can keep the items
    252      // linked into their display list.
    253      // The caret can move without invalidating, but we always set the force
    254      // descend into frame state bit on that frame, so check for that too.
    255      // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make
    256      // us think future siblings need to be merged, even though we don't really
    257      // need to.
    258      bool keepLinked = aKeepLinked;
    259      nsIFrame* invalid = item->FrameForInvalidation();
    260      if (!invalid->ForceDescendIntoIfVisible() &&
    261          !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
    262        keepLinked = true;
    263      }
    264 
    265      // If this item's frame is an AGR (can be moved asynchronously by the
    266      // compositor), then use that frame for descendants. Also pass the ASR
    267      // for that item, so that descendants can compare to see if any new
    268      // ASRs have been pushed since.
    269      nsIFrame* asyncAncestor = aAsyncAncestor;
    270      const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR;
    271      if (item->CanMoveAsync()) {
    272        asyncAncestor = item->Frame();
    273        asyncAncestorASR = item->GetActiveScrolledRoot();
    274      }
    275 
    276      if (!PreProcessDisplayList(
    277              item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated,
    278              asyncAncestor, asyncAncestorASR, item->Frame(),
    279              item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) {
    280        MOZ_RELEASE_ASSERT(
    281            !aKeepLinked,
    282            "Can't early return since we need to move the out list back");
    283        return false;
    284      }
    285    }
    286 
    287    // TODO: We should be able to check the clipped bounds relative
    288    // to the common AGR (of both the existing item and the invalidated
    289    // frame) and determine if they can ever intersect.
    290    // TODO: We only really need to build the ancestor container item that is a
    291    // sibling of the changed thing to get correct ordering. The changed content
    292    // is a frame though, and it's hard to map that to container items in this
    293    // list.
    294    // If an ancestor display item is an AGR, and our ASR matches the ASR
    295    // of that item, then there can't have been any new ASRs pushed since that
    296    // item, so that item is our AGR. Otherwise, our AGR is our ASR.
    297    // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot
    298    // should be the same or a descendant and also non-null. Unfortunately an
    299    // RDL bug means this can be wrong for sticky items after a partial update,
    300    // so we have to work around it. Bug 1730749 and bug 1730826 should resolve
    301    // this.
    302    nsIFrame* agrFrame = nullptr;
    303    const ActiveScrolledRoot* asr = item->GetNearestScrollASR();
    304    if (aAsyncAncestorASR == asr || !asr) {
    305      agrFrame = aAsyncAncestor;
    306    } else {
    307      auto* scrollContainerFrame = asr->ScrollFrame();
    308      if (MOZ_UNLIKELY(!scrollContainerFrame)) {
    309        MOZ_DIAGNOSTIC_ASSERT(false);
    310        gfxCriticalNoteOnce << "Found null mScrollContainerFrame in asr";
    311        return false;
    312      }
    313      agrFrame = scrollContainerFrame->GetScrolledFrame();
    314    }
    315 
    316    if (aAGR && agrFrame != aAGR) {
    317      mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame());
    318    }
    319 
    320    // If we're going to keep this linked list and not merge it, then mark the
    321    // item as used and put it back into the list.
    322    if (aKeepLinked) {
    323      item->SetReused(true);
    324      if (item->GetChildren()) {
    325        item->UpdateBounds(Builder());
    326      }
    327      if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
    328        IncrementSubDocPresShellPaintCount(item);
    329      }
    330      out.AppendToTop(item);
    331    }
    332    i++;
    333  }
    334 
    335  MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length());
    336 
    337  if (aKeepLinked) {
    338    aList->AppendToTop(&out);
    339  }
    340 
    341  return true;
    342 }
    343 
    344 void IncrementPresShellPaintCount(nsDisplayListBuilder* aBuilder,
    345                                  nsDisplayItem* aItem) {
    346  MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
    347 
    348  nsSubDocumentFrame* subDocFrame =
    349      static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
    350  MOZ_ASSERT(subDocFrame);
    351 
    352  PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
    353  MOZ_ASSERT(presShell);
    354 
    355  aBuilder->IncrementPresShellPaintCount(presShell);
    356 }
    357 
    358 void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(
    359    nsDisplayItem* aItem) {
    360  IncrementPresShellPaintCount(&mBuilder, aItem);
    361 }
    362 
    363 static Maybe<const ActiveScrolledRoot*> SelectContainerASR(
    364    const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR,
    365    Maybe<const ActiveScrolledRoot*>& aContainerASR) {
    366  const ActiveScrolledRoot* itemClipASR =
    367      aClipChain ? aClipChain->mASR : nullptr;
    368 
    369  MOZ_DIAGNOSTIC_ASSERT(!aClipChain || aClipChain->mOnStack || !itemClipASR ||
    370                        itemClipASR->mFrame);
    371 
    372  const ActiveScrolledRoot* finiteBoundsASR =
    373      ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR);
    374 
    375  if (!aContainerASR) {
    376    return Some(finiteBoundsASR);
    377  }
    378 
    379  return Some(
    380      ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR));
    381 }
    382 
    383 static void UpdateASR(nsDisplayItem* aItem,
    384                      Maybe<const ActiveScrolledRoot*>& aContainerASR) {
    385  const Maybe<const ActiveScrolledRoot*> frameASR =
    386      aItem->GetBaseASRForAncestorOfContainedASR();
    387  if (!frameASR) {
    388    return;
    389  }
    390 
    391  if (!aContainerASR) {
    392    aItem->SetActiveScrolledRoot(*frameASR);
    393    return;
    394  }
    395 
    396  aItem->SetActiveScrolledRoot(
    397      ActiveScrolledRoot::PickAncestor(*frameASR, *aContainerASR));
    398 }
    399 
    400 static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) {
    401  aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot());
    402 }
    403 
    404 OldItemInfo::OldItemInfo(nsDisplayItem* aItem)
    405    : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) {
    406  if (mItem) {
    407    // Clear cached modified frame state when adding an item to the old list.
    408    mItem->SetModifiedFrame(false);
    409  }
    410 }
    411 
    412 void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
    413                                         MergedListIndex aIndex) {
    414  AddedToMergedList(aIndex);
    415 }
    416 
    417 void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder,
    418                          nsTArray<MergedListIndex>&& aDirectPredecessors) {
    419  MOZ_ASSERT(!IsUsed());
    420  mUsed = mDiscarded = true;
    421  mDirectPredecessors = std::move(aDirectPredecessors);
    422  if (mItem) {
    423    MOZ_ASSERT(mOwnsItem);
    424    mItem->Destroy(aBuilder->Builder());
    425    aBuilder->Metrics()->mRemovedItems++;
    426  }
    427  mItem = nullptr;
    428 }
    429 
    430 bool OldItemInfo::IsChanged() {
    431  return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame();
    432 }
    433 
    434 /**
    435 * A C++ implementation of Markus Stange's merge-dags algorithm.
    436 * https://github.com/mstange/merge-dags
    437 *
    438 * MergeState handles combining a new list of display items into an existing
    439 * DAG and computes the new DAG in a single pass.
    440 * Each time we add a new item, we resolve all dependencies for it, so that the
    441 * resulting list and DAG are built in topological ordering.
    442 */
    443 class MergeState {
    444 public:
    445  MergeState(RetainedDisplayListBuilder* aBuilder,
    446             RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem)
    447      : mBuilder(aBuilder),
    448        mOldList(&aOldList),
    449        mOldItems(std::move(aOldList.mOldItems)),
    450        mOldDAG(
    451            std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(
    452                &aOldList.mDAG))),
    453        mMergedItems(aBuilder->Builder()),
    454        mOuterItem(aOuterItem),
    455        mResultIsModified(false) {
    456    mMergedDAG.EnsureCapacityFor(mOldDAG);
    457    MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length());
    458  }
    459 
    460  Maybe<MergedListIndex> ProcessItemFromNewList(
    461      nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) {
    462    OldListIndex oldIndex;
    463    MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() ==
    464                          HasModifiedFrame(aNewItem));
    465    if (!aNewItem->HasModifiedFrame() &&
    466        HasMatchingItemInOldList(aNewItem, &oldIndex)) {
    467      mBuilder->Metrics()->mRebuiltItems++;
    468      nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
    469      MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() ==
    470                                aNewItem->GetPerFrameKey() &&
    471                            oldItem->Frame() == aNewItem->Frame());
    472      if (!mOldItems[oldIndex.val].IsChanged()) {
    473        MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
    474        nsDisplayItem* destItem;
    475        if (ShouldUseNewItem(aNewItem)) {
    476          destItem = aNewItem;
    477        } else {
    478          destItem = oldItem;
    479          // The building rect can depend on the overflow rect (when the parent
    480          // frame is position:fixed), which can change without invalidating
    481          // the frame/items. If we're using the old item, copy the building
    482          // rect across from the new item.
    483          oldItem->SetBuildingRect(aNewItem->GetBuildingRect());
    484        }
    485 
    486        MergeChildLists(aNewItem, oldItem, destItem);
    487 
    488        AutoTArray<MergedListIndex, 2> directPredecessors =
    489            ProcessPredecessorsOfOldNode(oldIndex);
    490        MergedListIndex newIndex = AddNewNode(
    491            destItem, Some(oldIndex), directPredecessors, aPreviousItem);
    492        mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex);
    493        if (destItem == aNewItem) {
    494          oldItem->Destroy(mBuilder->Builder());
    495        } else {
    496          aNewItem->Destroy(mBuilder->Builder());
    497        }
    498        return Some(newIndex);
    499      }
    500    }
    501    mResultIsModified = true;
    502    return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(),
    503                           aPreviousItem));
    504  }
    505 
    506  void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem,
    507                       nsDisplayItem* aOutItem) {
    508    if (!aOutItem->GetChildren()) {
    509      return;
    510    }
    511 
    512    Maybe<const ActiveScrolledRoot*> containerASRForChildren;
    513    nsDisplayList empty(mBuilder->Builder());
    514    const bool modified = mBuilder->MergeDisplayLists(
    515        aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(),
    516        aOutItem->GetChildren(), containerASRForChildren, aOutItem);
    517    if (modified) {
    518      aOutItem->InvalidateCachedChildInfo(mBuilder->Builder());
    519      UpdateASR(aOutItem, containerASRForChildren);
    520      mResultIsModified = true;
    521    } else if (aOutItem == aNewItem) {
    522      // If nothing changed, but we copied the contents across to
    523      // the new item, then also copy the ASR data.
    524      CopyASR(aOldItem, aNewItem);
    525    }
    526    // Ideally we'd only UpdateBounds if something changed, but
    527    // nsDisplayWrapList also uses this to update the clip chain for the
    528    // current ASR, which gets reset during RestoreState(), so we always need
    529    // to run it again.
    530    aOutItem->UpdateBounds(mBuilder->Builder());
    531 
    532    if (aOutItem->GetType() == DisplayItemType::TYPE_TRANSFORM) {
    533      MOZ_ASSERT(!aNewItem ||
    534                 aNewItem->GetType() == DisplayItemType::TYPE_TRANSFORM);
    535      MOZ_ASSERT(aOldItem->GetType() == DisplayItemType::TYPE_TRANSFORM);
    536      static_cast<nsDisplayTransform*>(aOutItem)->SetContainsASRs(
    537          static_cast<nsDisplayTransform*>(aOldItem)->GetContainsASRs() ||
    538          (aNewItem
    539               ? static_cast<nsDisplayTransform*>(aNewItem)->GetContainsASRs()
    540               : false));
    541    }
    542  }
    543 
    544  bool ShouldUseNewItem(nsDisplayItem* aNewItem) {
    545    // Generally we want to use the old item when the frame isn't marked as
    546    // modified so that any cached information on the item (or referencing the
    547    // item) gets retained. Quite a few FrameLayerBuilder performance
    548    // improvements benefit by this. Sometimes, however, we can end up where the
    549    // new item paints something different from the old item, even though we
    550    // haven't modified the frame, and it's hard to fix. In these cases we just
    551    // always use the new item to be safe.
    552    DisplayItemType type = aNewItem->GetType();
    553    if (type == DisplayItemType::TYPE_SOLID_COLOR) {
    554      // The canvas background color item can paint the color from another
    555      // frame, and even though we schedule a paint, we don't mark the canvas
    556      // frame as invalid.
    557      return true;
    558    }
    559 
    560    if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) {
    561      // We intentionally don't mark the root table frame as modified when a
    562      // subframe changes, even though the border collapse item for the root
    563      // frame is what paints the changed border. Marking the root frame as
    564      // modified would rebuild display items for the whole table area, and we
    565      // don't want that.
    566      return true;
    567    }
    568 
    569    if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) {
    570      // Text overflow marker items are created with the wrapping block as their
    571      // frame, and have an index value to note which line they are created for.
    572      // Their rendering can change if the items on that line change, which may
    573      // not mark the block as modified. We rebuild them if we build any item on
    574      // the line, so we should always get new items if they might have changed
    575      // rendering, and it's easier to just use the new items rather than
    576      // computing if we actually need them.
    577      return true;
    578    }
    579 
    580    if (type == DisplayItemType::TYPE_SUBDOCUMENT ||
    581        type == DisplayItemType::TYPE_STICKY_POSITION) {
    582      // nsDisplaySubDocument::mShouldFlatten can change without an invalidation
    583      // (and is the reason we unconditionally build the subdocument item), so
    584      // always use the new one to make sure we get the right value.
    585      // Same for |nsDisplayStickyPosition::mShouldFlatten|.
    586      return true;
    587    }
    588 
    589    if (type == DisplayItemType::TYPE_CARET) {
    590      // The caret can change position while still being owned by the same frame
    591      // and we don't invalidate in that case. Use the new version since the
    592      // changed bounds are needed for DLBI.
    593      return true;
    594    }
    595 
    596    if (type == DisplayItemType::TYPE_MASK ||
    597        type == DisplayItemType::TYPE_FILTER ||
    598        type == DisplayItemType::TYPE_SVG_WRAPPER) {
    599      // SVG items have some invalidation issues, see bugs 1494110 and 1494663.
    600      return true;
    601    }
    602 
    603    if (type == DisplayItemType::TYPE_TRANSFORM) {
    604      // Prerendering of transforms can change without frame invalidation.
    605      return true;
    606    }
    607 
    608    return false;
    609  }
    610 
    611  RetainedDisplayList Finalize() {
    612    for (size_t i = 0; i < mOldDAG.Length(); i++) {
    613      if (mOldItems[i].IsUsed()) {
    614        continue;
    615      }
    616 
    617      AutoTArray<MergedListIndex, 2> directPredecessors =
    618          ResolveNodeIndexesOldToMerged(
    619              mOldDAG.GetDirectPredecessors(OldListIndex(i)));
    620      ProcessOldNode(OldListIndex(i), std::move(directPredecessors));
    621    }
    622 
    623    RetainedDisplayList result(mBuilder->Builder());
    624    result.AppendToTop(&mMergedItems);
    625    result.mDAG = std::move(mMergedDAG);
    626    MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Length());
    627    return result;
    628  }
    629 
    630  bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) {
    631    // Look for an item that matches aItem's frame and per-frame-key, but isn't
    632    // the same item.
    633    uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0;
    634    nsIFrame* frame = aItem->Frame();
    635    for (nsDisplayItem* i : frame->DisplayItems()) {
    636      if (i != aItem && i->Frame() == frame &&
    637          i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
    638        if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) {
    639          return true;
    640        }
    641      }
    642    }
    643    return false;
    644  }
    645 
    646 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    647  bool HasModifiedFrame(nsDisplayItem* aItem) {
    648    nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr;
    649    return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame);
    650  }
    651 #endif
    652 
    653  void UpdateContainerASR(nsDisplayItem* aItem) {
    654    mContainerASR = SelectContainerASR(
    655        aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR);
    656  }
    657 
    658  MergedListIndex AddNewNode(
    659      nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex,
    660      Span<const MergedListIndex> aDirectPredecessors,
    661      const Maybe<MergedListIndex>& aExtraDirectPredecessor) {
    662    if (aItem->GetType() != DisplayItemType::TYPE_VT_CAPTURE) {
    663      UpdateContainerASR(aItem);
    664    }
    665    aItem->NotifyUsed(mBuilder->Builder());
    666 
    667 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    668    for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) {
    669      if (i->Frame() == aItem->Frame() &&
    670          i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
    671        MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem());
    672      }
    673    }
    674 
    675    aItem->SetMergedPreProcessed(true, false);
    676 #endif
    677 
    678    mMergedItems.AppendToTop(aItem);
    679    mBuilder->Metrics()->mTotalItems++;
    680 
    681    MergedListIndex newIndex =
    682        mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
    683    return newIndex;
    684  }
    685 
    686  void ProcessOldNode(OldListIndex aNode,
    687                      nsTArray<MergedListIndex>&& aDirectPredecessors) {
    688    nsDisplayItem* item = mOldItems[aNode.val].mItem;
    689    if (mOldItems[aNode.val].IsChanged()) {
    690      mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors));
    691      mResultIsModified = true;
    692    } else {
    693      MergeChildLists(nullptr, item, item);
    694 
    695      if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
    696        mBuilder->IncrementSubDocPresShellPaintCount(item);
    697      }
    698      item->SetReused(true);
    699      mBuilder->Metrics()->mReusedItems++;
    700      mOldItems[aNode.val].AddedToMergedList(
    701          AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing()));
    702    }
    703  }
    704 
    705  struct PredecessorStackItem {
    706    PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors)
    707        : mNode(aNode),
    708          mDirectPredecessors(aPredecessors),
    709          mCurrentPredecessorIndex(0) {}
    710 
    711    bool IsFinished() {
    712      return mCurrentPredecessorIndex == mDirectPredecessors.Length();
    713    }
    714 
    715    OldListIndex GetAndIncrementCurrentPredecessor() {
    716      return mDirectPredecessors[mCurrentPredecessorIndex++];
    717    }
    718 
    719    OldListIndex mNode;
    720    Span<OldListIndex> mDirectPredecessors;
    721    size_t mCurrentPredecessorIndex;
    722  };
    723 
    724  AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode(
    725      OldListIndex aNode) {
    726    AutoTArray<PredecessorStackItem, 256> mStack;
    727    mStack.AppendElement(
    728        PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode)));
    729 
    730    while (true) {
    731      if (mStack.LastElement().IsFinished()) {
    732        // If we've finished processing all the entries in the current set, then
    733        // pop it off the processing stack and process it.
    734        PredecessorStackItem item = mStack.PopLastElement();
    735        AutoTArray<MergedListIndex, 2> result =
    736            ResolveNodeIndexesOldToMerged(item.mDirectPredecessors);
    737 
    738        if (mStack.IsEmpty()) {
    739          return result;
    740        }
    741 
    742        ProcessOldNode(item.mNode, std::move(result));
    743      } else {
    744        // Grab the current predecessor, push predecessors of that onto the
    745        // processing stack (if it hasn't already been processed), and then
    746        // advance to the next entry.
    747        OldListIndex currentIndex =
    748            mStack.LastElement().GetAndIncrementCurrentPredecessor();
    749        if (!mOldItems[currentIndex.val].IsUsed()) {
    750          mStack.AppendElement(PredecessorStackItem(
    751              currentIndex, mOldDAG.GetDirectPredecessors(currentIndex)));
    752        }
    753      }
    754    }
    755  }
    756 
    757  AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged(
    758      Span<OldListIndex> aDirectPredecessors) {
    759    AutoTArray<MergedListIndex, 2> result;
    760    result.SetCapacity(aDirectPredecessors.Length());
    761    for (OldListIndex index : aDirectPredecessors) {
    762      OldItemInfo& oldItem = mOldItems[index.val];
    763      if (oldItem.IsDiscarded()) {
    764        for (MergedListIndex inner : oldItem.mDirectPredecessors) {
    765          if (!result.Contains(inner)) {
    766            result.AppendElement(inner);
    767          }
    768        }
    769      } else {
    770        result.AppendElement(oldItem.mIndex);
    771      }
    772    }
    773    return result;
    774  }
    775 
    776  RetainedDisplayListBuilder* mBuilder;
    777  RetainedDisplayList* mOldList;
    778  Maybe<const ActiveScrolledRoot*> mContainerASR;
    779  nsTArray<OldItemInfo> mOldItems;
    780  DirectedAcyclicGraph<OldListUnits> mOldDAG;
    781  // Unfortunately we can't use strong typing for the hashtables
    782  // since they internally encode the type with the mOps pointer,
    783  // and assert when we try swap the contents
    784  nsDisplayList mMergedItems;
    785  DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
    786  nsDisplayItem* mOuterItem;
    787  bool mResultIsModified;
    788 };
    789 
    790 #ifdef DEBUG
    791 void VerifyNotModified(nsDisplayList* aList) {
    792  for (nsDisplayItem* item : *aList) {
    793    MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation()));
    794 
    795    if (item->GetChildren()) {
    796      VerifyNotModified(item->GetChildren());
    797    }
    798  }
    799 }
    800 #endif
    801 
    802 /**
    803 * Takes two display lists and merges them into an output list.
    804 *
    805 * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a
    806 * maximum of one direct predecessor and one direct successor per node). We add
    807 * the two DAGs together, and then output the topological sorted ordering as the
    808 * final display list.
    809 *
    810 * Once we've merged a list, we then retain the DAG (as part of the
    811 * RetainedDisplayList object) to use for future merges.
    812 */
    813 bool RetainedDisplayListBuilder::MergeDisplayLists(
    814    nsDisplayList* aNewList, RetainedDisplayList* aOldList,
    815    RetainedDisplayList* aOutList,
    816    mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
    817    nsDisplayItem* aOuterItem) {
    818  AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging);
    819 
    820  if (!aOldList->IsEmpty()) {
    821    // If we still have items in the actual list, then it is because
    822    // PreProcessDisplayList decided that it was sure it can't be modified. We
    823    // can just use it directly, and throw any new items away.
    824 
    825    aNewList->DeleteAll(&mBuilder);
    826 #ifdef DEBUG
    827    VerifyNotModified(aOldList);
    828 #endif
    829 
    830    if (aOldList != aOutList) {
    831      *aOutList = std::move(*aOldList);
    832    }
    833 
    834    return false;
    835  }
    836 
    837  MergeState merge(this, *aOldList, aOuterItem);
    838 
    839  Maybe<MergedListIndex> previousItemIndex;
    840  for (nsDisplayItem* item : aNewList->TakeItems()) {
    841    Metrics()->mNewItems++;
    842    previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex);
    843  }
    844 
    845  *aOutList = merge.Finalize();
    846  aOutContainerASR = merge.mContainerASR;
    847  return merge.mResultIsModified;
    848 }
    849 
    850 void RetainedDisplayListBuilder::GetModifiedAndFramesWithProps(
    851    nsTArray<nsIFrame*>* aOutModifiedFrames,
    852    nsTArray<nsIFrame*>* aOutFramesWithProps) {
    853  for (auto it = Data()->ConstIterator(); !it.Done(); it.Next()) {
    854    nsIFrame* frame = it.Key();
    855    const RetainedDisplayListData::FrameFlags& flags = it.Data();
    856 
    857    if (flags.contains(RetainedDisplayListData::FrameFlag::Modified)) {
    858      aOutModifiedFrames->AppendElement(frame);
    859    }
    860 
    861    if (flags.contains(RetainedDisplayListData::FrameFlag::HasProps)) {
    862      aOutFramesWithProps->AppendElement(frame);
    863    }
    864 
    865    if (flags.contains(RetainedDisplayListData::FrameFlag::HadWillChange)) {
    866      Builder()->RemoveFromWillChangeBudgets(frame);
    867    }
    868  }
    869 
    870  Data()->Clear();
    871 }
    872 
    873 // ComputeRebuildRegion  debugging
    874 // #define CRR_DEBUG 1
    875 #if CRR_DEBUG
    876 #  define CRR_LOG(...) printf_stderr(__VA_ARGS__)
    877 #else
    878 #  define CRR_LOG(...)
    879 #endif
    880 
    881 static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) {
    882  for (nsDisplayItem* i : aFrame->DisplayItems()) {
    883    if (i->HasDeletedFrame() || i->Frame() != aFrame) {
    884      // The main frame for the display item has been deleted or the display
    885      // item belongs to another frame.
    886      continue;
    887    }
    888 
    889    if (i->HasChildren()) {
    890      return static_cast<nsDisplayItem*>(i);
    891    }
    892  }
    893  return nullptr;
    894 }
    895 
    896 static bool IsInPreserve3DContext(const nsIFrame* aFrame) {
    897  return aFrame->Extend3DContext() ||
    898         aFrame->Combines3DTransformWithAncestors();
    899 }
    900 
    901 // Returns true if |aFrame| can store a display list building rect.
    902 // These limitations are necessary to guarantee that
    903 // 1) Just enough items are rebuilt to properly update display list
    904 // 2) Modified frames will be visited during a partial display list build.
    905 static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder,
    906                                            nsIFrame* aFrame) {
    907  return aFrame != aBuilder->RootReferenceFrame() &&
    908         aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() &&
    909         // Split frames might have placeholders for modified frames in their
    910         // unmodified continuation frame.
    911         !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation();
    912 }
    913 
    914 static bool ProcessFrameInternal(nsIFrame* aFrame,
    915                                 nsDisplayListBuilder* aBuilder,
    916                                 nsIFrame** aAGR, nsRect& aOverflow,
    917                                 const nsIFrame* aStopAtFrame,
    918                                 nsTArray<nsIFrame*>& aOutFramesWithProps,
    919                                 const bool aStopAtStackingContext) {
    920  nsIFrame* currentFrame = aFrame;
    921 
    922  while (currentFrame != aStopAtFrame) {
    923    CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
    924            currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y,
    925            aOverflow.width, aOverflow.height);
    926 
    927    // If the current frame is an OOF frame, DisplayListBuildingData needs to be
    928    // set on all the ancestor stacking contexts of the  placeholder frame, up
    929    // to the containing block of the OOF frame. This is done to ensure that the
    930    // content that might be behind the OOF frame is built for merging.
    931    nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
    932                                ? currentFrame->GetPlaceholderFrame()
    933                                : nullptr;
    934 
    935    if (placeholder) {
    936      nsRect placeholderOverflow = aOverflow;
    937      auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder,
    938                                             placeholderOverflow);
    939      if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
    940        placeholderOverflow = nsRect();
    941      }
    942 
    943      CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder,
    944              currentFrame);
    945 
    946      CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x,
    947              placeholderOverflow.y, placeholderOverflow.width,
    948              placeholderOverflow.height);
    949 
    950      // Tracking AGRs for the placeholder processing is not necessary, as the
    951      // goal is to only modify the DisplayListBuildingData rect.
    952      nsIFrame* dummyAGR = nullptr;
    953 
    954      // Find a common ancestor frame to handle frame continuations.
    955      // TODO: It might be possible to write a more specific and efficient
    956      // function for this.
    957      const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame(
    958          currentFrame->GetParent(), placeholder->GetParent());
    959 
    960      if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR,
    961                                placeholderOverflow, ancestor,
    962                                aOutFramesWithProps, false)) {
    963        return false;
    964      }
    965    }
    966 
    967    // Convert 'aOverflow' into the coordinate space of the nearest stacking
    968    // context or display port ancestor and update 'currentFrame' to point to
    969    // that frame.
    970    aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(
    971        currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr,
    972        /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
    973        &currentFrame);
    974    if (IsInPreserve3DContext(currentFrame)) {
    975      return false;
    976    }
    977 
    978    MOZ_ASSERT(currentFrame);
    979 
    980    // Check whether the current frame is a scrollable frame with display port.
    981    nsRect displayPort;
    982    ScrollContainerFrame* sf = do_QueryFrame(currentFrame);
    983    nsIContent* content = sf ? currentFrame->GetContent() : nullptr;
    984 
    985    if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) {
    986      CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
    987 
    988      // Get overflow relative to the scrollport (from the scrollframe)
    989      nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft();
    990      r.IntersectRect(r, displayPort);
    991      if (!r.IsEmpty()) {
    992        nsRect* rect = currentFrame->GetProperty(
    993            nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
    994        if (!rect) {
    995          rect = new nsRect();
    996          currentFrame->SetProperty(
    997              nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
    998          currentFrame->SetHasOverrideDirtyRegion(true);
    999          aOutFramesWithProps.AppendElement(currentFrame);
   1000        }
   1001        rect->UnionRect(*rect, r);
   1002        CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y,
   1003                r.width, r.height);
   1004 
   1005        // TODO: Can we just use MarkFrameForDisplayIfVisible, plus
   1006        // MarkFramesForDifferentAGR to ensure that this displayport, plus any
   1007        // items that move relative to it get rebuilt, and then not contribute
   1008        // to the root dirty area?
   1009        aOverflow = sf->GetScrollPortRect();
   1010      } else {
   1011        // Don't contribute to the root dirty area at all.
   1012        aOverflow.SetEmpty();
   1013      }
   1014    } else {
   1015      aOverflow.IntersectRect(aOverflow,
   1016                              currentFrame->InkOverflowRectRelativeToSelf());
   1017    }
   1018 
   1019    if (aOverflow.IsEmpty()) {
   1020      break;
   1021    }
   1022 
   1023    if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) {
   1024      CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
   1025      // If we found an intermediate stacking context with an existing display
   1026      // item then we can store the dirty rect there and stop. If we couldn't
   1027      // find one then we need to keep bubbling up to the next stacking context.
   1028      nsDisplayItem* wrapperItem =
   1029          GetFirstDisplayItemWithChildren(currentFrame);
   1030      if (!wrapperItem) {
   1031        continue;
   1032      }
   1033 
   1034      // Store the stacking context relative dirty area such
   1035      // that display list building will pick it up when it
   1036      // gets to it.
   1037      nsDisplayListBuilder::DisplayListBuildingData* data =
   1038          currentFrame->GetProperty(
   1039              nsDisplayListBuilder::DisplayListBuildingRect());
   1040      if (!data) {
   1041        data = new nsDisplayListBuilder::DisplayListBuildingData();
   1042        currentFrame->SetProperty(
   1043            nsDisplayListBuilder::DisplayListBuildingRect(), data);
   1044        currentFrame->SetHasOverrideDirtyRegion(true);
   1045        aOutFramesWithProps.AppendElement(currentFrame);
   1046      }
   1047      CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
   1048              aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
   1049      data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
   1050 
   1051      if (!aStopAtStackingContext) {
   1052        // Continue ascending the frame tree until we reach aStopAtFrame.
   1053        continue;
   1054      }
   1055 
   1056      // Grab the visible (display list building) rect for children of this
   1057      // wrapper item and convert into into coordinate relative to the current
   1058      // frame.
   1059      nsRect previousVisible = wrapperItem->GetBuildingRectForChildren();
   1060      if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) {
   1061        previousVisible -= wrapperItem->ToReferenceFrame();
   1062      }
   1063 
   1064      if (!previousVisible.Contains(aOverflow)) {
   1065        // If the overflow area of the changed frame isn't contained within the
   1066        // old item, then we might change the size of the item and need to
   1067        // update its sorting accordingly. Keep propagating the overflow area up
   1068        // so that we build intersecting items for sorting.
   1069        continue;
   1070      }
   1071 
   1072      if (!data->mModifiedAGR) {
   1073        data->mModifiedAGR = *aAGR;
   1074      } else if (data->mModifiedAGR != *aAGR) {
   1075        data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf();
   1076        CRR_LOG(
   1077            "Found multiple modified AGRs within this stacking context, "
   1078            "giving up\n");
   1079      }
   1080 
   1081      // Don't contribute to the root dirty area at all.
   1082      aOverflow.SetEmpty();
   1083      *aAGR = nullptr;
   1084 
   1085      break;
   1086    }
   1087  }
   1088  return true;
   1089 }
   1090 
   1091 bool RetainedDisplayListBuilder::ProcessFrame(
   1092    nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame,
   1093    nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext,
   1094    nsRect* aOutDirty, nsIFrame** aOutModifiedAGR) {
   1095  if (aFrame->HasOverrideDirtyRegion()) {
   1096    aOutFramesWithProps.AppendElement(aFrame);
   1097  }
   1098 
   1099  if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
   1100    return true;
   1101  }
   1102 
   1103  // TODO: There is almost certainly a faster way of doing this, probably can be
   1104  // combined with the ancestor walk for TransformFrameRectToAncestor.
   1105  nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame);
   1106 
   1107  CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame);
   1108 
   1109  // Convert the frame's overflow rect into the coordinate space
   1110  // of the nearest stacking context that has an existing display item.
   1111  // We store that as a dirty rect on that stacking context so that we build
   1112  // all items that intersect the changed frame within the stacking context,
   1113  // and then we use MarkFrameForDisplayIfVisible to make sure the stacking
   1114  // context itself gets built. We don't need to build items that intersect
   1115  // outside of the stacking context, since we know the stacking context item
   1116  // exists in the old list, so we can trivially merge without needing other
   1117  // items.
   1118  nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
   1119 
   1120  // If the modified frame is also a caret frame, include the caret area.
   1121  // This is needed because some frames (for example text frames without text)
   1122  // might have an empty overflow rect.
   1123  if (aFrame == aBuilder->GetCaretFrame()) {
   1124    overflow.UnionRect(overflow, aBuilder->GetCaretRect());
   1125  }
   1126 
   1127  if (!ProcessFrameInternal(aFrame, aBuilder, &agrFrame, overflow, aStopAtFrame,
   1128                            aOutFramesWithProps, aStopAtStackingContext)) {
   1129    return false;
   1130  }
   1131 
   1132  if (!overflow.IsEmpty()) {
   1133    aOutDirty->UnionRect(*aOutDirty, overflow);
   1134    CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x,
   1135            overflow.y, overflow.width, overflow.height);
   1136 
   1137    // If we get changed frames from multiple AGRS, then just give up as it gets
   1138    // really complex to track which items would need to be marked in
   1139    // MarkFramesForDifferentAGR.
   1140    if (!*aOutModifiedAGR) {
   1141      CRR_LOG("Setting %p as root stacking context AGR\n", agrFrame);
   1142      *aOutModifiedAGR = agrFrame;
   1143    } else if (agrFrame && *aOutModifiedAGR != agrFrame) {
   1144      CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
   1145      return false;
   1146    }
   1147  }
   1148  return true;
   1149 }
   1150 
   1151 static void AddFramesForContainingBlock(nsIFrame* aBlock,
   1152                                        const nsFrameList& aFrames,
   1153                                        nsTArray<nsIFrame*>& aExtraFrames) {
   1154  for (nsIFrame* f : aFrames) {
   1155    if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) {
   1156      CRR_LOG("Adding invalid OOF %p\n", f);
   1157      aExtraFrames.AppendElement(f);
   1158    }
   1159  }
   1160 }
   1161 
   1162 // Placeholder descendants of aFrame don't contribute to aFrame's overflow area.
   1163 // Find all the containing blocks that might own placeholders under us, walk
   1164 // their OOF frames list, and manually invalidate any frames that are
   1165 // descendants of a modified frame (us, or another frame we'll get to soon).
   1166 // This is combined with the work required for MarkFrameForDisplayIfVisible,
   1167 // so that we can avoid an extra ancestor walk, and we can reuse the flag
   1168 // to detect when we've already visited an ancestor (and thus all further
   1169 // ancestors must also be visited).
   1170 static void FindContainingBlocks(nsIFrame* aFrame,
   1171                                 nsTArray<nsIFrame*>& aExtraFrames) {
   1172  for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
   1173    if (f->ForceDescendIntoIfVisible()) {
   1174      return;
   1175    }
   1176    f->SetForceDescendIntoIfVisible(true);
   1177    CRR_LOG("Considering OOFs for %p\n", f);
   1178 
   1179    AddFramesForContainingBlock(f, f->GetChildList(FrameChildListID::Float),
   1180                                aExtraFrames);
   1181    AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()),
   1182                                aExtraFrames);
   1183 
   1184    // This condition must match the condition in
   1185    // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
   1186    // nsLayoutUtils::GetDisplayListParent
   1187    if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
   1188      nsIFrame* parent = f->GetParent();
   1189      if (parent && !parent->ForceDescendIntoIfVisible()) {
   1190        // If the GetDisplayListParent call is going to walk to a placeholder,
   1191        // in rare cases the placeholder might be contained in a different
   1192        // continuation from the oof. So we have to make sure to mark the oofs
   1193        // parent. In the common case this doesn't make us do any extra work,
   1194        // just changes the order in which we visit the frames since walking
   1195        // through placeholders will walk through the parent, and we stop when
   1196        // we find a ForceDescendIntoIfVisible bit set.
   1197        FindContainingBlocks(parent, aExtraFrames);
   1198      }
   1199    }
   1200  }
   1201 }
   1202 
   1203 /**
   1204 * Given a list of frames that has been modified, computes the region that we
   1205 * need to do display list building for in order to build all modified display
   1206 * items.
   1207 *
   1208 * When a modified frame is within a stacking context (with an existing display
   1209 * item), then we only contribute to the build area within the stacking context,
   1210 * as well as forcing display list building to descend to the stacking context.
   1211 * We don't need to add build area outside of the stacking context (and force
   1212 * items above/below the stacking context container item to be built), since
   1213 * just matching the position of the stacking context container item is
   1214 * sufficient to ensure correct ordering during merging.
   1215 *
   1216 * We need to rebuild all items that might intersect with the modified frame,
   1217 * both now and during async changes on the compositor. We do this by rebuilding
   1218 * the area covered by the changed frame, as well as rebuilding all items that
   1219 * have a different (async) AGR to the changed frame. If we have changes to
   1220 * multiple AGRs (within a stacking context), then we rebuild that stacking
   1221 * context entirely.
   1222 *
   1223 * @param aModifiedFrames The list of modified frames.
   1224 * @param aOutDirty The result region to use for display list building.
   1225 * @param aOutModifiedAGR The modified AGR for the root stacking context.
   1226 * @param aOutFramesWithProps The list of frames to which we attached partial
   1227 * build data so that it can be cleaned up.
   1228 *
   1229 * @return true if we succesfully computed a partial rebuild region, false if a
   1230 * full build is required.
   1231 */
   1232 bool RetainedDisplayListBuilder::ComputeRebuildRegion(
   1233    nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty,
   1234    nsIFrame** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps) {
   1235  CRR_LOG("Computing rebuild regions for %zu frames:\n",
   1236          aModifiedFrames.Length());
   1237  nsTArray<nsIFrame*> extraFrames;
   1238  for (nsIFrame* f : aModifiedFrames) {
   1239    MOZ_ASSERT(f);
   1240 
   1241    mBuilder.AddFrameMarkedForDisplayIfVisible(f);
   1242    FindContainingBlocks(f, extraFrames);
   1243 
   1244    if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps,
   1245                      true, aOutDirty, aOutModifiedAGR)) {
   1246      return false;
   1247    }
   1248  }
   1249 
   1250  // Since we set modified to true on the extraFrames, add them to
   1251  // aModifiedFrames so that it will get reverted.
   1252  aModifiedFrames.AppendElements(extraFrames);
   1253 
   1254  for (nsIFrame* f : extraFrames) {
   1255    f->SetFrameIsModified(true);
   1256 
   1257    if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps,
   1258                      true, aOutDirty, aOutModifiedAGR)) {
   1259      return false;
   1260    }
   1261  }
   1262 
   1263  return true;
   1264 }
   1265 
   1266 bool RetainedDisplayListBuilder::ShouldBuildPartial(
   1267    nsTArray<nsIFrame*>& aModifiedFrames) {
   1268  // We don't support retaining with overlay scrollbars, since they require
   1269  // us to look at the display list and pick the highest z-index, which
   1270  // we can't do during partial building.
   1271  if (mBuilder.DisablePartialUpdates()) {
   1272    mBuilder.SetDisablePartialUpdates(false);
   1273    Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
   1274    return false;
   1275  }
   1276 
   1277  if (mList.IsEmpty()) {
   1278    // Partial builds without a previous display list do not make sense.
   1279    Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList;
   1280    return false;
   1281  }
   1282 
   1283  if (aModifiedFrames.Length() >
   1284      StaticPrefs::layout_display_list_rebuild_frame_limit()) {
   1285    // Computing a dirty rect with too many modified frames can be slow.
   1286    Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit;
   1287    return false;
   1288  }
   1289 
   1290  for (nsIFrame* f : aModifiedFrames) {
   1291    MOZ_ASSERT(f);
   1292 
   1293    const LayoutFrameType type = f->Type();
   1294 
   1295    // If we have any modified frames of the following types, it is likely that
   1296    // doing a partial rebuild of the display list will be slower than doing a
   1297    // full rebuild.
   1298    // This is because these frames either intersect or may intersect with most
   1299    // of the page content. This is either due to display port size or different
   1300    // async AGR.
   1301    if (type == LayoutFrameType::Viewport ||
   1302        type == LayoutFrameType::PageContent ||
   1303        type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) {
   1304      Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType;
   1305      return false;
   1306    }
   1307 
   1308    // Detect root scroll frame and do a full rebuild for them too for the same
   1309    // reasons as above, but also because top layer items should to be marked
   1310    // modified if the root scroll frame is modified. Putting this check here
   1311    // means we don't need to check everytime a frame is marked modified though.
   1312    if (type == LayoutFrameType::ScrollContainer && f->GetParent() &&
   1313        !f->GetParent()->GetParent()) {
   1314      Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType;
   1315      return false;
   1316    }
   1317  }
   1318 
   1319  return true;
   1320 }
   1321 
   1322 class AutoClearFramePropsArray {
   1323 public:
   1324  explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {}
   1325  AutoClearFramePropsArray() = default;
   1326  ~AutoClearFramePropsArray() {
   1327    size_t len = mFrames.Length();
   1328    nsIFrame** elements = mFrames.Elements();
   1329    for (size_t i = 0; i < len; ++i) {
   1330      nsIFrame* f = elements[i];
   1331      DL_LOGV("RDL - Clearing modified flags for frame %p", f);
   1332      if (f->HasOverrideDirtyRegion()) {
   1333        f->SetHasOverrideDirtyRegion(false);
   1334        f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect());
   1335        f->RemoveProperty(
   1336            nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
   1337      }
   1338      f->SetFrameIsModified(false);
   1339      f->SetHasModifiedDescendants(false);
   1340    }
   1341  }
   1342 
   1343  nsTArray<nsIFrame*>& Frames() { return mFrames; }
   1344  bool IsEmpty() const { return mFrames.IsEmpty(); }
   1345 
   1346 private:
   1347  nsTArray<nsIFrame*> mFrames;
   1348 };
   1349 
   1350 void RetainedDisplayListBuilder::ClearFramesWithProps() {
   1351  AutoClearFramePropsArray modifiedFrames(Data()->GetModifiedFrameCount());
   1352  AutoClearFramePropsArray framesWithProps;
   1353  GetModifiedAndFramesWithProps(&modifiedFrames.Frames(),
   1354                                &framesWithProps.Frames());
   1355 }
   1356 
   1357 void RetainedDisplayListBuilder::ClearRetainedData() {
   1358  DL_LOGI("(%p) RDL - Clearing retained display list builder data", this);
   1359  List()->DeleteAll(Builder());
   1360  ClearFramesWithProps();
   1361  ClearReuseableDisplayItems();
   1362 }
   1363 
   1364 namespace RDLUtils {
   1365 
   1366 MOZ_NEVER_INLINE_DEBUG void AssertFrameSubtreeUnmodified(
   1367    const nsIFrame* aFrame) {
   1368  MOZ_ASSERT(!aFrame->IsFrameModified());
   1369  MOZ_ASSERT(!aFrame->HasModifiedDescendants());
   1370 
   1371  for (const auto& childList : aFrame->ChildLists()) {
   1372    for (nsIFrame* child : childList.mList) {
   1373      AssertFrameSubtreeUnmodified(child);
   1374    }
   1375  }
   1376 }
   1377 
   1378 MOZ_NEVER_INLINE_DEBUG void AssertDisplayListUnmodified(nsDisplayList* aList) {
   1379  for (nsDisplayItem* item : *aList) {
   1380    AssertDisplayItemUnmodified(item);
   1381  }
   1382 }
   1383 
   1384 MOZ_NEVER_INLINE_DEBUG void AssertDisplayItemUnmodified(nsDisplayItem* aItem) {
   1385  MOZ_ASSERT(!aItem->HasDeletedFrame());
   1386  MOZ_ASSERT(!AnyContentAncestorModified(aItem->FrameForInvalidation()));
   1387 
   1388  if (aItem->GetChildren()) {
   1389    AssertDisplayListUnmodified(aItem->GetChildren());
   1390  }
   1391 }
   1392 
   1393 }  // namespace RDLUtils
   1394 
   1395 namespace RDL {
   1396 
   1397 void MarkAncestorFrames(nsIFrame* aFrame,
   1398                        nsTArray<nsIFrame*>& aOutFramesWithProps) {
   1399  nsIFrame* frame = nsLayoutUtils::GetDisplayListParent(aFrame);
   1400  while (frame && !frame->HasModifiedDescendants()) {
   1401    aOutFramesWithProps.AppendElement(frame);
   1402    frame->SetHasModifiedDescendants(true);
   1403    frame = nsLayoutUtils::GetDisplayListParent(frame);
   1404  }
   1405 }
   1406 
   1407 /**
   1408 * Iterates over the modified frames array and updates the frame tree flags
   1409 * so that container frames know whether they have modified descendant frames.
   1410 * Frames that were marked modified are added to |aOutFramesWithProps|, so that
   1411 * the modified status can be cleared after the display list build.
   1412 */
   1413 void MarkAllAncestorFrames(const nsTArray<nsIFrame*>& aModifiedFrames,
   1414                           nsTArray<nsIFrame*>& aOutFramesWithProps) {
   1415  nsAutoString frameName;
   1416  DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length());
   1417  for (nsIFrame* frame : aModifiedFrames) {
   1418 #ifdef DEBUG
   1419    frame->GetFrameName(frameName);
   1420 #endif
   1421    DL_LOGV("RDL - Processing modified frame: %p (%s)", frame,
   1422            NS_ConvertUTF16toUTF8(frameName).get());
   1423 
   1424    MarkAncestorFrames(frame, aOutFramesWithProps);
   1425  }
   1426 }
   1427 
   1428 /**
   1429 * Marks the given display item |aItem| as reuseable container, and updates the
   1430 * bounds in case some child items were destroyed.
   1431 */
   1432 MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem(
   1433    nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
   1434  aItem->SetPreProcessed();
   1435 
   1436  if (aItem->HasChildren()) {
   1437    aItem->UpdateBounds(aBuilder);
   1438  }
   1439 
   1440  aBuilder->AddReusableDisplayItem(aItem);
   1441  DL_LOGD("Reusing display item %p", aItem);
   1442 }
   1443 
   1444 bool IsSupportedFrameType(const nsIFrame* aFrame) {
   1445  // The way table backgrounds are handled makes these frames incompatible with
   1446  // this retained display list approach.
   1447  if (aFrame->IsTableColFrame()) {
   1448    return false;
   1449  }
   1450 
   1451  if (aFrame->IsTableColGroupFrame()) {
   1452    return false;
   1453  }
   1454 
   1455  if (aFrame->IsTableRowFrame()) {
   1456    return false;
   1457  }
   1458 
   1459  if (aFrame->IsTableRowGroupFrame()) {
   1460    return false;
   1461  }
   1462 
   1463  if (aFrame->IsTableCellFrame()) {
   1464    return false;
   1465  }
   1466 
   1467  // Everything else should work.
   1468  return true;
   1469 }
   1470 
   1471 bool IsReuseableStackingContextItem(nsDisplayItem* aItem) {
   1472  if (!IsSupportedFrameType(aItem->Frame())) {
   1473    return false;
   1474  }
   1475 
   1476  if (!aItem->IsReusable()) {
   1477    return false;
   1478  }
   1479 
   1480  const nsIFrame* frame = aItem->FrameForInvalidation();
   1481  return !frame->HasModifiedDescendants() && !frame->GetPrevContinuation() &&
   1482         !frame->GetNextContinuation();
   1483 }
   1484 
   1485 /**
   1486 * Recursively visits every display item of the display list and destroys all
   1487 * display items that depend on deleted or modified frames.
   1488 * The stacking context display items for unmodified frame subtrees are kept
   1489 * linked and collected in given |aOutItems| array.
   1490 */
   1491 void CollectStackingContextItems(nsDisplayListBuilder* aBuilder,
   1492                                 nsDisplayList* aList, nsIFrame* aOuterFrame,
   1493                                 int aDepth = 0, bool aParentReused = false) {
   1494  for (nsDisplayItem* item : aList->TakeItems()) {
   1495    if (DL_LOG_TEST(LogLevel::Debug)) {
   1496      DL_LOGD(
   1497          "%*s Preprocessing item %p (%s) (frame: %p) "
   1498          "(children: %zu) (depth: %d) (parentReused: %d)",
   1499          aDepth, "", item, item->Name(),
   1500          item->HasDeletedFrame() ? nullptr : item->Frame(),
   1501          item->GetChildren() ? item->GetChildren()->Length() : 0, aDepth,
   1502          aParentReused);
   1503    }
   1504 
   1505    if (!item->CanBeReused() || item->HasDeletedFrame() ||
   1506        AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) {
   1507      DL_LOGD("%*s Deleted modified or temporary item %p", aDepth, "", item);
   1508      item->Destroy(aBuilder);
   1509      continue;
   1510    }
   1511 
   1512    MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation()));
   1513    MOZ_ASSERT(!item->IsPreProcessed());
   1514    item->InvalidateCachedChildInfo(aBuilder);
   1515 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   1516    item->SetMergedPreProcessed(false, true);
   1517 #endif
   1518    item->SetReused(true);
   1519 
   1520    const bool isStackingContextItem = IsReuseableStackingContextItem(item);
   1521 
   1522    if (item->GetChildren()) {
   1523      CollectStackingContextItems(aBuilder, item->GetChildren(), item->Frame(),
   1524                                  aDepth + 1,
   1525                                  aParentReused || isStackingContextItem);
   1526    }
   1527 
   1528    if (aParentReused) {
   1529      // Keep the contents of the current container item linked.
   1530 #ifdef DEBUG
   1531      RDLUtils::AssertDisplayItemUnmodified(item);
   1532 #endif
   1533      aList->AppendToTop(item);
   1534    } else if (isStackingContextItem) {
   1535      // |item| is a stacking context item that can be reused.
   1536      ReuseStackingContextItem(aBuilder, item);
   1537    } else {
   1538      // |item| is inside a container item that will be destroyed later.
   1539      DL_LOGD("%*s Deleted unused item %p", aDepth, "", item);
   1540      item->Destroy(aBuilder);
   1541      continue;
   1542    }
   1543 
   1544    if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
   1545      IncrementPresShellPaintCount(aBuilder, item);
   1546    }
   1547  }
   1548 }
   1549 
   1550 }  // namespace RDL
   1551 
   1552 bool RetainedDisplayListBuilder::TrySimpleUpdate(
   1553    const nsTArray<nsIFrame*>& aModifiedFrames,
   1554    nsTArray<nsIFrame*>& aOutFramesWithProps) {
   1555  if (!mBuilder.IsReusingStackingContextItems()) {
   1556    return false;
   1557  }
   1558 
   1559  RDL::MarkAllAncestorFrames(aModifiedFrames, aOutFramesWithProps);
   1560  RDL::CollectStackingContextItems(&mBuilder, &mList, RootReferenceFrame());
   1561 
   1562  return true;
   1563 }
   1564 
   1565 PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate(
   1566    nscolor aBackstop) {
   1567  DL_LOGI("(%p) RDL - AttemptPartialUpdate, root frame: %p", this,
   1568          RootReferenceFrame());
   1569 
   1570  mBuilder.RemoveModifiedWindowRegions();
   1571 
   1572  if (mBuilder.ShouldSyncDecodeImages()) {
   1573    DL_LOGI("RDL - Sync decoding images");
   1574    MarkFramesWithItemsAndImagesModified(&mList);
   1575  }
   1576 
   1577  mBuilder.InvalidateCaretFramesIfNeeded();
   1578 
   1579  // We set the override dirty regions during ComputeRebuildRegion or in
   1580  // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change
   1581  // also marks the frame modified, so those regions are cleared here as well.
   1582  AutoClearFramePropsArray modifiedFrames(Data()->GetModifiedFrameCount());
   1583  AutoClearFramePropsArray framesWithProps(64);
   1584  GetModifiedAndFramesWithProps(&modifiedFrames.Frames(),
   1585                                &framesWithProps.Frames());
   1586 
   1587  if (!ShouldBuildPartial(modifiedFrames.Frames())) {
   1588    // Do not allow partial builds if the |ShouldBuildPartial()| heuristic
   1589    // fails.
   1590    mBuilder.SetPartialBuildFailed(true);
   1591    return PartialUpdateResult::Failed;
   1592  }
   1593 
   1594  nsRect modifiedDirty;
   1595  nsDisplayList modifiedDL(&mBuilder);
   1596  nsIFrame* modifiedAGR = nullptr;
   1597  PartialUpdateResult result = PartialUpdateResult::NoChange;
   1598  const bool simpleUpdate =
   1599      TrySimpleUpdate(modifiedFrames.Frames(), framesWithProps.Frames());
   1600 
   1601  mBuilder.EnterPresShell(RootReferenceFrame());
   1602 
   1603  if (!simpleUpdate) {
   1604    if (!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
   1605                              &modifiedAGR, framesWithProps.Frames()) ||
   1606        !PreProcessDisplayList(&mList, modifiedAGR, result,
   1607                               RootReferenceFrame(), nullptr)) {
   1608      DL_LOGI("RDL - Partial update aborted");
   1609      mBuilder.SetPartialBuildFailed(true);
   1610      mBuilder.LeavePresShell(RootReferenceFrame(), nullptr);
   1611      mList.DeleteAll(&mBuilder);
   1612      return PartialUpdateResult::Failed;
   1613    }
   1614  } else {
   1615    modifiedDirty = mBuilder.GetVisibleRect();
   1616  }
   1617 
   1618  // This is normally handled by EnterPresShell, but we skipped it so that we
   1619  // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
   1620  ScrollContainerFrame* sf =
   1621      RootReferenceFrame()->PresShell()->GetRootScrollContainerFrame();
   1622  if (sf) {
   1623    nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
   1624    if (canvasFrame) {
   1625      mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame());
   1626    }
   1627  }
   1628 
   1629  nsRect rootOverflow = RootOverflowRect();
   1630  modifiedDirty.IntersectRect(modifiedDirty, rootOverflow);
   1631 
   1632  mBuilder.SetDirtyRect(modifiedDirty);
   1633  mBuilder.SetPartialUpdate(true);
   1634  mBuilder.SetPartialBuildFailed(false);
   1635 
   1636  DL_LOGI("RDL - Starting display list build");
   1637  RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder,
   1638                                                           &modifiedDL);
   1639  DL_LOGI("RDL - Finished display list build");
   1640 
   1641  if (!modifiedDL.IsEmpty()) {
   1642    nsLayoutUtils::AddExtraBackgroundItems(
   1643        &mBuilder, &modifiedDL, RootReferenceFrame(),
   1644        nsRect(nsPoint(0, 0), rootOverflow.Size()), rootOverflow, aBackstop);
   1645  }
   1646  mBuilder.SetPartialUpdate(false);
   1647 
   1648  if (mBuilder.PartialBuildFailed()) {
   1649    DL_LOGI("RDL - Partial update failed!");
   1650    mBuilder.LeavePresShell(RootReferenceFrame(), nullptr);
   1651    mBuilder.ClearReuseableDisplayItems();
   1652    mList.DeleteAll(&mBuilder);
   1653    modifiedDL.DeleteAll(&mBuilder);
   1654    Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content;
   1655    return PartialUpdateResult::Failed;
   1656  }
   1657 
   1658  // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
   1659  //              modifiedDirty.x, modifiedDirty.y, modifiedDirty.width,
   1660  //              modifiedDirty.height);
   1661  // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL);
   1662 
   1663  // |modifiedDL| can sometimes be empty here. We still perform the
   1664  // display list merging to prune unused items (for example, items that
   1665  // are not visible anymore) from the old list.
   1666  // TODO: Optimization opportunity. In this case, MergeDisplayLists()
   1667  // unnecessarily creates a hashtable of the old items.
   1668  // TODO: Ideally we could skip this if result is NoChange, but currently when
   1669  // we call RestoreState on nsDisplayWrapList it resets the clip to the base
   1670  // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
   1671  // move it to the correct inner clip.
   1672  if (!simpleUpdate) {
   1673    Maybe<const ActiveScrolledRoot*> dummy;
   1674    if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
   1675      result = PartialUpdateResult::Updated;
   1676    }
   1677  } else {
   1678    MOZ_ASSERT(mList.IsEmpty());
   1679    mList = std::move(modifiedDL);
   1680    mBuilder.ClearReuseableDisplayItems();
   1681    result = PartialUpdateResult::Updated;
   1682  }
   1683 
   1684 #if 0
   1685  if (DL_LOG_TEST(LogLevel::Verbose)) {
   1686    printf_stderr("Painting --- Display list:\n");
   1687    nsIFrame::PrintDisplayList(&mBuilder, mList);
   1688  }
   1689 #endif
   1690 
   1691  mBuilder.LeavePresShell(RootReferenceFrame(), List());
   1692  return result;
   1693 }
   1694 
   1695 nsRect RetainedDisplayListBuilder::RootOverflowRect() const {
   1696  const nsIFrame* rootReferenceFrame = RootReferenceFrame();
   1697  nsRect rootOverflowRect = rootReferenceFrame->InkOverflowRectRelativeToSelf();
   1698  const nsPresContext* presContext = rootReferenceFrame->PresContext();
   1699  if (!rootReferenceFrame->GetParent() &&
   1700      presContext->IsRootContentDocumentCrossProcess() &&
   1701      presContext->HasDynamicToolbar()) {
   1702    rootOverflowRect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
   1703        presContext, rootOverflowRect.Size()));
   1704  }
   1705 
   1706  return rootOverflowRect;
   1707 }
   1708 
   1709 }  // namespace mozilla