gecko-dev/layout/painting/RetainedDisplayListBuilder.cpp
Matt Woodrow 0ab5810be5 Bug 1443027 - Fix the merging algorithm to pass the new tests correctly. r=mstange
MozReview-Commit-ID: JnglCbdhZzE
* * *
[mq]: update-test

MozReview-Commit-ID: JMIzrnVeSTo

--HG--
extra : rebase_source : 0ea5ff0e79d1eb1a8f13ea4a17e37fe2601d44e7
2018-03-23 16:47:37 +13:00

1096 lines
40 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "DisplayListChecker.h"
#include "gfxPrefs.h"
#include "nsPlaceholderFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsViewManager.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 ComputedStyle, 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;
static 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);
//XXX: handle webrender case
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->GetDependentFrame()) {
i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
}
}
}
if (i->GetChildren()) {
MarkFramesWithItemsAndImagesModified(i->GetChildren());
}
}
}
static bool
IsAnyAncestorModified(nsIFrame* aFrame)
{
nsIFrame* f = aFrame;
while (f) {
if (f->IsFrameModified()) {
return true;
}
f = nsLayoutUtils::GetCrossDocParentFrame(f);
}
return false;
}
static AnimatedGeometryRoot*
SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR)
{
if (!aFrame->IsStackingContext()) {
return aParentAGR;
}
if (!aFrame->HasOverrideDirtyRegion()) {
return nullptr;
}
nsDisplayListBuilder::DisplayListBuildingData* data =
aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
return data && data->mModifiedAGR ? data->mModifiedAGR.get()
: nullptr;
}
// 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.
bool
RetainedDisplayListBuilder::PreProcessDisplayList(RetainedDisplayList* aList,
AnimatedGeometryRoot* aAGR)
{
// The DAG merging algorithm does not have strong mechanisms in place to keep the
// complexity of the resulting DAG under control. In some cases we can build up
// edges very quickly. Detect those cases and force a full display list build if
// we hit them.
static const uint32_t kMaxEdgeRatio = 5;
bool initializeDAG = !aList->mDAG.Length();
if (!initializeDAG &&
aList->mDAG.mDirectPredecessorList.Length() >
(aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) {
return false;
}
nsDisplayList saved;
aList->mOldItems.SetCapacity(aList->Count());
size_t i = 0;
while (nsDisplayItem* item = aList->RemoveBottom()) {
if (item->HasDeletedFrame() || !item->CanBeReused()) {
// If we haven't yet initialized the DAG, then we can
// just drop this item. Otherwise we need to keep it
// around to preserve indices, and merging will
// get rid of it.
if (initializeDAG) {
item->Destroy(&mBuilder);
} else {
aList->mOldItems.AppendElement(OldItemInfo(item));
}
continue;
}
aList->mOldItems.AppendElement(OldItemInfo(item));
if (initializeDAG) {
if (i == 0) {
aList->mDAG.AddNode(Span<const MergedListIndex>());
} else {
MergedListIndex previous(i - 1);
aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
}
aList->mKeyLookup.Put({ item->Frame(), item->GetPerFrameKey() }, i);
i++;
}
nsIFrame* f = item->Frame();
if (item->GetChildren()) {
if (!PreProcessDisplayList(item->GetChildren(), SelectAGRForFrame(f, aAGR))) {
mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
mBuilder.MarkFrameModifiedDuringBuilding(f);
}
}
// 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 && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
}
// 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.
item->RestoreState();
}
aList->RestoreState();
return true;
}
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);
}
static void
UpdateASR(nsDisplayItem* aItem,
Maybe<const ActiveScrolledRoot*>& aContainerASR)
{
if (!aContainerASR) {
return;
}
nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
if (!wrapList) {
aItem->SetActiveScrolledRoot(aContainerASR.value());
return;
}
wrapList->SetActiveScrolledRoot(
ActiveScrolledRoot::PickAncestor(wrapList->GetFrameActiveScrolledRoot(),
aContainerASR.value()));
}
void
OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
MergedListIndex aIndex)
{
mItem->Destroy(aBuilder->Builder());
AddedToMergedList(aIndex);
}
void
OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder,
nsTArray<MergedListIndex>&& aDirectPredecessors)
{
MOZ_ASSERT(!IsUsed());
mUsed = mDiscarded = true;
mDirectPredecessors = Move(aDirectPredecessors);
mItem->Destroy(aBuilder->Builder());
mItem = nullptr;
}
/**
* A C++ implementation of Markus Stange's merge-dags algorthim.
* https://github.com/mstange/merge-dags
*
* MergeState handles combining a new list of display items into an existing
* DAG and computes the new DAG in a single pass.
* Each time we add a new item, we resolve all dependencies for it, so that the resulting
* list and DAG are built in topological ordering.
*/
class MergeState {
public:
MergeState(RetainedDisplayListBuilder* aBuilder, RetainedDisplayList&& aOldList)
: mBuilder(aBuilder)
, mOldItems(Move(aOldList.mOldItems))
, mOldDAG(Move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(&aOldList.mDAG)))
, mResultIsModified(false)
{
mOldKeyLookup.SwapElements(aOldList.mKeyLookup);
mMergedDAG.EnsureCapacityFor(mOldDAG);
}
MergedListIndex ProcessItemFromNewList(nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) {
OldListIndex oldIndex;
if (mOldKeyLookup.Get({ aNewItem->Frame(), aNewItem->GetPerFrameKey() }, &oldIndex.val)) {
bool changed = IsChanged(aNewItem);
if (!changed || HasSameSinglePredecessor(oldIndex, aPreviousItem)) {
MOZ_ASSERT(!mOldItems[oldIndex.val].IsUsed());
if (changed) {
mResultIsModified = true;
} else if (aNewItem->GetChildren()) {
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(),
mOldItems[oldIndex.val].mItem->GetChildren(),
aNewItem->GetChildren(),
containerASRForChildren)) {
mResultIsModified = true;
}
UpdateASR(aNewItem, containerASRForChildren);
aNewItem->UpdateBounds(mBuilder->Builder());
}
AutoTArray<MergedListIndex, 2> directPredecessors = ProcessPredecessorsOfOldNode(oldIndex);
MergedListIndex newIndex = AddNewNode(aNewItem, Some(oldIndex), directPredecessors, aPreviousItem);
mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex);
return newIndex;
}
}
mResultIsModified = true;
return AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(), aPreviousItem);
}
bool HasSameSinglePredecessor(OldListIndex aNode,
const Maybe<MergedListIndex>& aNewItemPredecessor)
{
if (!aNewItemPredecessor) {
return false;
}
Span<OldListIndex> directPredecessors = mOldDAG.GetDirectPredecessors(aNode);
if (directPredecessors.Length() != 1) {
return false;
}
OldItemInfo& oldItem = mOldItems[directPredecessors[0].val];
if (oldItem.IsUsed() && !oldItem.IsDiscarded()) {
return oldItem.mIndex == aNewItemPredecessor.value();
}
return false;
}
RetainedDisplayList Finalize() {
for (size_t i = 0; i < mOldDAG.Length(); i++) {
if (mOldItems[i].IsUsed()) {
continue;
}
AutoTArray<MergedListIndex, 2> directPredecessors =
ResolveNodeIndexesOldToMerged(mOldDAG.GetDirectPredecessors(OldListIndex(i)));
ProcessOldNode(OldListIndex(i), Move(directPredecessors));
}
RetainedDisplayList result;
result.AppendToTop(&mMergedItems);
result.mDAG = Move(mMergedDAG);
result.mKeyLookup.SwapElements(mMergedKeyLookup);
return result;
}
bool IsChanged(nsDisplayItem* aItem) {
return aItem->HasDeletedFrame() || !aItem->CanBeReused() ||
IsAnyAncestorModified(aItem->FrameForInvalidation());
}
void UpdateContainerASR(nsDisplayItem* aItem)
{
const ActiveScrolledRoot* itemClipASR =
aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr;
const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant(
itemClipASR, aItem->GetActiveScrolledRoot());
if (!mContainerASR) {
mContainerASR = Some(finiteBoundsASR);
} else {
mContainerASR = Some(ActiveScrolledRoot::PickAncestor(mContainerASR.value(), finiteBoundsASR));
}
}
MergedListIndex AddNewNode(nsDisplayItem* aItem,
const Maybe<OldListIndex>& aOldIndex,
Span<const MergedListIndex> aDirectPredecessors,
const Maybe<MergedListIndex>& aExtraDirectPredecessor) {
UpdateContainerASR(aItem);
mMergedItems.AppendToTop(aItem);
MergedListIndex newIndex = mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
mMergedKeyLookup.Put({ aItem->Frame(), aItem->GetPerFrameKey() }, newIndex.val);
return newIndex;
}
void ProcessOldNode(OldListIndex aNode, nsTArray<MergedListIndex>&& aDirectPredecessors) {
nsDisplayItem* item = mOldItems[aNode.val].mItem;
if (IsChanged(item)) {
mOldItems[aNode.val].Discard(mBuilder, Move(aDirectPredecessors));
mResultIsModified = true;
} else {
if (item->GetChildren()) {
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
nsDisplayList empty;
if (mBuilder->MergeDisplayLists(&empty, item->GetChildren(), item->GetChildren(),
containerASRForChildren)) {
mResultIsModified = true;
}
UpdateASR(item, containerASRForChildren);
item->UpdateBounds(mBuilder->Builder());
}
if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
mBuilder->IncrementSubDocPresShellPaintCount(item);
}
item->SetReused(true);
mOldItems[aNode.val].AddedToMergedList(
AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing()));
}
}
struct PredecessorStackItem {
PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors)
: mNode(aNode)
, mDirectPredecessors(aPredecessors)
, mCurrentPredecessorIndex(0)
{}
bool IsFinished() {
return mCurrentPredecessorIndex == mDirectPredecessors.Length();
}
OldListIndex GetAndIncrementCurrentPredecessor() { return mDirectPredecessors[mCurrentPredecessorIndex++]; }
OldListIndex mNode;
Span<OldListIndex> mDirectPredecessors;
size_t mCurrentPredecessorIndex;
};
AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode(OldListIndex aNode) {
AutoTArray<PredecessorStackItem,256> mStack;
mStack.AppendElement(PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode)));
while (true) {
if (mStack.LastElement().IsFinished()) {
// If we've finished processing all the entries in the current set, then pop
// it off the processing stack and process it.
PredecessorStackItem item = mStack.PopLastElement();
AutoTArray<MergedListIndex,2> result =
ResolveNodeIndexesOldToMerged(item.mDirectPredecessors);
if (mStack.IsEmpty()) {
return result;
} else {
ProcessOldNode(item.mNode, Move(result));
}
} else {
// Grab the current predecessor, push predecessors of that onto the processing
// stack (if it hasn't already been processed), and then advance to the next entry.
OldListIndex currentIndex = mStack.LastElement().GetAndIncrementCurrentPredecessor();
if (!mOldItems[currentIndex.val].IsUsed()) {
mStack.AppendElement(
PredecessorStackItem(currentIndex, mOldDAG.GetDirectPredecessors(currentIndex)));
}
}
}
}
AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged(Span<OldListIndex> aDirectPredecessors) {
AutoTArray<MergedListIndex, 2> result;
result.SetCapacity(aDirectPredecessors.Length());
for (OldListIndex index : aDirectPredecessors) {
OldItemInfo& oldItem = mOldItems[index.val];
if (oldItem.IsDiscarded()) {
for (MergedListIndex inner : oldItem.mDirectPredecessors) {
if (!result.Contains(inner)) {
result.AppendElement(inner);
}
}
} else {
result.AppendElement(oldItem.mIndex);
}
}
return result;
}
RetainedDisplayListBuilder* mBuilder;
Maybe<const ActiveScrolledRoot*> mContainerASR;
nsTArray<OldItemInfo> mOldItems;
DirectedAcyclicGraph<OldListUnits> mOldDAG;
// Unfortunately we can't use strong typing for the hashtables
// since they internally encode the type with the mOps pointer,
// and assert when we try swap the contents
nsDataHashtable<DisplayItemHashEntry, size_t> mOldKeyLookup;
nsDisplayList mMergedItems;
DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
nsDataHashtable<DisplayItemHashEntry, size_t> mMergedKeyLookup;
bool mResultIsModified;
};
void RetainedDisplayList::ClearDAG()
{
mDAG.Clear();
mKeyLookup.Clear();
}
/**
* Takes two display lists and merges them into an output list.
*
* Display lists wthout an explicit DAG are interpreted as linear DAGs (with a maximum
* of one direct predecessor and one direct successor per node). We add the two DAGs
* together, and then output the topological sorted ordering as the final display list.
*
* Once we've merged a list, we then retain the DAG (as part of the RetainedDisplayList
* object) to use for future merges.
*/
bool
RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList,
RetainedDisplayList* aOldList,
RetainedDisplayList* aOutList,
mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR)
{
MergeState merge(this, Move(*aOldList));
Maybe<MergedListIndex> previousItemIndex;
while (nsDisplayItem* item = aNewList->RemoveBottom()) {
previousItemIndex = Some(merge.ProcessItemFromNewList(item, previousItemIndex));
}
*aOutList = Move(merge.Finalize());
aOutContainerASR = merge.mContainerASR;
return merge.mResultIsModified;
}
static void
TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
nsTArray<nsIFrame*>* aModifiedFrames,
nsTArray<nsIFrame*>* aFramesWithProps,
nsIFrame* aRootFrame)
{
MOZ_ASSERT(aRootFrame);
nsTArray<nsIFrame*>* frames =
aRootFrame->GetProperty(nsIFrame::ModifiedFrameList());
if (frames) {
for (nsIFrame* f : *frames) {
if (f) {
aModifiedFrames->AppendElement(f);
}
}
frames->Clear();
}
frames =
aRootFrame->GetProperty(nsIFrame::OverriddenDirtyRectFrameList());
if (frames) {
for (nsIFrame* f : *frames) {
if (f) {
aFramesWithProps->AppendElement(f);
}
}
frames->Clear();
}
}
struct CbData {
nsDisplayListBuilder* builder;
nsTArray<nsIFrame*>* modifiedFrames;
nsTArray<nsIFrame*>* framesWithProps;
};
static nsIFrame*
GetRootFrameForPainting(nsDisplayListBuilder* aBuilder, nsIDocument* aDocument)
{
// Although this is the actual subdocument, it might not be
// what painting uses. Walk up to the nsSubDocumentFrame owning
// us, and then ask that which subdoc it's going to paint.
nsIPresShell* presShell = aDocument->GetShell();
if (!presShell) {
return nullptr;
}
nsView* rootView = presShell->GetViewManager()->GetRootView();
if (!rootView) {
return nullptr;
}
// There should be an anonymous inner view between the root view
// of the subdoc, and the view for the nsSubDocumentFrame.
nsView* innerView = rootView->GetParent();
if (!innerView) {
return nullptr;
}
nsView* subDocView = innerView->GetParent();
if (!subDocView) {
return nullptr;
}
nsIFrame* subDocFrame = subDocView->GetFrame();
if (!subDocFrame) {
return nullptr;
}
nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame);
MOZ_ASSERT(subdocumentFrame);
presShell = subdocumentFrame->GetSubdocumentPresShellForPainting(
aBuilder->IsIgnoringPaintSuppression() ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION : 0);
return presShell ? presShell->GetRootFrame() : nullptr;
}
static bool
SubDocEnumCb(nsIDocument* aDocument, void* aData)
{
MOZ_ASSERT(aDocument);
MOZ_ASSERT(aData);
CbData* data = static_cast<CbData*>(aData);
nsIFrame* rootFrame = GetRootFrameForPainting(data->builder, aDocument);
if (rootFrame) {
TakeAndAddModifiedAndFramesWithPropsFromRootFrame(data->modifiedFrames,
data->framesWithProps,
rootFrame);
nsIDocument* innerDoc = rootFrame->PresShell()->GetDocument();
if (innerDoc) {
innerDoc->EnumerateSubDocuments(SubDocEnumCb, aData);
}
}
return true;
}
static void
GetModifiedAndFramesWithProps(nsDisplayListBuilder* aBuilder,
nsTArray<nsIFrame*>* aOutModifiedFrames,
nsTArray<nsIFrame*>* aOutFramesWithProps)
{
MOZ_ASSERT(aBuilder->RootReferenceFrame());
TakeAndAddModifiedAndFramesWithPropsFromRootFrame(aOutModifiedFrames,
aOutFramesWithProps,
aBuilder->RootReferenceFrame());
nsIDocument* rootdoc = aBuilder->RootReferenceFrame()->PresContext()->Document();
if (rootdoc) {
CbData data = {
aBuilder,
aOutModifiedFrames,
aOutFramesWithProps
};
rootdoc->EnumerateSubDocuments(SubDocEnumCb, &data);
}
}
// ComputeRebuildRegion debugging
// #define CRR_DEBUG 1
#if CRR_DEBUG
# define CRR_LOG(...) printf_stderr(__VA_ARGS__)
#else
# define CRR_LOG(...)
#endif
static nsDisplayItem*
GetFirstDisplayItemWithChildren(nsIFrame* aFrame)
{
nsIFrame::DisplayItemArray* items = aFrame->GetProperty(nsIFrame::DisplayItems());
if (!items) {
return nullptr;
}
for (nsDisplayItem* i : *items) {
if (i->GetChildren()) {
return i;
}
}
return nullptr;
}
static nsIFrame*
HandlePreserve3D(nsIFrame* aFrame, nsRect& aOverflow)
{
// Preserve-3d frames don't have valid overflow areas, and they might
// have singular transforms (despite still being visible when combined
// with their ancestors). If we're at one, jump up to the root of the
// preserve-3d context and use the whole overflow area.
nsIFrame* last = aFrame;
while (aFrame->Extend3DContext() ||
aFrame->Combines3DTransformWithAncestors()) {
last = aFrame;
aFrame = aFrame->GetParent();
}
if (last != aFrame) {
aOverflow = last->GetVisualOverflowRectRelativeToParent();
CRR_LOG("HandlePreserve3D() Updated overflow rect to: %d %d %d %d\n",
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
}
return aFrame;
}
static void
ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder& aBuilder,
AnimatedGeometryRoot** aAGR, nsRect& aOverflow,
nsIFrame* aStopAtFrame, nsTArray<nsIFrame*>& aOutFramesWithProps,
const bool aStopAtStackingContext)
{
nsIFrame* currentFrame = aFrame;
aBuilder.MarkFrameForDisplayIfVisible(aFrame, aBuilder.RootReferenceFrame());
while (currentFrame != aStopAtFrame) {
CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
currentFrame, !aStopAtStackingContext,
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
currentFrame = HandlePreserve3D(currentFrame, aOverflow);
// If the current frame is an OOF frame, DisplayListBuildingData needs to be
// set on all the ancestor stacking contexts of the placeholder frame, up
// to the containing block of the OOF frame. This is done to ensure that the
// content that might be behind the OOF frame is built for merging.
nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
? currentFrame->GetPlaceholderFrame()
: nullptr;
if (placeholder) {
// The rect aOverflow is in the coordinate space of the containing block.
// Convert it to a coordinate space of the placeholder frame.
nsRect placeholderOverflow =
aOverflow + currentFrame->GetOffsetTo(placeholder);
CRR_LOG("Processing placeholder %p for OOF frame %p\n",
placeholder, currentFrame);
CRR_LOG("OOF frame draw area: %d %d %d %d\n",
placeholderOverflow.x, placeholderOverflow.y,
placeholderOverflow.width, placeholderOverflow.height);
// Tracking AGRs for the placeholder processing is not necessary, as the
// goal is to only modify the DisplayListBuildingData rect.
AnimatedGeometryRoot* dummyAGR = nullptr;
// Find a common ancestor frame to handle frame continuations.
// TODO: It might be possible to write a more specific and efficient
// function for this.
nsIFrame* ancestor =
nsLayoutUtils::FindNearestCommonAncestorFrame(currentFrame->GetParent(),
placeholder->GetParent());
ProcessFrame(placeholder, aBuilder, &dummyAGR, placeholderOverflow,
ancestor, aOutFramesWithProps, false);
}
// Convert 'aOverflow' into the coordinate space of the nearest stacking context
// or display port ancestor and update 'currentFrame' to point to that frame.
aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame,
nullptr, nullptr,
/* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
&currentFrame);
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 = aOverflow - 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);
aOutFramesWithProps.AppendElement(currentFrame);
}
rect->UnionRect(*rect, r);
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?
aOverflow = sf->GetScrollPortRect();
} else {
// Don't contribute to the root dirty area at all.
aOverflow.SetEmpty();
}
} else {
aOverflow.IntersectRect(aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf());
}
if (aOverflow.IsEmpty()) {
break;
}
if (currentFrame != aBuilder.RootReferenceFrame() &&
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.
nsDisplayItem* wrapperItem = GetFirstDisplayItemWithChildren(currentFrame);
if (!wrapperItem) {
continue;
}
// 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);
}
CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
if (!aStopAtStackingContext) {
// Continue ascending the frame tree until we reach aStopAtFrame.
continue;
}
// Grab the visible (display list building) rect for children of this wrapper
// item and convert into into coordinate relative to the current frame.
nsRect previousVisible = wrapperItem->GetVisibleRectForChildren();
if (wrapperItem->ReferenceFrameForChildren() == wrapperItem->ReferenceFrame()) {
previousVisible -= wrapperItem->ToReferenceFrame();
} else {
MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() == wrapperItem->Frame());
}
if (!previousVisible.Contains(aOverflow)) {
// If the overflow area of the changed frame isn't contained within the old
// item, then we might change the size of the item and need to update its
// sorting accordingly. Keep propagating the overflow area up so that we
// build intersecting items for sorting.
continue;
}
if (!data->mModifiedAGR) {
data->mModifiedAGR = *aAGR;
} else if (data->mModifiedAGR != *aAGR) {
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.
aOverflow.SetEmpty();
*aAGR = nullptr;
break;
}
}
}
/**
* 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(nsTArray<nsIFrame*>& aModifiedFrames,
nsRect* aOutDirty,
AnimatedGeometryRoot** aOutModifiedAGR,
nsTArray<nsIFrame*>& aOutFramesWithProps)
{
CRR_LOG("Computing rebuild regions for %zu frames:\n", aModifiedFrames.Length());
for (nsIFrame* f : aModifiedFrames) {
MOZ_ASSERT(f);
if (f->HasOverrideDirtyRegion()) {
aOutFramesWithProps.AppendElement(f);
}
if (f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
continue;
}
// 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();
// If the modified frame is also a caret frame, include the caret area.
// This is needed because some frames (for example text frames without text)
// might have an empty overflow rect.
if (f == mBuilder.GetCaretFrame()) {
overflow.UnionRect(overflow, mBuilder.GetCaretRect());
}
ProcessFrame(f, mBuilder, &agr, overflow, mBuilder.RootReferenceFrame(),
aOutFramesWithProps, true);
if (!overflow.IsEmpty()) {
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) {
CRR_LOG("Setting %p as root stacking context AGR\n", agr);
*aOutModifiedAGR = agr;
} else if (agr && *aOutModifiedAGR != agr) {
CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
return false;
}
}
}
return true;
}
/*
* A simple early exit heuristic to avoid slow partial display list rebuilds.
*/
static bool
ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames)
{
if (aModifiedFrames.Length() > gfxPrefs::LayoutRebuildFrameLimit()) {
return false;
}
for (nsIFrame* f : aModifiedFrames) {
MOZ_ASSERT(f);
const LayoutFrameType type = f->Type();
// If we have any modified frames of the following types, it is likely that
// doing a partial rebuild of the display list will be slower than doing a
// full rebuild.
// This is because these frames either intersect or may intersect with most
// of the page content. This is either due to display port size or different
// async AGR.
if (type == LayoutFrameType::Viewport ||
type == LayoutFrameType::PageContent ||
type == LayoutFrameType::Canvas ||
type == LayoutFrameType::Scrollbar) {
return false;
}
}
return true;
}
static void
ClearFrameProps(nsTArray<nsIFrame*>& aFrames)
{
for (nsIFrame* f : aFrames) {
if (f->HasOverrideDirtyRegion()) {
f->SetHasOverrideDirtyRegion(false);
f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect());
f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
}
f->SetFrameIsModified(false);
}
}
class AutoClearFramePropsArray
{
public:
AutoClearFramePropsArray() = default;
~AutoClearFramePropsArray()
{
ClearFrameProps(mFrames);
}
nsTArray<nsIFrame*>& Frames() { return mFrames; }
bool IsEmpty() const { return mFrames.IsEmpty(); }
private:
nsTArray<nsIFrame*> mFrames;
};
void
RetainedDisplayListBuilder::ClearFramesWithProps()
{
AutoClearFramePropsArray modifiedFrames;
AutoClearFramePropsArray framesWithProps;
GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
}
auto
RetainedDisplayListBuilder::AttemptPartialUpdate(
nscolor aBackstop,
mozilla::DisplayListChecker* aChecker) -> PartialUpdateResult
{
mBuilder.RemoveModifiedWindowRegions();
mBuilder.ClearWindowOpaqueRegion();
if (mBuilder.ShouldSyncDecodeImages()) {
MarkFramesWithItemsAndImagesModified(List());
}
mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
// We set the override dirty regions during ComputeRebuildRegion or in
// nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also
// marks the frame modified, so those regions are cleared here as well.
AutoClearFramePropsArray modifiedFrames;
AutoClearFramePropsArray framesWithProps;
GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
// Do not allow partial builds if the retained display list is empty, or if
// ShouldBuildPartial heuristic fails.
const bool shouldBuildPartial = !List()->IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames());
if (mPreviousCaret != mBuilder.GetCaretFrame()) {
if (mPreviousCaret) {
if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) {
modifiedFrames.Frames().AppendElement(mPreviousCaret);
}
}
if (mBuilder.GetCaretFrame()) {
if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) {
modifiedFrames.Frames().AppendElement(mBuilder.GetCaretFrame());
}
}
mPreviousCaret = mBuilder.GetCaretFrame();
}
nsRect modifiedDirty;
AnimatedGeometryRoot* modifiedAGR = nullptr;
if (!shouldBuildPartial ||
!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
&modifiedAGR, framesWithProps.Frames()) ||
!PreProcessDisplayList(&mList, modifiedAGR)) {
mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
mList.ClearDAG();
return PartialUpdateResult::Failed;
}
modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
PartialUpdateResult result = PartialUpdateResult::NoChange;
if (!modifiedDirty.IsEmpty() ||
!framesWithProps.IsEmpty()) {
result = PartialUpdateResult::Updated;
}
mBuilder.SetDirtyRect(modifiedDirty);
mBuilder.SetPartialUpdate(true);
nsDisplayList modifiedDL;
mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
if (!modifiedDL.IsEmpty()) {
nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
aBackstop);
}
mBuilder.SetPartialUpdate(false);
if (aChecker) {
aChecker->Set(&modifiedDL, "TM");
}
//printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
// modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
//nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
// |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.
// TODO: Ideally we could skip this if result is NoChange, but currently when
// we call RestoreState on nsDisplayWrapList it resets the clip to the base
// clip, and we need the UpdateBounds call (within MergeDisplayLists) to
// move it to the correct inner clip.
Maybe<const ActiveScrolledRoot*> dummy;
if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
result = PartialUpdateResult::Updated;
}
//printf_stderr("Painting --- Merged list:\n");
//nsFrame::PrintDisplayList(&mBuilder, mList);
mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
return result;
}