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 ¤tFrame);
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