Bug 1687263: part 4) Defer and in some cases avoid removing spellchecking-ranges...
[gecko.git] / layout / painting / RetainedDisplayListBuilder.cpp
blob520f315856552648a3fa7760174a94daaeda9f46
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 */
8 #include "RetainedDisplayListBuilder.h"
10 #include "mozilla/StaticPrefs_layout.h"
11 #include "nsIFrame.h"
12 #include "nsIFrameInlines.h"
13 #include "nsIScrollableFrame.h"
14 #include "nsPlaceholderFrame.h"
15 #include "nsSubDocumentFrame.h"
16 #include "nsViewManager.h"
17 #include "nsCanvasFrame.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/DisplayPortUtils.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/ProfilerLabels.h"
23 /**
24 * Code for doing display list building for a modified subset of the window,
25 * and then merging it into the existing display list (for the full window).
27 * The approach primarily hinges on the observation that the 'true' ordering
28 * of display items is represented by a DAG (only items that intersect in 2d
29 * space have a defined ordering). Our display list is just one of a many
30 * possible linear representations of this ordering.
32 * Each time a frame changes (gets a new ComputedStyle, or has a size/position
33 * change), we schedule a paint (as we do currently), but also reord the frame
34 * that changed.
36 * When the next paint occurs we union the overflow areas (in screen space) of
37 * the changed frames, and compute a rect/region that contains all changed
38 * items. We then build a display list just for this subset of the screen and
39 * merge it into the display list from last paint.
41 * Any items that exist in one list and not the other must not have a defined
42 * ordering in the DAG, since they need to intersect to have an ordering and
43 * we would have built both in the new list if they intersected. Given that, we
44 * can align items that appear in both lists, and any items that appear between
45 * matched items can be inserted into the merged list in any order.
48 using namespace mozilla;
49 using mozilla::dom::Document;
51 void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) {
52 MOZ_ASSERT(!aFrame->IsFrameModified());
53 Flags(aFrame) |= RetainedDisplayListData::FrameFlags::Modified;
54 mModifiedFramesCount++;
57 RetainedDisplayListData* GetRetainedDisplayListData(nsIFrame* aRootFrame) {
58 RetainedDisplayListData* data =
59 aRootFrame->GetProperty(RetainedDisplayListData::DisplayListData());
61 return data;
64 RetainedDisplayListData* GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame) {
65 RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame);
67 if (!data) {
68 data = new RetainedDisplayListData();
69 aRootFrame->SetProperty(RetainedDisplayListData::DisplayListData(), data);
72 MOZ_ASSERT(data);
73 return data;
76 static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) {
77 for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
78 if (!i->HasDeletedFrame() && i->CanBeReused() &&
79 !i->Frame()->IsFrameModified()) {
80 // If we have existing cached geometry for this item, then check that for
81 // whether we need to invalidate for a sync decode. If we don't, then
82 // use the item's flags.
83 DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
84 // XXX: handle webrender case
85 bool invalidate = false;
86 if (data && data->GetGeometry()) {
87 invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages();
88 } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
89 invalidate = true;
92 if (invalidate) {
93 i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
94 if (i->GetDependentFrame()) {
95 i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
99 if (i->GetChildren()) {
100 MarkFramesWithItemsAndImagesModified(i->GetChildren());
105 static AnimatedGeometryRoot* SelectAGRForFrame(
106 nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR) {
107 if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) {
108 return aParentAGR;
111 if (!aFrame->HasOverrideDirtyRegion()) {
112 return nullptr;
115 nsDisplayListBuilder::DisplayListBuildingData* data =
116 aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
118 return data && data->mModifiedAGR ? data->mModifiedAGR.get() : nullptr;
121 void RetainedDisplayListBuilder::AddSizeOfIncludingThis(
122 nsWindowSizes& aSizes) const {
123 aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this);
124 mBuilder.AddSizeOfExcludingThis(aSizes);
125 mList.AddSizeOfExcludingThis(aSizes);
128 bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
129 nsIFrame* f = aFrame;
130 while (f) {
131 if (f->IsFrameModified()) {
132 return true;
135 if (aStopAtFrame && f == aStopAtFrame) {
136 break;
139 f = nsLayoutUtils::GetDisplayListParent(f);
142 return false;
145 // Removes any display items that belonged to a frame that was deleted,
146 // and mark frames that belong to a different AGR so that get their
147 // items built again.
148 // TODO: We currently descend into all children even if we don't have an AGR
149 // to mark, as child stacking contexts might. It would be nice if we could
150 // jump into those immediately rather than walking the entire thing.
151 bool RetainedDisplayListBuilder::PreProcessDisplayList(
152 RetainedDisplayList* aList, AnimatedGeometryRoot* aAGR,
153 PartialUpdateResult& aUpdated, nsIFrame* aOuterFrame, uint32_t aCallerKey,
154 uint32_t aNestingDepth, bool aKeepLinked) {
155 // The DAG merging algorithm does not have strong mechanisms in place to keep
156 // the complexity of the resulting DAG under control. In some cases we can
157 // build up edges very quickly. Detect those cases and force a full display
158 // list build if we hit them.
159 static const uint32_t kMaxEdgeRatio = 5;
160 const bool initializeDAG = !aList->mDAG.Length();
161 if (!aKeepLinked && !initializeDAG &&
162 aList->mDAG.mDirectPredecessorList.Length() >
163 (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) {
164 return false;
167 // If we had aKeepLinked=true for this list on the previous paint, then
168 // mOldItems will already be initialized as it won't have been consumed during
169 // a merge.
170 const bool initializeOldItems = aList->mOldItems.IsEmpty();
171 if (initializeOldItems) {
172 aList->mOldItems.SetCapacity(aList->Count());
173 } else {
174 MOZ_RELEASE_ASSERT(!initializeDAG);
177 MOZ_RELEASE_ASSERT(
178 initializeDAG ||
179 aList->mDAG.Length() ==
180 (initializeOldItems ? aList->Count() : aList->mOldItems.Length()));
182 nsDisplayList out;
184 size_t i = 0;
185 while (nsDisplayItem* item = aList->RemoveBottom()) {
186 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
187 item->SetMergedPreProcessed(false, true);
188 #endif
190 // If we have a previously initialized old items list, then it can differ
191 // from the current list due to items removed for having a deleted frame.
192 // We can't easily remove these, since the DAG has entries for those indices
193 // and it's hard to rewrite in-place.
194 // Skip over entries with no current item to keep the iterations in sync.
195 if (!initializeOldItems) {
196 while (!aList->mOldItems[i].mItem) {
197 i++;
201 if (initializeDAG) {
202 if (i == 0) {
203 aList->mDAG.AddNode(Span<const MergedListIndex>());
204 } else {
205 MergedListIndex previous(i - 1);
206 aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
210 if (!item->CanBeReused() || item->HasDeletedFrame() ||
211 AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) {
212 if (initializeOldItems) {
213 aList->mOldItems.AppendElement(OldItemInfo(nullptr));
214 } else {
215 MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item);
216 aList->mOldItems[i].mItem = nullptr;
219 if (item->IsGlassItem() && item == mBuilder.GetGlassDisplayItem()) {
220 mBuilder.ClearGlassDisplayItem();
223 item->Destroy(&mBuilder);
224 Metrics()->mRemovedItems++;
226 i++;
227 aUpdated = PartialUpdateResult::Updated;
228 continue;
231 if (initializeOldItems) {
232 aList->mOldItems.AppendElement(OldItemInfo(item));
235 // If we're not going to keep the list linked, then this old item entry
236 // is the only pointer to the item. Let it know that it now strongly
237 // owns the item, so it can destroy it if it goes away.
238 aList->mOldItems[i].mOwnsItem = !aKeepLinked;
240 item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth);
242 nsIFrame* f = item->Frame();
244 if (item->GetChildren()) {
245 // If children inside this list were invalid, then we'd have walked the
246 // ancestors and set ForceDescendIntoVisible on the current frame. If an
247 // ancestor is modified, then we'll throw this away entirely. Either way,
248 // we won't need to run merging on this sublist, and we can keep the items
249 // linked into their display list.
250 // The caret can move without invalidating, but we always set the force
251 // descend into frame state bit on that frame, so check for that too.
252 // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make
253 // us think future siblings need to be merged, even though we don't really
254 // need to.
255 bool keepLinked = aKeepLinked;
256 nsIFrame* invalid = item->FrameForInvalidation();
257 if (!invalid->ForceDescendIntoIfVisible() &&
258 !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
259 keepLinked = true;
262 if (!PreProcessDisplayList(item->GetChildren(),
263 SelectAGRForFrame(f, aAGR), aUpdated,
264 item->Frame(), item->GetPerFrameKey(),
265 aNestingDepth + 1, keepLinked)) {
266 MOZ_RELEASE_ASSERT(
267 !aKeepLinked,
268 "Can't early return since we need to move the out list back");
269 return false;
273 // TODO: We should be able to check the clipped bounds relative
274 // to the common AGR (of both the existing item and the invalidated
275 // frame) and determine if they can ever intersect.
276 // TODO: We only really need to build the ancestor container item that is a
277 // sibling of the changed thing to get correct ordering. The changed content
278 // is a frame though, and it's hard to map that to container items in this
279 // list.
280 if (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
281 mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
284 // TODO: This is here because we sometimes reuse the previous display list
285 // completely. For optimization, we could only restore the state for reused
286 // display items.
287 if (item->RestoreState()) {
288 item->InvalidateItemCacheEntry();
291 // If we're going to keep this linked list and not merge it, then mark the
292 // item as used and put it back into the list.
293 if (aKeepLinked) {
294 item->SetReused(true);
295 if (item->GetChildren()) {
296 item->UpdateBounds(Builder());
298 if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
299 IncrementSubDocPresShellPaintCount(item);
301 out.AppendToTop(item);
303 i++;
306 MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length());
307 aList->RestoreState();
309 if (aKeepLinked) {
310 aList->AppendToTop(&out);
312 return true;
315 void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(
316 nsDisplayItem* aItem) {
317 MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
319 nsSubDocumentFrame* subDocFrame =
320 static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
321 MOZ_ASSERT(subDocFrame);
323 PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
324 MOZ_ASSERT(presShell);
326 mBuilder.IncrementPresShellPaintCount(presShell);
329 static Maybe<const ActiveScrolledRoot*> SelectContainerASR(
330 const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR,
331 Maybe<const ActiveScrolledRoot*>& aContainerASR) {
332 const ActiveScrolledRoot* itemClipASR =
333 aClipChain ? aClipChain->mASR : nullptr;
335 const ActiveScrolledRoot* finiteBoundsASR =
336 ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR);
338 if (!aContainerASR) {
339 return Some(finiteBoundsASR);
342 return Some(
343 ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR));
346 static void UpdateASR(nsDisplayItem* aItem,
347 Maybe<const ActiveScrolledRoot*>& aContainerASR) {
348 if (!aContainerASR) {
349 return;
352 nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
353 if (!wrapList) {
354 aItem->SetActiveScrolledRoot(*aContainerASR);
355 return;
358 wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor(
359 wrapList->GetFrameActiveScrolledRoot(), *aContainerASR));
362 static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) {
363 aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot());
366 OldItemInfo::OldItemInfo(nsDisplayItem* aItem)
367 : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) {
368 if (mItem) {
369 // Clear cached modified frame state when adding an item to the old list.
370 mItem->SetModifiedFrame(false);
374 void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
375 MergedListIndex aIndex) {
376 AddedToMergedList(aIndex);
379 void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder,
380 nsTArray<MergedListIndex>&& aDirectPredecessors) {
381 MOZ_ASSERT(!IsUsed());
382 mUsed = mDiscarded = true;
383 mDirectPredecessors = std::move(aDirectPredecessors);
384 if (mItem) {
385 MOZ_ASSERT(mOwnsItem);
386 mItem->Destroy(aBuilder->Builder());
387 aBuilder->Metrics()->mRemovedItems++;
389 mItem = nullptr;
392 bool OldItemInfo::IsChanged() {
393 return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame();
397 * A C++ implementation of Markus Stange's merge-dags algorithm.
398 * https://github.com/mstange/merge-dags
400 * MergeState handles combining a new list of display items into an existing
401 * DAG and computes the new DAG in a single pass.
402 * Each time we add a new item, we resolve all dependencies for it, so that the
403 * resulting list and DAG are built in topological ordering.
405 class MergeState {
406 public:
407 MergeState(RetainedDisplayListBuilder* aBuilder,
408 RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem)
409 : mBuilder(aBuilder),
410 mOldList(&aOldList),
411 mOldItems(std::move(aOldList.mOldItems)),
412 mOldDAG(
413 std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(
414 &aOldList.mDAG))),
415 mOuterItem(aOuterItem),
416 mResultIsModified(false) {
417 mMergedDAG.EnsureCapacityFor(mOldDAG);
418 MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length());
421 Maybe<MergedListIndex> ProcessItemFromNewList(
422 nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) {
423 OldListIndex oldIndex;
424 MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() ==
425 HasModifiedFrame(aNewItem));
426 if (!aNewItem->HasModifiedFrame() &&
427 HasMatchingItemInOldList(aNewItem, &oldIndex)) {
428 mBuilder->Metrics()->mRebuiltItems++;
429 nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
430 MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() ==
431 aNewItem->GetPerFrameKey() &&
432 oldItem->Frame() == aNewItem->Frame());
433 if (!mOldItems[oldIndex.val].IsChanged()) {
434 MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
435 nsDisplayItem* destItem;
436 if (ShouldUseNewItem(aNewItem)) {
437 destItem = aNewItem;
438 } else {
439 destItem = oldItem;
440 // The building rect can depend on the overflow rect (when the parent
441 // frame is position:fixed), which can change without invalidating
442 // the frame/items. If we're using the old item, copy the building
443 // rect across from the new item.
444 oldItem->SetBuildingRect(aNewItem->GetBuildingRect());
447 if (destItem == aNewItem) {
448 if (oldItem->IsGlassItem() &&
449 oldItem == mBuilder->Builder()->GetGlassDisplayItem()) {
450 mBuilder->Builder()->ClearGlassDisplayItem();
452 } // aNewItem can't be the glass item on the builder yet.
454 if (destItem->IsGlassItem()) {
455 if (destItem != oldItem ||
456 destItem != mBuilder->Builder()->GetGlassDisplayItem()) {
457 mBuilder->Builder()->SetGlassDisplayItem(destItem);
461 MergeChildLists(aNewItem, oldItem, destItem);
463 AutoTArray<MergedListIndex, 2> directPredecessors =
464 ProcessPredecessorsOfOldNode(oldIndex);
465 MergedListIndex newIndex = AddNewNode(
466 destItem, Some(oldIndex), directPredecessors, aPreviousItem);
467 mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex);
468 if (destItem == aNewItem) {
469 oldItem->Destroy(mBuilder->Builder());
470 } else {
471 aNewItem->Destroy(mBuilder->Builder());
473 return Some(newIndex);
476 mResultIsModified = true;
477 if (aNewItem->IsGlassItem()) {
478 mBuilder->Builder()->SetGlassDisplayItem(aNewItem);
480 return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(),
481 aPreviousItem));
484 void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem,
485 nsDisplayItem* aOutItem) {
486 if (!aOutItem->GetChildren()) {
487 return;
490 Maybe<const ActiveScrolledRoot*> containerASRForChildren;
491 nsDisplayList empty;
492 const bool modified = mBuilder->MergeDisplayLists(
493 aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(),
494 aOutItem->GetChildren(), containerASRForChildren, aOutItem);
495 if (modified) {
496 aOutItem->InvalidateCachedChildInfo(mBuilder->Builder());
497 UpdateASR(aOutItem, containerASRForChildren);
498 mResultIsModified = true;
499 } else if (aOutItem == aNewItem) {
500 // If nothing changed, but we copied the contents across to
501 // the new item, then also copy the ASR data.
502 CopyASR(aOldItem, aNewItem);
504 // Ideally we'd only UpdateBounds if something changed, but
505 // nsDisplayWrapList also uses this to update the clip chain for the
506 // current ASR, which gets reset during RestoreState(), so we always need
507 // to run it again.
508 aOutItem->UpdateBounds(mBuilder->Builder());
511 bool ShouldUseNewItem(nsDisplayItem* aNewItem) {
512 // Generally we want to use the old item when the frame isn't marked as
513 // modified so that any cached information on the item (or referencing the
514 // item) gets retained. Quite a few FrameLayerBuilder performance
515 // improvements benefit by this. Sometimes, however, we can end up where the
516 // new item paints something different from the old item, even though we
517 // haven't modified the frame, and it's hard to fix. In these cases we just
518 // always use the new item to be safe.
519 DisplayItemType type = aNewItem->GetType();
520 if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR ||
521 type == DisplayItemType::TYPE_SOLID_COLOR) {
522 // The canvas background color item can paint the color from another
523 // frame, and even though we schedule a paint, we don't mark the canvas
524 // frame as invalid.
525 return true;
528 if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) {
529 // We intentionally don't mark the root table frame as modified when a
530 // subframe changes, even though the border collapse item for the root
531 // frame is what paints the changed border. Marking the root frame as
532 // modified would rebuild display items for the whole table area, and we
533 // don't want that.
534 return true;
537 if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) {
538 // Text overflow marker items are created with the wrapping block as their
539 // frame, and have an index value to note which line they are created for.
540 // Their rendering can change if the items on that line change, which may
541 // not mark the block as modified. We rebuild them if we build any item on
542 // the line, so we should always get new items if they might have changed
543 // rendering, and it's easier to just use the new items rather than
544 // computing if we actually need them.
545 return true;
548 if (type == DisplayItemType::TYPE_SUBDOCUMENT) {
549 // nsDisplaySubDocument::mShouldFlatten can change without an invalidation
550 // (and is the reason we unconditionally build the subdocument item), so
551 // always use the new one to make sure we get the right value.
552 return true;
555 if (type == DisplayItemType::TYPE_CARET) {
556 // The caret can change position while still being owned by the same frame
557 // and we don't invalidate in that case. Use the new version since the
558 // changed bounds are needed for DLBI.
559 return true;
562 if (type == DisplayItemType::TYPE_MASK ||
563 type == DisplayItemType::TYPE_FILTER ||
564 type == DisplayItemType::TYPE_SVG_WRAPPER) {
565 // SVG items have some invalidation issues, see bugs 1494110 and 1494663.
566 return true;
569 if (type == DisplayItemType::TYPE_TRANSFORM) {
570 // Prerendering of transforms can change without frame invalidation.
571 return true;
574 return false;
577 RetainedDisplayList Finalize() {
578 for (size_t i = 0; i < mOldDAG.Length(); i++) {
579 if (mOldItems[i].IsUsed()) {
580 continue;
583 AutoTArray<MergedListIndex, 2> directPredecessors =
584 ResolveNodeIndexesOldToMerged(
585 mOldDAG.GetDirectPredecessors(OldListIndex(i)));
586 ProcessOldNode(OldListIndex(i), std::move(directPredecessors));
589 RetainedDisplayList result;
590 result.AppendToTop(&mMergedItems);
591 result.mDAG = std::move(mMergedDAG);
592 MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Count());
593 return result;
596 bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) {
597 // Look for an item that matches aItem's frame and per-frame-key, but isn't
598 // the same item.
599 uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0;
600 nsIFrame* frame = aItem->Frame();
601 for (nsDisplayItemBase* i : frame->DisplayItems()) {
602 if (i != aItem && i->Frame() == frame &&
603 i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
604 if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) {
605 return true;
609 return false;
612 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
613 bool HasModifiedFrame(nsDisplayItem* aItem) {
614 nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr;
615 return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame);
617 #endif
619 void UpdateContainerASR(nsDisplayItem* aItem) {
620 mContainerASR = SelectContainerASR(
621 aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR);
624 MergedListIndex AddNewNode(
625 nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex,
626 Span<const MergedListIndex> aDirectPredecessors,
627 const Maybe<MergedListIndex>& aExtraDirectPredecessor) {
628 UpdateContainerASR(aItem);
629 aItem->NotifyUsed(mBuilder->Builder());
631 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
632 for (nsDisplayItemBase* i : aItem->Frame()->DisplayItems()) {
633 if (i->Frame() == aItem->Frame() &&
634 i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
635 MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem());
639 aItem->SetMergedPreProcessed(true, false);
640 #endif
642 mMergedItems.AppendToTop(aItem);
643 mBuilder->Metrics()->mTotalItems++;
645 MergedListIndex newIndex =
646 mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
647 return newIndex;
650 void ProcessOldNode(OldListIndex aNode,
651 nsTArray<MergedListIndex>&& aDirectPredecessors) {
652 nsDisplayItem* item = mOldItems[aNode.val].mItem;
653 if (mOldItems[aNode.val].IsChanged()) {
654 if (item && item->IsGlassItem() &&
655 item == mBuilder->Builder()->GetGlassDisplayItem()) {
656 mBuilder->Builder()->ClearGlassDisplayItem();
659 mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors));
660 mResultIsModified = true;
661 } else {
662 MergeChildLists(nullptr, item, item);
664 if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
665 mBuilder->IncrementSubDocPresShellPaintCount(item);
667 item->SetReused(true);
668 mBuilder->Metrics()->mReusedItems++;
669 mOldItems[aNode.val].AddedToMergedList(
670 AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing()));
674 struct PredecessorStackItem {
675 PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors)
676 : mNode(aNode),
677 mDirectPredecessors(aPredecessors),
678 mCurrentPredecessorIndex(0) {}
680 bool IsFinished() {
681 return mCurrentPredecessorIndex == mDirectPredecessors.Length();
684 OldListIndex GetAndIncrementCurrentPredecessor() {
685 return mDirectPredecessors[mCurrentPredecessorIndex++];
688 OldListIndex mNode;
689 Span<OldListIndex> mDirectPredecessors;
690 size_t mCurrentPredecessorIndex;
693 AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode(
694 OldListIndex aNode) {
695 AutoTArray<PredecessorStackItem, 256> mStack;
696 mStack.AppendElement(
697 PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode)));
699 while (true) {
700 if (mStack.LastElement().IsFinished()) {
701 // If we've finished processing all the entries in the current set, then
702 // pop it off the processing stack and process it.
703 PredecessorStackItem item = mStack.PopLastElement();
704 AutoTArray<MergedListIndex, 2> result =
705 ResolveNodeIndexesOldToMerged(item.mDirectPredecessors);
707 if (mStack.IsEmpty()) {
708 return result;
711 ProcessOldNode(item.mNode, std::move(result));
712 } else {
713 // Grab the current predecessor, push predecessors of that onto the
714 // processing stack (if it hasn't already been processed), and then
715 // advance to the next entry.
716 OldListIndex currentIndex =
717 mStack.LastElement().GetAndIncrementCurrentPredecessor();
718 if (!mOldItems[currentIndex.val].IsUsed()) {
719 mStack.AppendElement(PredecessorStackItem(
720 currentIndex, mOldDAG.GetDirectPredecessors(currentIndex)));
726 AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged(
727 Span<OldListIndex> aDirectPredecessors) {
728 AutoTArray<MergedListIndex, 2> result;
729 result.SetCapacity(aDirectPredecessors.Length());
730 for (OldListIndex index : aDirectPredecessors) {
731 OldItemInfo& oldItem = mOldItems[index.val];
732 if (oldItem.IsDiscarded()) {
733 for (MergedListIndex inner : oldItem.mDirectPredecessors) {
734 if (!result.Contains(inner)) {
735 result.AppendElement(inner);
738 } else {
739 result.AppendElement(oldItem.mIndex);
742 return result;
745 RetainedDisplayListBuilder* mBuilder;
746 RetainedDisplayList* mOldList;
747 Maybe<const ActiveScrolledRoot*> mContainerASR;
748 nsTArray<OldItemInfo> mOldItems;
749 DirectedAcyclicGraph<OldListUnits> mOldDAG;
750 // Unfortunately we can't use strong typing for the hashtables
751 // since they internally encode the type with the mOps pointer,
752 // and assert when we try swap the contents
753 nsDisplayList mMergedItems;
754 DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
755 nsDisplayItem* mOuterItem;
756 bool mResultIsModified;
759 #ifdef DEBUG
760 void VerifyNotModified(nsDisplayList* aList) {
761 for (nsDisplayItem* item = aList->GetBottom(); item;
762 item = item->GetAbove()) {
763 MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation()));
765 if (item->GetChildren()) {
766 VerifyNotModified(item->GetChildren());
770 #endif
773 * Takes two display lists and merges them into an output list.
775 * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a
776 * maximum of one direct predecessor and one direct successor per node). We add
777 * the two DAGs together, and then output the topological sorted ordering as the
778 * final display list.
780 * Once we've merged a list, we then retain the DAG (as part of the
781 * RetainedDisplayList object) to use for future merges.
783 bool RetainedDisplayListBuilder::MergeDisplayLists(
784 nsDisplayList* aNewList, RetainedDisplayList* aOldList,
785 RetainedDisplayList* aOutList,
786 mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
787 nsDisplayItem* aOuterItem) {
788 AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging);
790 if (!aOldList->IsEmpty()) {
791 // If we still have items in the actual list, then it is because
792 // PreProcessDisplayList decided that it was sure it can't be modified. We
793 // can just use it directly, and throw any new items away.
795 aNewList->DeleteAll(&mBuilder);
796 #ifdef DEBUG
797 VerifyNotModified(aOldList);
798 #endif
800 if (aOldList != aOutList) {
801 *aOutList = std::move(*aOldList);
804 return false;
807 MergeState merge(this, *aOldList, aOuterItem);
809 Maybe<MergedListIndex> previousItemIndex;
810 while (nsDisplayItem* item = aNewList->RemoveBottom()) {
811 Metrics()->mNewItems++;
812 previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex);
815 *aOutList = merge.Finalize();
816 aOutContainerASR = merge.mContainerASR;
817 return merge.mResultIsModified;
820 static nsIFrame* GetRootFrameForPainting(nsDisplayListBuilder* aBuilder,
821 Document& aDocument) {
822 // Although this is the actual subdocument, it might not be
823 // what painting uses. Walk up to the nsSubDocumentFrame owning
824 // us, and then ask that which subdoc it's going to paint.
826 PresShell* presShell = aDocument.GetPresShell();
827 if (!presShell) {
828 return nullptr;
830 nsView* rootView = presShell->GetViewManager()->GetRootView();
831 if (!rootView) {
832 return nullptr;
835 // There should be an anonymous inner view between the root view
836 // of the subdoc, and the view for the nsSubDocumentFrame.
837 nsView* innerView = rootView->GetParent();
838 if (!innerView) {
839 return nullptr;
842 nsView* subDocView = innerView->GetParent();
843 if (!subDocView) {
844 return nullptr;
847 nsIFrame* subDocFrame = subDocView->GetFrame();
848 if (!subDocFrame) {
849 return nullptr;
852 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame);
853 MOZ_ASSERT(subdocumentFrame);
854 presShell = subdocumentFrame->GetSubdocumentPresShellForPainting(
855 aBuilder->IsIgnoringPaintSuppression()
856 ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION
857 : 0);
858 return presShell ? presShell->GetRootFrame() : nullptr;
861 static void TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
862 nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aModifiedFrames,
863 nsTArray<nsIFrame*>* aFramesWithProps, nsIFrame* aRootFrame,
864 Document& aDoc) {
865 MOZ_ASSERT(aRootFrame);
867 if (RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame)) {
868 for (auto it = data->ConstIterator(); !it.Done(); it.Next()) {
869 nsIFrame* frame = it.Key();
870 const RetainedDisplayListData::FrameFlags& flags = it.Data();
872 if (flags & RetainedDisplayListData::FrameFlags::Modified) {
873 aModifiedFrames->AppendElement(frame);
876 if (flags & RetainedDisplayListData::FrameFlags::HasProps) {
877 aFramesWithProps->AppendElement(frame);
880 if (flags & RetainedDisplayListData::FrameFlags::HadWillChange) {
881 aBuilder->RemoveFromWillChangeBudgets(frame);
885 data->Clear();
888 auto recurse = [&](Document& aSubDoc) {
889 if (nsIFrame* rootFrame = GetRootFrameForPainting(aBuilder, aSubDoc)) {
890 TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
891 aBuilder, aModifiedFrames, aFramesWithProps, rootFrame, aSubDoc);
893 return CallState::Continue;
895 aDoc.EnumerateSubDocuments(recurse);
898 static void GetModifiedAndFramesWithProps(
899 nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aOutModifiedFrames,
900 nsTArray<nsIFrame*>* aOutFramesWithProps) {
901 nsIFrame* rootFrame = aBuilder->RootReferenceFrame();
902 MOZ_ASSERT(rootFrame);
904 Document* rootDoc = rootFrame->PresContext()->Document();
905 TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
906 aBuilder, aOutModifiedFrames, aOutFramesWithProps, rootFrame, *rootDoc);
909 // ComputeRebuildRegion debugging
910 // #define CRR_DEBUG 1
911 #if CRR_DEBUG
912 # define CRR_LOG(...) printf_stderr(__VA_ARGS__)
913 #else
914 # define CRR_LOG(...)
915 #endif
917 static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) {
918 for (nsDisplayItemBase* i : aFrame->DisplayItems()) {
919 if (i->HasChildren()) {
920 return static_cast<nsDisplayItem*>(i);
923 return nullptr;
926 static bool IsInPreserve3DContext(const nsIFrame* aFrame) {
927 return aFrame->Extend3DContext() ||
928 aFrame->Combines3DTransformWithAncestors();
931 // Returns true if |aFrame| can store a display list building rect.
932 // These limitations are necessary to guarantee that
933 // 1) Just enough items are rebuilt to properly update display list
934 // 2) Modified frames will be visited during a partial display list build.
935 static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder,
936 nsIFrame* aFrame) {
937 return aFrame != aBuilder->RootReferenceFrame() &&
938 aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() &&
939 // Split frames might have placeholders for modified frames in their
940 // unmodified continuation frame.
941 !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation();
944 static bool ProcessFrameInternal(nsIFrame* aFrame,
945 nsDisplayListBuilder* aBuilder,
946 AnimatedGeometryRoot** aAGR, nsRect& aOverflow,
947 const nsIFrame* aStopAtFrame,
948 nsTArray<nsIFrame*>& aOutFramesWithProps,
949 const bool aStopAtStackingContext) {
950 nsIFrame* currentFrame = aFrame;
952 while (currentFrame != aStopAtFrame) {
953 CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
954 currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y,
955 aOverflow.width, aOverflow.height);
957 // If the current frame is an OOF frame, DisplayListBuildingData needs to be
958 // set on all the ancestor stacking contexts of the placeholder frame, up
959 // to the containing block of the OOF frame. This is done to ensure that the
960 // content that might be behind the OOF frame is built for merging.
961 nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
962 ? currentFrame->GetPlaceholderFrame()
963 : nullptr;
965 if (placeholder) {
966 nsRect placeholderOverflow = aOverflow;
967 auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder,
968 placeholderOverflow);
969 if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
970 placeholderOverflow = nsRect();
973 CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder,
974 currentFrame);
976 CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x,
977 placeholderOverflow.y, placeholderOverflow.width,
978 placeholderOverflow.height);
980 // Tracking AGRs for the placeholder processing is not necessary, as the
981 // goal is to only modify the DisplayListBuildingData rect.
982 AnimatedGeometryRoot* dummyAGR = nullptr;
984 // Find a common ancestor frame to handle frame continuations.
985 // TODO: It might be possible to write a more specific and efficient
986 // function for this.
987 const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame(
988 currentFrame->GetParent(), placeholder->GetParent());
990 if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR,
991 placeholderOverflow, ancestor,
992 aOutFramesWithProps, false)) {
993 return false;
997 // Convert 'aOverflow' into the coordinate space of the nearest stacking
998 // context or display port ancestor and update 'currentFrame' to point to
999 // that frame.
1000 aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(
1001 currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr,
1002 /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
1003 &currentFrame);
1004 if (IsInPreserve3DContext(currentFrame)) {
1005 return false;
1008 MOZ_ASSERT(currentFrame);
1010 // Check whether the current frame is a scrollable frame with display port.
1011 nsRect displayPort;
1012 nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
1013 nsIContent* content = sf ? currentFrame->GetContent() : nullptr;
1015 if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) {
1016 CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
1018 // Get overflow relative to the scrollport (from the scrollframe)
1019 nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft();
1020 r.IntersectRect(r, displayPort);
1021 if (!r.IsEmpty()) {
1022 nsRect* rect = currentFrame->GetProperty(
1023 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
1024 if (!rect) {
1025 rect = new nsRect();
1026 currentFrame->SetProperty(
1027 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
1028 currentFrame->SetHasOverrideDirtyRegion(true);
1029 aOutFramesWithProps.AppendElement(currentFrame);
1031 rect->UnionRect(*rect, r);
1032 CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y,
1033 r.width, r.height);
1035 // TODO: Can we just use MarkFrameForDisplayIfVisible, plus
1036 // MarkFramesForDifferentAGR to ensure that this displayport, plus any
1037 // items that move relative to it get rebuilt, and then not contribute
1038 // to the root dirty area?
1039 aOverflow = sf->GetScrollPortRect();
1040 } else {
1041 // Don't contribute to the root dirty area at all.
1042 aOverflow.SetEmpty();
1044 } else {
1045 aOverflow.IntersectRect(aOverflow,
1046 currentFrame->InkOverflowRectRelativeToSelf());
1049 if (aOverflow.IsEmpty()) {
1050 break;
1053 if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) {
1054 CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
1055 // If we found an intermediate stacking context with an existing display
1056 // item then we can store the dirty rect there and stop. If we couldn't
1057 // find one then we need to keep bubbling up to the next stacking context.
1058 nsDisplayItem* wrapperItem =
1059 GetFirstDisplayItemWithChildren(currentFrame);
1060 if (!wrapperItem) {
1061 continue;
1064 // Store the stacking context relative dirty area such
1065 // that display list building will pick it up when it
1066 // gets to it.
1067 nsDisplayListBuilder::DisplayListBuildingData* data =
1068 currentFrame->GetProperty(
1069 nsDisplayListBuilder::DisplayListBuildingRect());
1070 if (!data) {
1071 data = new nsDisplayListBuilder::DisplayListBuildingData();
1072 currentFrame->SetProperty(
1073 nsDisplayListBuilder::DisplayListBuildingRect(), data);
1074 currentFrame->SetHasOverrideDirtyRegion(true);
1075 aOutFramesWithProps.AppendElement(currentFrame);
1077 CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
1078 aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
1079 data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
1081 if (!aStopAtStackingContext) {
1082 // Continue ascending the frame tree until we reach aStopAtFrame.
1083 continue;
1086 // Grab the visible (display list building) rect for children of this
1087 // wrapper item and convert into into coordinate relative to the current
1088 // frame.
1089 nsRect previousVisible = wrapperItem->GetBuildingRectForChildren();
1090 if (wrapperItem->ReferenceFrameForChildren() ==
1091 wrapperItem->ReferenceFrame()) {
1092 previousVisible -= wrapperItem->ToReferenceFrame();
1093 } else {
1094 MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() ==
1095 wrapperItem->Frame());
1098 if (!previousVisible.Contains(aOverflow)) {
1099 // If the overflow area of the changed frame isn't contained within the
1100 // old item, then we might change the size of the item and need to
1101 // update its sorting accordingly. Keep propagating the overflow area up
1102 // so that we build intersecting items for sorting.
1103 continue;
1106 if (!data->mModifiedAGR) {
1107 data->mModifiedAGR = *aAGR;
1108 } else if (data->mModifiedAGR != *aAGR) {
1109 data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf();
1110 CRR_LOG(
1111 "Found multiple modified AGRs within this stacking context, "
1112 "giving up\n");
1115 // Don't contribute to the root dirty area at all.
1116 aOverflow.SetEmpty();
1117 *aAGR = nullptr;
1119 break;
1122 return true;
1125 bool RetainedDisplayListBuilder::ProcessFrame(
1126 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame,
1127 nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext,
1128 nsRect* aOutDirty, AnimatedGeometryRoot** aOutModifiedAGR) {
1129 if (aFrame->HasOverrideDirtyRegion()) {
1130 aOutFramesWithProps.AppendElement(aFrame);
1133 if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
1134 return true;
1137 // TODO: There is almost certainly a faster way of doing this, probably can be
1138 // combined with the ancestor walk for TransformFrameRectToAncestor.
1139 AnimatedGeometryRoot* agr =
1140 aBuilder->FindAnimatedGeometryRootFor(aFrame)->GetAsyncAGR();
1142 CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame);
1144 // Convert the frame's overflow rect into the coordinate space
1145 // of the nearest stacking context that has an existing display item.
1146 // We store that as a dirty rect on that stacking context so that we build
1147 // all items that intersect the changed frame within the stacking context,
1148 // and then we use MarkFrameForDisplayIfVisible to make sure the stacking
1149 // context itself gets built. We don't need to build items that intersect
1150 // outside of the stacking context, since we know the stacking context item
1151 // exists in the old list, so we can trivially merge without needing other
1152 // items.
1153 nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
1155 // If the modified frame is also a caret frame, include the caret area.
1156 // This is needed because some frames (for example text frames without text)
1157 // might have an empty overflow rect.
1158 if (aFrame == aBuilder->GetCaretFrame()) {
1159 overflow.UnionRect(overflow, aBuilder->GetCaretRect());
1162 if (!ProcessFrameInternal(aFrame, aBuilder, &agr, overflow, aStopAtFrame,
1163 aOutFramesWithProps, aStopAtStackingContext)) {
1164 return false;
1167 if (!overflow.IsEmpty()) {
1168 aOutDirty->UnionRect(*aOutDirty, overflow);
1169 CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x,
1170 overflow.y, overflow.width, overflow.height);
1172 // If we get changed frames from multiple AGRS, then just give up as it gets
1173 // really complex to track which items would need to be marked in
1174 // MarkFramesForDifferentAGR.
1175 if (!*aOutModifiedAGR) {
1176 CRR_LOG("Setting %p as root stacking context AGR\n", agr);
1177 *aOutModifiedAGR = agr;
1178 } else if (agr && *aOutModifiedAGR != agr) {
1179 CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
1180 return false;
1183 return true;
1186 static void AddFramesForContainingBlock(nsIFrame* aBlock,
1187 const nsFrameList& aFrames,
1188 nsTArray<nsIFrame*>& aExtraFrames) {
1189 for (nsIFrame* f : aFrames) {
1190 if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) {
1191 CRR_LOG("Adding invalid OOF %p\n", f);
1192 aExtraFrames.AppendElement(f);
1197 // Placeholder descendants of aFrame don't contribute to aFrame's overflow area.
1198 // Find all the containing blocks that might own placeholders under us, walk
1199 // their OOF frames list, and manually invalidate any frames that are
1200 // descendants of a modified frame (us, or another frame we'll get to soon).
1201 // This is combined with the work required for MarkFrameForDisplayIfVisible,
1202 // so that we can avoid an extra ancestor walk, and we can reuse the flag
1203 // to detect when we've already visited an ancestor (and thus all further
1204 // ancestors must also be visited).
1205 static void FindContainingBlocks(nsIFrame* aFrame,
1206 nsTArray<nsIFrame*>& aExtraFrames) {
1207 for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
1208 if (f->ForceDescendIntoIfVisible()) {
1209 return;
1211 f->SetForceDescendIntoIfVisible(true);
1212 CRR_LOG("Considering OOFs for %p\n", f);
1214 AddFramesForContainingBlock(f, f->GetChildList(nsIFrame::kFloatList),
1215 aExtraFrames);
1216 AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()),
1217 aExtraFrames);
1222 * Given a list of frames that has been modified, computes the region that we
1223 * need to do display list building for in order to build all modified display
1224 * items.
1226 * When a modified frame is within a stacking context (with an existing display
1227 * item), then we only contribute to the build area within the stacking context,
1228 * as well as forcing display list building to descend to the stacking context.
1229 * We don't need to add build area outside of the stacking context (and force
1230 * items above/below the stacking context container item to be built), since
1231 * just matching the position of the stacking context container item is
1232 * sufficient to ensure correct ordering during merging.
1234 * We need to rebuild all items that might intersect with the modified frame,
1235 * both now and during async changes on the compositor. We do this by rebuilding
1236 * the area covered by the changed frame, as well as rebuilding all items that
1237 * have a different (async) AGR to the changed frame. If we have changes to
1238 * multiple AGRs (within a stacking context), then we rebuild that stacking
1239 * context entirely.
1241 * @param aModifiedFrames The list of modified frames.
1242 * @param aOutDirty The result region to use for display list building.
1243 * @param aOutModifiedAGR The modified AGR for the root stacking context.
1244 * @param aOutFramesWithProps The list of frames to which we attached partial
1245 * build data so that it can be cleaned up.
1247 * @return true if we succesfully computed a partial rebuild region, false if a
1248 * full build is required.
1250 bool RetainedDisplayListBuilder::ComputeRebuildRegion(
1251 nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty,
1252 AnimatedGeometryRoot** aOutModifiedAGR,
1253 nsTArray<nsIFrame*>& aOutFramesWithProps) {
1254 CRR_LOG("Computing rebuild regions for %zu frames:\n",
1255 aModifiedFrames.Length());
1256 nsTArray<nsIFrame*> extraFrames;
1257 for (nsIFrame* f : aModifiedFrames) {
1258 MOZ_ASSERT(f);
1260 mBuilder.AddFrameMarkedForDisplayIfVisible(f);
1261 FindContainingBlocks(f, extraFrames);
1263 if (!ProcessFrame(f, &mBuilder, mBuilder.RootReferenceFrame(),
1264 aOutFramesWithProps, true, aOutDirty, aOutModifiedAGR)) {
1265 return false;
1269 // Since we set modified to true on the extraFrames, add them to
1270 // aModifiedFrames so that it will get reverted.
1271 aModifiedFrames.AppendElements(extraFrames);
1273 for (nsIFrame* f : extraFrames) {
1274 f->SetFrameIsModified(true);
1276 if (!ProcessFrame(f, &mBuilder, mBuilder.RootReferenceFrame(),
1277 aOutFramesWithProps, true, aOutDirty, aOutModifiedAGR)) {
1278 return false;
1282 return true;
1285 bool RetainedDisplayListBuilder::ShouldBuildPartial(
1286 nsTArray<nsIFrame*>& aModifiedFrames) {
1287 if (mList.IsEmpty()) {
1288 // Partial builds without a previous display list do not make sense.
1289 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList;
1290 return false;
1293 if (aModifiedFrames.Length() >
1294 StaticPrefs::layout_display_list_rebuild_frame_limit()) {
1295 // Computing a dirty rect with too many modified frames can be slow.
1296 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit;
1297 return false;
1300 // We don't support retaining with overlay scrollbars, since they require
1301 // us to look at the display list and pick the highest z-index, which
1302 // we can't do during partial building.
1303 if (mBuilder.DisablePartialUpdates()) {
1304 mBuilder.SetDisablePartialUpdates(false);
1305 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
1306 return false;
1309 for (nsIFrame* f : aModifiedFrames) {
1310 MOZ_ASSERT(f);
1312 const LayoutFrameType type = f->Type();
1314 // If we have any modified frames of the following types, it is likely that
1315 // doing a partial rebuild of the display list will be slower than doing a
1316 // full rebuild.
1317 // This is because these frames either intersect or may intersect with most
1318 // of the page content. This is either due to display port size or different
1319 // async AGR.
1320 if (type == LayoutFrameType::Viewport ||
1321 type == LayoutFrameType::PageContent ||
1322 type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) {
1323 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType;
1324 return false;
1328 return true;
1331 void RetainedDisplayListBuilder::InvalidateCaretFramesIfNeeded() {
1332 if (mPreviousCaret == mBuilder.GetCaretFrame()) {
1333 // The current caret frame is the same as the previous one.
1334 return;
1337 if (mPreviousCaret) {
1338 mPreviousCaret->MarkNeedsDisplayItemRebuild();
1341 if (mBuilder.GetCaretFrame()) {
1342 mBuilder.GetCaretFrame()->MarkNeedsDisplayItemRebuild();
1345 mPreviousCaret = mBuilder.GetCaretFrame();
1348 static void ClearFrameProps(nsTArray<nsIFrame*>& aFrames) {
1349 for (nsIFrame* f : aFrames) {
1350 if (f->HasOverrideDirtyRegion()) {
1351 f->SetHasOverrideDirtyRegion(false);
1352 f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect());
1353 f->RemoveProperty(
1354 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
1357 f->SetFrameIsModified(false);
1361 class AutoClearFramePropsArray {
1362 public:
1363 explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {}
1364 AutoClearFramePropsArray() = default;
1365 ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); }
1367 nsTArray<nsIFrame*>& Frames() { return mFrames; }
1368 bool IsEmpty() const { return mFrames.IsEmpty(); }
1370 private:
1371 nsTArray<nsIFrame*> mFrames;
1374 void RetainedDisplayListBuilder::ClearFramesWithProps() {
1375 AutoClearFramePropsArray modifiedFrames;
1376 AutoClearFramePropsArray framesWithProps;
1377 GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(),
1378 &framesWithProps.Frames());
1381 PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate(
1382 nscolor aBackstop) {
1383 mBuilder.RemoveModifiedWindowRegions();
1385 if (mBuilder.ShouldSyncDecodeImages()) {
1386 MarkFramesWithItemsAndImagesModified(&mList);
1389 InvalidateCaretFramesIfNeeded();
1391 mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
1393 // We set the override dirty regions during ComputeRebuildRegion or in
1394 // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change
1395 // also marks the frame modified, so those regions are cleared here as well.
1396 AutoClearFramePropsArray modifiedFrames(64);
1397 AutoClearFramePropsArray framesWithProps;
1398 GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(),
1399 &framesWithProps.Frames());
1401 // Do not allow partial builds if the |ShouldBuildPartial()| heuristic fails.
1402 bool shouldBuildPartial = ShouldBuildPartial(modifiedFrames.Frames());
1404 nsRect modifiedDirty;
1405 AnimatedGeometryRoot* modifiedAGR = nullptr;
1406 PartialUpdateResult result = PartialUpdateResult::NoChange;
1407 if (!shouldBuildPartial ||
1408 !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
1409 &modifiedAGR, framesWithProps.Frames()) ||
1410 !PreProcessDisplayList(&mList, modifiedAGR, result)) {
1411 mBuilder.SetPartialBuildFailed(true);
1412 mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr);
1413 mList.DeleteAll(&mBuilder);
1414 return PartialUpdateResult::Failed;
1417 // This is normally handled by EnterPresShell, but we skipped it so that we
1418 // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
1419 nsIScrollableFrame* sf = mBuilder.RootReferenceFrame()
1420 ->PresShell()
1421 ->GetRootScrollFrameAsScrollable();
1422 if (sf) {
1423 nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
1424 if (canvasFrame) {
1425 mBuilder.MarkFrameForDisplayIfVisible(canvasFrame,
1426 mBuilder.RootReferenceFrame());
1430 modifiedDirty.IntersectRect(
1431 modifiedDirty,
1432 mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf());
1434 mBuilder.SetDirtyRect(modifiedDirty);
1435 mBuilder.SetPartialUpdate(true);
1436 mBuilder.SetPartialBuildFailed(false);
1438 nsDisplayList modifiedDL;
1439 mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(
1440 &mBuilder, &modifiedDL);
1441 if (!modifiedDL.IsEmpty()) {
1442 nsLayoutUtils::AddExtraBackgroundItems(
1443 &mBuilder, &modifiedDL, mBuilder.RootReferenceFrame(),
1444 nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
1445 mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf(),
1446 aBackstop);
1448 mBuilder.SetPartialUpdate(false);
1450 if (mBuilder.PartialBuildFailed()) {
1451 mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr);
1452 mList.DeleteAll(&mBuilder);
1453 modifiedDL.DeleteAll(&mBuilder);
1454 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content;
1455 return PartialUpdateResult::Failed;
1458 // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
1459 // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width,
1460 // modifiedDirty.height);
1461 // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL);
1463 // |modifiedDL| can sometimes be empty here. We still perform the
1464 // display list merging to prune unused items (for example, items that
1465 // are not visible anymore) from the old list.
1466 // TODO: Optimization opportunity. In this case, MergeDisplayLists()
1467 // unnecessarily creates a hashtable of the old items.
1468 // TODO: Ideally we could skip this if result is NoChange, but currently when
1469 // we call RestoreState on nsDisplayWrapList it resets the clip to the base
1470 // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
1471 // move it to the correct inner clip.
1472 Maybe<const ActiveScrolledRoot*> dummy;
1473 if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
1474 result = PartialUpdateResult::Updated;
1477 // printf_stderr("Painting --- Merged list:\n");
1478 // nsIFrame::PrintDisplayList(&mBuilder, mList);
1480 mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
1481 return result;