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