--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -120,16 +120,17 @@
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "RegionBuilder.h"
#include "SVGViewportElement.h"
#include "DisplayItemClip.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "prenv.h"
+#include "RetainedDisplayListBuilder.h"
#include "TextDrawTarget.h"
#include "nsDeckFrame.h"
#include "nsIEffectiveTLDService.h" // for IsInStyloBlocklist
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#endif
@@ -3601,18 +3602,48 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
nsPresContext* presContext = aFrame->PresContext();
nsIPresShell* presShell = presContext->PresShell();
nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
if (!rootPresContext) {
return NS_OK;
}
TimeStamp startBuildDisplayList = TimeStamp::Now();
- nsDisplayListBuilder builder(aFrame, aBuilderMode,
- !(aFlags & PaintFrameFlags::PAINT_HIDE_CARET));
+
+ Maybe<nsDisplayListBuilder> nonRetainedBuilder;
+ Maybe<nsDisplayList> nonRetainedList;
+ nsDisplayListBuilder* builderPtr = nullptr;
+ nsDisplayList* listPtr = nullptr;
+ RetainedDisplayListBuilder* retainedBuilder = nullptr;
+
+ const bool buildCaret = !(aFlags & PaintFrameFlags::PAINT_HIDE_CARET);
+ const bool retainDisplayList = gfxPrefs::LayoutRetainDisplayList();
+
+ if (retainDisplayList &&
+ aBuilderMode == nsDisplayListBuilderMode::PAINTING &&
+ (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS)) {
+ retainedBuilder = aFrame->GetProperty(RetainedDisplayListBuilder::Cached());
+
+ if (!retainedBuilder) {
+ retainedBuilder =
+ new RetainedDisplayListBuilder(aFrame, aBuilderMode, buildCaret);
+ aFrame->SetProperty(RetainedDisplayListBuilder::Cached(), retainedBuilder);
+ }
+
+ MOZ_ASSERT(retainedBuilder);
+ builderPtr = retainedBuilder->Builder();
+ listPtr = retainedBuilder->List();
+ } else {
+ nonRetainedBuilder.emplace(aFrame, aBuilderMode, buildCaret);
+ builderPtr = nonRetainedBuilder.ptr();
+ nonRetainedList.emplace(builderPtr);
+ listPtr = nonRetainedList.ptr();
+ }
+ nsDisplayListBuilder& builder = *builderPtr;
+ nsDisplayList& list = *listPtr;
builder.BeginFrame();
if (aFlags & PaintFrameFlags::PAINT_IN_TRANSFORM) {
builder.SetInTransform(true);
}
if (aFlags & PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES) {
builder.SetSyncDecodeImages(true);
@@ -3643,18 +3674,16 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
// |ignoreViewportScrolling| and |usingDisplayPort| are persistent
// document-rendering state. We rely on PresShell to flush
// retained layers as needed when that persistent state changes.
visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf();
} else {
visibleRegion = aDirtyRegion;
}
- nsDisplayList list(&builder);
-
// If the root has embedded plugins, flag the builder so we know we'll need
// to update plugin geometry after painting.
if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) &&
!(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) &&
rootPresContext->NeedToComputePluginGeometryUpdates()) {
builder.SetWillComputePluginGeometry(true);
}
@@ -3697,18 +3726,18 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
nsRect dirtyRect = visibleRegion.GetBounds();
{
AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList",
GRAPHICS);
AUTO_PROFILER_TRACING("Paint", "DisplayList");
PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList);
-
- builder.EnterPresShell(aFrame);
+ TimeStamp dlStart = TimeStamp::Now();
+
{
// If a scrollable container layer is created in nsDisplayList::PaintForFrame,
// it will be the scroll parent for display items that are built in the
// BuildDisplayListForStackingContext call below. We need to set the scroll
// parent on the display list builder while we build those items, so that they
// can pick up their scroll parent's id.
ViewID id = FrameMetrics::NULL_SCROLL_ID;
if (ignoreViewportScrolling && presContext->IsRootContentDocument()) {
@@ -3726,30 +3755,49 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
// nsGfxScrollFrame::BuilDisplayList will do it instead.
if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) {
id = nsLayoutUtils::FindOrCreateIDFor(element);
}
}
nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
- builder.SetDirtyRect(dirtyRect);
builder.SetVisibleRect(dirtyRect);
- aFrame->BuildDisplayListForStackingContext(&builder, &list);
- }
-
- AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
-
- builder.LeavePresShell(aFrame, &list);
+ builder.SetIsBuilding(true);
+
+ const bool paintedPreviously =
+ aFrame->HasProperty(nsIFrame::ModifiedFrameList());
+
+ // Attempt to do a partial build and merge into the existing list.
+ // This calls BuildDisplayListForStacking context on a subset of the
+ // viewport.
+ bool merged = false;
+ if (retainedBuilder && paintedPreviously) {
+ merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
+ }
+
+ if (!merged) {
+ list.DeleteAll(&builder);
+ builder.EnterPresShell(aFrame);
+ builder.SetDirtyRect(dirtyRect);
+ builder.ClearWindowDraggingRegion();
+ aFrame->BuildDisplayListForStackingContext(&builder, &list);
+ AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
+
+ builder.LeavePresShell(aFrame, &list);
+ }
+ }
+
+ builder.SetIsBuilding(false);
builder.IncrementPresShellPaintCount(presShell);
- if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) {
+ if (gfxPrefs::LayersDrawFPS()) {
if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
- pt->dlMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
+ pt->dlMs() = (TimeStamp::Now() - dlStart).ToMilliseconds();
}
}
}
}
Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
startBuildDisplayList);
@@ -3929,20 +3977,27 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
// We told the compositor thread not to composite when it received the
// transaction because we wanted to update plugins first. Schedule the
// composite now.
if (layerManager) {
layerManager->ScheduleComposite();
}
}
- builder.EndFrame();
-
- // Flush the list so we don't trigger the IsEmpty-on-destruction assertion
- list.DeleteAll(&builder);
+ {
+ AutoProfilerTracing tracing("Paint", "DisplayListResources");
+
+ // Flush the list so we don't trigger the IsEmpty-on-destruction assertion
+ if (!retainedBuilder) {
+ list.DeleteAll(&builder);
+ builder.EndFrame();
+ } else {
+ builder.EndFrame();
+ }
+ }
return NS_OK;
}
/**
* Uses a binary search for find where the cursor falls in the line of text
* It also keeps track of the part of the string that has already been measured
* so it doesn't have to keep measuring the same text over and over
*
new file mode 100644
--- /dev/null
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -0,0 +1,767 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=78:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "RetainedDisplayListBuilder.h"
+#include "nsSubDocumentFrame.h"
+
+/**
+ * Code for doing display list building for a modified subset of the window,
+ * and then merging it into the existing display list (for the full window).
+ *
+ * The approach primarily hinges on the observation that the ‘true’ ordering of
+ * display items is represented by a DAG (only items that intersect in 2d space
+ * have a defined ordering). Our display list is just one of a many possible linear
+ * representations of this ordering.
+ *
+ * Each time a frame changes (gets a new style context, or has a size/position
+ * change), we schedule a paint (as we do currently), but also reord the frame that
+ * changed.
+ *
+ * When the next paint occurs we union the overflow areas (in screen space) of the
+ * changed frames, and compute a rect/region that contains all changed items. We
+ * then build a display list just for this subset of the screen and merge it into
+ * the display list from last paint.
+ *
+ * Any items that exist in one list and not the other must not have a defined
+ * ordering in the DAG, since they need to intersect to have an ordering and
+ * we would have built both in the new list if they intersected. Given that, we
+ * can align items that appear in both lists, and any items that appear between
+ * matched items can be inserted into the merged list in any order.
+ */
+
+using namespace mozilla;
+
+void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList)
+{
+ for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
+ if (!i->HasDeletedFrame() && i->CanBeReused() && !i->Frame()->IsFrameModified()) {
+ // If we have existing cached geometry for this item, then check that for
+ // whether we need to invalidate for a sync decode. If we don't, then
+ // use the item's flags.
+ DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
+ bool invalidate = false;
+ if (data &&
+ data->GetGeometry()) {
+ invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages();
+ } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
+ invalidate = true;
+ }
+
+ if (invalidate) {
+ i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
+ }
+ }
+ if (i->GetChildren()) {
+ MarkFramesWithItemsAndImagesModified(i->GetChildren());
+ }
+ }
+}
+
+bool IsAnyAncestorModified(nsIFrame* aFrame)
+{
+ nsIFrame* f = aFrame;
+ while (f) {
+ if (f->IsFrameModified()) {
+ return true;
+ }
+ f = nsLayoutUtils::GetCrossDocParentFrame(f);
+ }
+ return false;
+}
+
+// Removes any display items that belonged to a frame that was deleted,
+// and mark frames that belong to a different AGR so that get their
+// items built again.
+// TODO: We currently descend into all children even if we don't have an AGR
+// to mark, as child stacking contexts might. It would be nice if we could
+// jump into those immediately rather than walking the entire thing.
+void
+RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList,
+ AnimatedGeometryRoot* aAGR)
+{
+ nsDisplayList saved(&mBuilder);
+ while (nsDisplayItem* i = aList->RemoveBottom()) {
+ if (i->HasDeletedFrame() || !i->CanBeReused()) {
+ i->Destroy(&mBuilder);
+ continue;
+ }
+
+ nsIFrame* f = i->Frame();
+
+ if (i->GetChildren()) {
+ AnimatedGeometryRoot *childAGR = aAGR;
+ if (f->IsStackingContext()) {
+ if (f->HasOverrideDirtyRegion()) {
+ nsDisplayListBuilder::DisplayListBuildingData* data =
+ f->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ if (data) {
+ childAGR = data->mModifiedAGR;
+ }
+ } else {
+ childAGR = nullptr;
+ }
+ }
+ PreProcessDisplayList(i->GetChildren(), childAGR);
+ }
+
+ // TODO: We should be able to check the clipped bounds relative
+ // to the common AGR (of both the existing item and the invalidated
+ // frame) and determine if they can ever intersect.
+ if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
+ mBuilder.MarkFrameForDisplayIfVisible(f);
+ }
+
+ // TODO: This is here because we sometimes reuse the previous display list
+ // completely. For optimization, we could only restore the state for reused
+ // display items.
+ i->RestoreState();
+
+ saved.AppendToTop(i);
+ }
+ aList->AppendToTop(&saved);
+ aList->RestoreState();
+}
+
+bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
+{
+ return aFirst->Frame() == aSecond->Frame() &&
+ aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey();
+}
+
+struct DisplayItemKey
+{
+ bool operator ==(const DisplayItemKey& aOther) const {
+ return mFrame == aOther.mFrame &&
+ mPerFrameKey == aOther.mPerFrameKey;
+ }
+
+ nsIFrame* mFrame;
+ uint32_t mPerFrameKey;
+};
+
+class DisplayItemHashEntry : public PLDHashEntryHdr
+{
+public:
+ typedef DisplayItemKey KeyType;
+ typedef const DisplayItemKey* KeyTypePointer;
+
+ explicit DisplayItemHashEntry(KeyTypePointer aKey)
+ : mKey(*aKey) {}
+ explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy)=default;
+
+ ~DisplayItemHashEntry() = default;
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const
+ {
+ return mKey == *aKey;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ {
+ if (!aKey)
+ return 0;
+
+ return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ DisplayItemKey mKey;
+};
+
+template<typename T>
+void SwapAndRemove(nsTArray<T>& aArray, uint32_t aIndex)
+{
+ if (aIndex != (aArray.Length() - 1)) {
+ T last = aArray.LastElement();
+ aArray.LastElement() = aArray[aIndex];
+ aArray[aIndex] = last;
+ }
+
+ aArray.RemoveElementAt(aArray.Length() - 1);
+}
+
+static void
+MergeFrameRects(nsDisplayLayerEventRegions* aOldItem,
+ nsDisplayLayerEventRegions* aNewItem,
+ nsDisplayLayerEventRegions::FrameRects nsDisplayLayerEventRegions::*aRectList,
+ nsTArray<nsIFrame*>& aAddedFrames)
+{
+ // Go through the old item's rect list and remove any rectangles
+ // belonging to invalidated frames (deleted frames should
+ // already be gone at this point)
+ nsDisplayLayerEventRegions::FrameRects& oldRects = aOldItem->*aRectList;
+ uint32_t i = 0;
+ while (i < oldRects.mFrames.Length()) {
+ // TODO: As mentioned in nsDisplayLayerEventRegions, this
+ // operation might perform really poorly on a vector.
+ nsIFrame* f = oldRects.mFrames[i];
+ if (IsAnyAncestorModified(f)) {
+ MOZ_ASSERT(f != aOldItem->Frame());
+ f->RemoveDisplayItem(aOldItem);
+ SwapAndRemove(oldRects.mFrames, i);
+ SwapAndRemove(oldRects.mBoxes, i);
+ } else {
+ i++;
+ }
+ }
+ if (!aNewItem) {
+ return;
+ }
+
+ // Copy items from the source list to the dest list, but
+ // only if the dest doesn't already include them.
+ nsDisplayItem* destItem = aOldItem;
+ nsDisplayLayerEventRegions::FrameRects* destRects = &(aOldItem->*aRectList);
+ nsDisplayLayerEventRegions::FrameRects* srcRects = &(aNewItem->*aRectList);
+
+ for (uint32_t i = 0; i < srcRects->mFrames.Length(); i++) {
+ nsIFrame* f = srcRects->mFrames[i];
+ if (!f->HasDisplayItem(destItem)) {
+ // If this frame isn't already in the destination item,
+ // then add it!
+ destRects->Add(f, srcRects->mBoxes[i]);
+
+ // We also need to update RealDisplayItemData for 'f',
+ // but that'll mess up this check for the following
+ // FrameRects lists, so defer that until the end.
+ aAddedFrames.AppendElement(f);
+ MOZ_ASSERT(f != aOldItem->Frame());
+ }
+
+ }
+}
+
+void MergeLayerEventRegions(nsDisplayItem* aOldItem,
+ nsDisplayItem* aNewItem)
+{
+ nsDisplayLayerEventRegions* oldItem =
+ static_cast<nsDisplayLayerEventRegions*>(aOldItem);
+ nsDisplayLayerEventRegions* newItem =
+ static_cast<nsDisplayLayerEventRegions*>(aNewItem);
+
+ nsTArray<nsIFrame*> addedFrames;
+
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames);
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames);
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames);
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames);
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames);
+ MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames);
+
+ // MergeFrameRects deferred updating the display item data list during
+ // processing so that earlier calls didn't change the result of later
+ // ones. Fix that up now.
+ for (nsIFrame* f : addedFrames) {
+ if (!f->HasDisplayItem(aOldItem)) {
+ f->AddDisplayItem(aOldItem);
+ }
+ }
+}
+
+void
+RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem)
+{
+ MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
+
+ nsSubDocumentFrame* subDocFrame =
+ static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
+ MOZ_ASSERT(subDocFrame);
+
+ nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
+ MOZ_ASSERT(presShell);
+
+ mBuilder.IncrementPresShellPaintCount(presShell);
+}
+
+/**
+ * Takes two display lists and merges them into an output list.
+ *
+ * The basic algorithm is:
+ *
+ * For-each item in the new list:
+ * If the item has a matching item in the old list:
+ * Remove items from the bottom of the old list until we reach the matching item:
+ * Add valid items to the merged list, destroy invalid items.
+ * Destroy the matching item from the old list.
+ * Add the item from the new list into the merged list.
+ * Add all remaining valid items from the old list into the merged list.
+ *
+ * If any item has a child display list, then we recurse into the merge
+ * algorithm once we match up the new/old versions (if present).
+ *
+ * Example 1:
+ *
+ * Old List: A,B,C,D
+ * New List: A,D
+ * Invalidations: C,D
+ *
+ * We first match the A items, and add the new one to the merged list.
+ * We then match the D items, copy B into the merged list, but not C
+ * (since it's invalid). We then add the new D to the list and we're
+ * finished.
+ *
+ * Merged List: A,B,D
+ *
+ * Example 2:
+ *
+ * Old List: A, B
+ * New List, B, A
+ * Invalidations: -
+ *
+ * This can happen because a prior merge might have changed the ordering
+ * for non-intersecting items.
+ *
+ * We match the B items, and copy A from the old list into the merged list,
+ * and then the new B into the merged list.
+ * We now get to A in the new list, but A has already been added to the merged
+ * list from the old list. This is fine since they must be identical (or we
+ * would have invalidated A), so we detect it and just destroy the new
+ * instance of A.
+ *
+ * Merged List: A, B
+ */
+void
+RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList,
+ nsDisplayList* aOldList,
+ nsDisplayList* aOutList)
+{
+ nsDisplayList merged(&mBuilder);
+
+ const auto ReuseItem = [&](nsDisplayItem* aItem) {
+ merged.AppendToTop(aItem);
+ aItem->SetReused(true);
+
+ if (aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
+ IncrementSubDocPresShellPaintCount(aItem);
+ }
+ };
+
+ // Build a hashtable of items in the old list so we can look for them quickly.
+ // We have similar data in the nsIFrame DisplayItems() property, but it doesn't
+ // know which display list items are in, and we only want to match items in
+ // this list.
+ nsDataHashtable<DisplayItemHashEntry, nsDisplayItem*> oldListLookup(aOldList->Count());
+
+ for (nsDisplayItem* i = aOldList->GetBottom(); i != nullptr; i = i->GetAbove()) {
+ i->SetReused(false);
+
+ if (!aNewList->IsEmpty()) {
+ oldListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i);
+ }
+ }
+
+#ifdef DEBUG
+ nsDataHashtable<DisplayItemHashEntry, nsDisplayItem*> newListLookup(aNewList->Count());
+ for (nsDisplayItem* i = aNewList->GetBottom(); i != nullptr; i = i->GetAbove()) {
+ if (newListLookup.Get({ i->Frame(), i->GetPerFrameKey() }, nullptr)) {
+ MOZ_CRASH_UNSAFE_PRINTF("Duplicate display items detected!: %s(0x%p) type=%d key=%d",
+ i->Name(), i->Frame(),
+ static_cast<int>(i->GetType()), i->GetPerFrameKey());
+ }
+ newListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i);
+ }
+#endif
+
+ while (nsDisplayItem* newItem = aNewList->RemoveBottom()) {
+ if (nsDisplayItem* oldItem = oldListLookup.Get({ newItem->Frame(), newItem->GetPerFrameKey() })) {
+ if (oldItem->IsReused()) {
+ // If there's a matching item in the old list, but we've already put it into the
+ // merged list then stick with that one. Merge any child lists, and then delete the
+ // new item. This solves example 2 from above.
+
+ if (oldItem->GetChildren()) {
+ MOZ_ASSERT(newItem->GetChildren());
+ MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), oldItem->GetChildren());
+ oldItem->UpdateBounds(&mBuilder);
+ }
+ if (oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
+ MergeLayerEventRegions(oldItem, newItem);
+ }
+ newItem->Destroy(&mBuilder);
+ } else {
+ // The new item has a matching counterpart in the old list, so copy all valid
+ // items from the old list into the merged list until we get to the matched item.
+ nsDisplayItem* old = nullptr;
+ while ((old = aOldList->RemoveBottom()) && !IsSameItem(newItem, old)) {
+ if (!IsAnyAncestorModified(old->FrameForInvalidation())) {
+ ReuseItem(old);
+ } else {
+ oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() });
+ old->Destroy(&mBuilder);
+ }
+ }
+ MOZ_ASSERT(old && IsSameItem(newItem, old));
+ MOZ_ASSERT(old == oldItem);
+
+ // Recursively merge any child lists, destroy the old item and add
+ // the new one to the list.
+ if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
+ !IsAnyAncestorModified(old->FrameForInvalidation())) {
+ // Event regions items don't have anything interesting other than
+ // the lists of regions and frames, so we have no need to use the
+ // newer item. Always use the old item instead since we assume it's
+ // likely to have the bigger lists and merging will be quicker.
+ MergeLayerEventRegions(old, newItem);
+ ReuseItem(old);
+ newItem->Destroy(&mBuilder);
+ } else {
+ if (!IsAnyAncestorModified(old->FrameForInvalidation()) &&
+ old->GetChildren()) {
+ MOZ_ASSERT(newItem->GetChildren());
+ MergeDisplayLists(newItem->GetChildren(), old->GetChildren(), newItem->GetChildren());
+ newItem->UpdateBounds(&mBuilder);
+ }
+
+ old->Destroy(&mBuilder);
+ merged.AppendToTop(newItem);
+ }
+ }
+ } else {
+ // If there was no matching item in the old list, then we only need to
+ // add the new item to the merged list.
+ merged.AppendToTop(newItem);
+ }
+ }
+
+ // Reuse the remaining valid items from the old display list.
+ while (nsDisplayItem* old = aOldList->RemoveBottom()) {
+ if (!IsAnyAncestorModified(old->FrameForInvalidation())) {
+ ReuseItem(old);
+
+ if (old->GetChildren()) {
+ // We are calling MergeDisplayLists() to ensure that the display items
+ // with modified or deleted children will be correctly handled.
+ // Passing an empty new display list as an argument skips the merging
+ // loop above and jumps back here.
+ nsDisplayList empty(&mBuilder);
+
+ MergeDisplayLists(&empty, old->GetChildren(), old->GetChildren());
+ old->UpdateBounds(&mBuilder);
+ }
+ if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
+ MergeLayerEventRegions(old, nullptr);
+ }
+ } else {
+ old->Destroy(&mBuilder);
+ }
+ }
+
+ aOutList->AppendToTop(&merged);
+}
+
+static void
+TakeAndAddModifiedFramesFromRootFrame(std::vector<WeakFrame>& aFrames,
+ nsIFrame* aRootFrame)
+{
+ MOZ_ASSERT(aRootFrame);
+
+ std::vector<WeakFrame>* frames =
+ aRootFrame->GetProperty(nsIFrame::ModifiedFrameList());
+
+ if (frames) {
+ for (WeakFrame& frame : *frames) {
+ aFrames.push_back(Move(frame));
+ }
+
+ frames->clear();
+ }
+}
+
+static bool
+SubDocEnumCb(nsIDocument* aDocument, void* aData)
+{
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(aData);
+
+ std::vector<WeakFrame>* modifiedFrames =
+ static_cast<std::vector<WeakFrame>*>(aData);
+
+ nsIPresShell* presShell = aDocument->GetShell();
+ nsIFrame* rootFrame = presShell ? presShell->GetRootFrame() : nullptr;
+
+ if (rootFrame) {
+ TakeAndAddModifiedFramesFromRootFrame(*modifiedFrames, rootFrame);
+ }
+
+ aDocument->EnumerateSubDocuments(SubDocEnumCb, aData);
+ return true;
+}
+
+static std::vector<WeakFrame>
+GetModifiedFrames(nsIFrame* aDisplayRootFrame)
+{
+ MOZ_ASSERT(aDisplayRootFrame);
+
+ std::vector<WeakFrame> modifiedFrames;
+ TakeAndAddModifiedFramesFromRootFrame(modifiedFrames, aDisplayRootFrame);
+
+ nsIDocument *rootdoc = aDisplayRootFrame->PresContext()->Document();
+
+ if (rootdoc) {
+ rootdoc->EnumerateSubDocuments(SubDocEnumCb, &modifiedFrames);
+ }
+
+ return modifiedFrames;
+}
+
+// ComputeRebuildRegion debugging
+// #define CRR_DEBUG 1
+#if CRR_DEBUG
+# define CRR_LOG(...) printf_stderr(__VA_ARGS__)
+#else
+# define CRR_LOG(...)
+#endif
+
+/**
+ * Given a list of frames that has been modified, computes the region that we need to
+ * do display list building for in order to build all modified display items.
+ *
+ * When a modified frame is within a stacking context (with an existing display item),
+ * then we only contribute to the build area within the stacking context, as well as forcing
+ * display list building to descend to the stacking context. We don't need to add build
+ * area outside of the stacking context (and force items above/below the stacking context
+ * container item to be built), since just matching the position of the stacking context
+ * container item is sufficient to ensure correct ordering during merging.
+ *
+ * We need to rebuild all items that might intersect with the modified frame, both now
+ * and during async changes on the compositor. We do this by rebuilding the area covered
+ * by the changed frame, as well as rebuilding all items that have a different (async)
+ * AGR to the changed frame. If we have changes to multiple AGRs (within a stacking
+ * context), then we rebuild that stacking context entirely.
+ *
+ * @param aModifiedFrames The list of modified frames.
+ * @param aOutDirty The result region to use for display list building.
+ * @param aOutModifiedAGR The modified AGR for the root stacking context.
+ * @param aOutFramesWithProps The list of frames to which we attached partial build
+ * data so that it can be cleaned up.
+ *
+ * @return true if we succesfully computed a partial rebuild region, false if a full
+ * build is required.
+ */
+bool
+RetainedDisplayListBuilder::ComputeRebuildRegion(std::vector<WeakFrame>& aModifiedFrames,
+ nsRect* aOutDirty,
+ AnimatedGeometryRoot** aOutModifiedAGR,
+ nsTArray<nsIFrame*>* aOutFramesWithProps)
+{
+ CRR_LOG("Computing rebuild regions for %d frames:\n", aModifiedFrames.size());
+ for (nsIFrame* f : aModifiedFrames) {
+ if (!f) {
+ continue;
+ }
+
+ if (f->HasOverrideDirtyRegion()) {
+ aOutFramesWithProps->AppendElement(f);
+ }
+
+ // TODO: There is almost certainly a faster way of doing this, probably can be combined with the ancestor
+ // walk for TransformFrameRectToAncestor.
+ AnimatedGeometryRoot* agr = mBuilder.FindAnimatedGeometryRootFor(f)->GetAsyncAGR();
+
+ CRR_LOG("Processing frame %p with agr %p\n", f, agr->mFrame);
+
+
+ // Convert the frame's overflow rect into the coordinate space
+ // of the nearest stacking context that has an existing display item.
+ // We store that as a dirty rect on that stacking context so that we build
+ // all items that intersect the changed frame within the stacking context,
+ // and then we use MarkFrameForDisplayIfVisible to make sure the stacking
+ // context itself gets built. We don't need to build items that intersect outside
+ // of the stacking context, since we know the stacking context item exists in
+ // the old list, so we can trivially merge without needing other items.
+ nsRect overflow = f->GetVisualOverflowRectRelativeToSelf();
+ nsIFrame* currentFrame = f;
+
+ while (currentFrame != mBuilder.RootReferenceFrame()) {
+ // Convert 'overflow' into the coordinate space of the nearest stacking context
+ // or display port ancestor and update 'currentFrame' to point to that frame.
+ overflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, overflow, mBuilder.RootReferenceFrame(),
+ nullptr, nullptr,
+ /* aStopAtStackingContextAndDisplayPort = */ true,
+ ¤tFrame);
+ MOZ_ASSERT(currentFrame);
+
+ if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
+ CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
+ nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
+ MOZ_ASSERT(sf);
+ nsRect displayPort;
+ DebugOnly<bool> hasDisplayPort =
+ nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort, RelativeTo::ScrollPort);
+ MOZ_ASSERT(hasDisplayPort);
+ // get it relative to the scrollport (from the scrollframe)
+ nsRect r = overflow - sf->GetScrollPortRect().TopLeft();
+ r.IntersectRect(r, displayPort);
+ if (!r.IsEmpty()) {
+ nsRect* rect =
+ currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+ if (!rect) {
+ rect = new nsRect();
+ currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
+ currentFrame->SetHasOverrideDirtyRegion(true);
+ }
+ rect->UnionRect(*rect, r);
+ aOutFramesWithProps->AppendElement(currentFrame);
+ CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, r.width, r.height);
+
+ // TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to
+ // ensure that this displayport, plus any items that move relative to it get rebuilt,
+ // and then not contribute to the root dirty area?
+ overflow = sf->GetScrollPortRect();
+ } else {
+ // Don't contribute to the root dirty area at all.
+ overflow.SetEmpty();
+ break;
+ }
+ }
+
+ if (currentFrame->IsStackingContext()) {
+ CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
+ // If we found an intermediate stacking context with an existing display item
+ // then we can store the dirty rect there and stop. If we couldn't find one then
+ // we need to keep bubbling up to the next stacking context.
+ if (currentFrame != mBuilder.RootReferenceFrame() &&
+ currentFrame->HasDisplayItems()) {
+ mBuilder.MarkFrameForDisplayIfVisible(currentFrame);
+
+ // Store the stacking context relative dirty area such
+ // that display list building will pick it up when it
+ // gets to it.
+ nsDisplayListBuilder::DisplayListBuildingData* data =
+ currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ if (!data) {
+ data = new nsDisplayListBuilder::DisplayListBuildingData;
+ currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingRect(), data);
+ currentFrame->SetHasOverrideDirtyRegion(true);
+ aOutFramesWithProps->AppendElement(currentFrame);
+ }
+ data->mDirtyRect.UnionRect(data->mDirtyRect, overflow);
+ CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
+ overflow.x, overflow.y, overflow.width, overflow.height);
+ if (!data->mModifiedAGR) {
+ data->mModifiedAGR = agr;
+ } else if (data->mModifiedAGR != agr) {
+ data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf();
+ CRR_LOG("Found multiple modified AGRs within this stacking context, giving up\n");
+ }
+
+ // Don't contribute to the root dirty area at all.
+ agr = nullptr;
+ overflow.SetEmpty();
+ break;
+ }
+ }
+ }
+ aOutDirty->UnionRect(*aOutDirty, overflow);
+ CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, overflow.y, overflow.width, overflow.height);
+
+ // If we get changed frames from multiple AGRS, then just give up as it gets really complex to
+ // track which items would need to be marked in MarkFramesForDifferentAGR.
+ if (!*aOutModifiedAGR) {
+ *aOutModifiedAGR = agr;
+ } else if (agr && *aOutModifiedAGR != agr) {
+ CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
+{
+ mBuilder.RemoveModifiedWindowDraggingRegion();
+ if (mBuilder.ShouldSyncDecodeImages()) {
+ MarkFramesWithItemsAndImagesModified(&mList);
+ }
+
+ mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
+
+ std::vector<WeakFrame> modifiedFrames = GetModifiedFrames(mBuilder.RootReferenceFrame());
+
+ if (mPreviousCaret != mBuilder.GetCaretFrame()) {
+ if (mPreviousCaret) {
+ mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret);
+ }
+
+ if (mBuilder.GetCaretFrame()) {
+ mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame());
+ }
+
+ mPreviousCaret = mBuilder.GetCaretFrame();
+ }
+
+ nsRect modifiedDirty;
+ AnimatedGeometryRoot* modifiedAGR = nullptr;
+ nsTArray<nsIFrame*> framesWithProps;
+ bool merged = false;
+ if (!mList.IsEmpty() &&
+ ComputeRebuildRegion(modifiedFrames, &modifiedDirty, &modifiedAGR, &framesWithProps)) {
+ modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
+
+ PreProcessDisplayList(&mList, modifiedAGR);
+
+ nsDisplayList modifiedDL(&mBuilder);
+ if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) {
+ mBuilder.SetDirtyRect(modifiedDirty);
+ mBuilder.SetPartialUpdate(true);
+ mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
+ nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
+ nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
+ mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
+ aBackstop);
+ mBuilder.SetPartialUpdate(false);
+
+ //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
+ // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
+ //nsFrame::PrintDisplayList(&builder, modifiedDL);
+
+ } else {
+ // TODO: We can also skip layer building and painting if
+ // PreProcessDisplayList didn't end up changing anything
+ // Invariant: display items should have their original state here.
+ // printf_stderr("Skipping display list building since nothing needed to be done\n");
+ }
+
+ // |modifiedDL| can sometimes be empty here. We still perform the
+ // display list merging to prune unused items (for example, items that
+ // are not visible anymore) from the old list.
+ // TODO: Optimization opportunity. In this case, MergeDisplayLists()
+ // unnecessarily creates a hashtable of the old items.
+ MergeDisplayLists(&modifiedDL, &mList, &mList);
+
+ //printf_stderr("Painting --- Merged list:\n");
+ //nsFrame::PrintDisplayList(&builder, list);
+
+ merged = true;
+ }
+
+ mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
+
+ // TODO: Do we mark frames as modified during displaylist building? If
+ // we do this isn't gonna work.
+ for (nsIFrame* f : modifiedFrames) {
+ if (f) {
+ f->SetFrameIsModified(false);
+ }
+ }
+ modifiedFrames.clear();
+
+ // Override dirty regions should only exist during this function. We set them up during
+ // ComputeRebuildRegion, and clear them here.
+ for (nsIFrame* f: framesWithProps) {
+ f->SetHasOverrideDirtyRegion(false);
+ f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+ }
+
+ return merged;
+}