SSL RetainedDisplayListBuilder.cpp
Interaktion und PortierbarkeitC
/* -*- 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/.
*/
/** * 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. * * Frames that are a stacking context, containing blocks for position:fixed * descendants, and don't have any continuations (see * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with * separate retaining decisions made. * * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest * ancestor frame which can be moved asynchronously on the compositor thread. * These are currently nsDisplayItems which return true from CanMoveAsync * (animated nsDisplayTransform and nsDisplayStickyPosition) and * ActiveScrolledRoots. * * For each context that we run the retaining algorithm, there can only be * mutations to one AnimatedGeometryRoot. This is because we are unable to * reason about intersections of items that might then move relative to each * other without RDL running again. If there are mutations to multiple * AnimatedGeometryRoots, then we bail out and rebuild all the items in the * context. * * Otherwise, when mutations are restricted to a single AGR, we pre-process the * old display list and mark the frames for all existing (unmodified!) items * that belong to a different AGR and ensure that we rebuild those items for * correct sorting with the modified ones.
*/
staticvoid MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { for (nsDisplayItem* i : *aList) { 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. // XXX: handle webrender case by looking up retained data for the item // and checking InvalidateForSyncDecodeImages bool invalidate = false; if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
invalidate = true;
}
if (invalidate) {
DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name());
i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); if (i->GetDependentFrame()) {
i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
}
}
} if (i->GetChildren()) {
MarkFramesWithItemsAndImagesModified(i->GetChildren());
}
}
}
bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
nsIFrame* f = aFrame; while (f) { if (f->IsFrameModified()) { returntrue;
}
if (aStopAtFrame && f == aStopAtFrame) { break;
}
f = nsLayoutUtils::GetDisplayListParent(f);
}
returnfalse;
}
// 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, nsIFrame* aAGR, PartialUpdateResult& aUpdated,
nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR,
nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth, bool aKeepLinked) { // 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. staticconst uint32_t kMaxEdgeRatio = 5; constbool initializeDAG = !aList->mDAG.Length(); if (!aKeepLinked && !initializeDAG &&
aList->mDAG.mDirectPredecessorList.Length() >
(aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { returnfalse;
}
// If we had aKeepLinked=true for this list on the previous paint, then // mOldItems will already be initialized as it won't have been consumed during // a merge. constbool initializeOldItems = aList->mOldItems.IsEmpty(); if (initializeOldItems) {
aList->mOldItems.SetCapacity(aList->Length());
} else {
MOZ_RELEASE_ASSERT(!initializeDAG);
}
size_t i = 0; while (nsDisplayItem* item = aList->RemoveBottom()) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
item->SetMergedPreProcessed(false, true); #endif
// If we have a previously initialized old items list, then it can differ // from the current list due to items removed for having a deleted frame. // We can't easily remove these, since the DAG has entries for those indices // and it's hard to rewrite in-place. // Skip over entries with no current item to keep the iterations in sync. if (!initializeOldItems) { while (!aList->mOldItems[i].mItem) {
i++;
}
}
if (initializeDAG) { if (i == 0) {
aList->mDAG.AddNode(Span<const MergedListIndex>());
} else {
MergedListIndex previous(i - 1);
aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
}
}
if (initializeOldItems) {
aList->mOldItems.AppendElement(OldItemInfo(item));
}
// If we're not going to keep the list linked, then this old item entry // is the only pointer to the item. Let it know that it now strongly // owns the item, so it can destroy it if it goes away.
aList->mOldItems[i].mOwnsItem = !aKeepLinked;
if (item->GetChildren()) { // If children inside this list were invalid, then we'd have walked the // ancestors and set ForceDescendIntoVisible on the current frame. If an // ancestor is modified, then we'll throw this away entirely. Either way, // we won't need to run merging on this sublist, and we can keep the items // linked into their display list. // The caret can move without invalidating, but we always set the force // descend into frame state bit on that frame, so check for that too. // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make // us think future siblings need to be merged, even though we don't really // need to. bool keepLinked = aKeepLinked;
nsIFrame* invalid = item->FrameForInvalidation(); if (!invalid->ForceDescendIntoIfVisible() &&
!invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
keepLinked = true;
}
// If this item's frame is an AGR (can be moved asynchronously by the // compositor), then use that frame for descendants. Also pass the ASR // for that item, so that descendants can compare to see if any new // ASRs have been pushed since.
nsIFrame* asyncAncestor = aAsyncAncestor; const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR; if (item->CanMoveAsync()) {
asyncAncestor = item->Frame();
asyncAncestorASR = item->GetActiveScrolledRoot();
}
if (!PreProcessDisplayList(
item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated,
asyncAncestor, asyncAncestorASR, item->Frame(),
item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) {
MOZ_RELEASE_ASSERT(
!aKeepLinked, "Can't early return since we need to move the out list back"); returnfalse;
}
}
// 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. // TODO: We only really need to build the ancestor container item that is a // sibling of the changed thing to get correct ordering. The changed content // is a frame though, and it's hard to map that to container items in this // list. // If an ancestor display item is an AGR, and our ASR matches the ASR // of that item, then there can't have been any new ASRs pushed since that // item, so that item is our AGR. Otherwise, our AGR is our ASR. // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot // should be the same or a descendant and also non-null. Unfortunately an // RDL bug means this can be wrong for sticky items after a partial update, // so we have to work around it. Bug 1730749 and bug 1730826 should resolve // this.
nsIFrame* agrFrame = nullptr; if (aAsyncAncestorASR == item->GetActiveScrolledRoot() ||
!item->GetActiveScrolledRoot()) {
agrFrame = aAsyncAncestor;
} else {
agrFrame = item->GetActiveScrolledRoot()
->mScrollContainerFrame->GetScrolledFrame();
}
if (aAGR && agrFrame != aAGR) {
mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame());
}
// If we're going to keep this linked list and not merge it, then mark the // item as used and put it back into the list. if (aKeepLinked) {
item->SetReused(true); if (item->GetChildren()) {
item->UpdateBounds(Builder());
} if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
IncrementSubDocPresShellPaintCount(item);
}
out.AppendToTop(item);
}
i++;
}
OldItemInfo::OldItemInfo(nsDisplayItem* aItem)
: mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) { if (mItem) { // Clear cached modified frame state when adding an item to the old list.
mItem->SetModifiedFrame(false);
}
}
/** * A C++ implementation of Markus Stange's merge-dags algorithm. * 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, nsDisplayItem* aOuterItem)
: mBuilder(aBuilder),
mOldList(&aOldList),
mOldItems(std::move(aOldList.mOldItems)),
mOldDAG(
std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(
&aOldList.mDAG))),
mMergedItems(aBuilder->Builder()),
mOuterItem(aOuterItem),
mResultIsModified(false) {
mMergedDAG.EnsureCapacityFor(mOldDAG);
MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length());
}
Maybe<MergedListIndex> ProcessItemFromNewList(
nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) {
OldListIndex oldIndex;
MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() ==
HasModifiedFrame(aNewItem)); if (!aNewItem->HasModifiedFrame() &&
HasMatchingItemInOldList(aNewItem, &oldIndex)) {
mBuilder->Metrics()->mRebuiltItems++;
nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() ==
aNewItem->GetPerFrameKey() &&
oldItem->Frame() == aNewItem->Frame()); if (!mOldItems[oldIndex.val].IsChanged()) {
MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
nsDisplayItem* destItem; if (ShouldUseNewItem(aNewItem)) {
destItem = aNewItem;
} else {
destItem = oldItem; // The building rect can depend on the overflow rect (when the parent // frame is position:fixed), which can change without invalidating // the frame/items. If we're using the old item, copy the building // rect across from the new item.
oldItem->SetBuildingRect(aNewItem->GetBuildingRect());
}
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
nsDisplayList empty(mBuilder->Builder()); constbool modified = mBuilder->MergeDisplayLists(
aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(),
aOutItem->GetChildren(), containerASRForChildren, aOutItem); if (modified) {
aOutItem->InvalidateCachedChildInfo(mBuilder->Builder());
UpdateASR(aOutItem, containerASRForChildren);
mResultIsModified = true;
} elseif (aOutItem == aNewItem) { // If nothing changed, but we copied the contents across to // the new item, then also copy the ASR data.
CopyASR(aOldItem, aNewItem);
} // Ideally we'd only UpdateBounds if something changed, but // nsDisplayWrapList also uses this to update the clip chain for the // current ASR, which gets reset during RestoreState(), so we always need // to run it again.
aOutItem->UpdateBounds(mBuilder->Builder());
bool ShouldUseNewItem(nsDisplayItem* aNewItem) { // Generally we want to use the old item when the frame isn't marked as // modified so that any cached information on the item (or referencing the // item) gets retained. Quite a few FrameLayerBuilder performance // improvements benefit by this. Sometimes, however, we can end up where the // new item paints something different from the old item, even though we // haven't modified the frame, and it's hard to fix. In these cases we just // always use the new item to be safe.
DisplayItemType type = aNewItem->GetType(); if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR ||
type == DisplayItemType::TYPE_SOLID_COLOR) { // The canvas background color item can paint the color from another // frame, and even though we schedule a paint, we don't mark the canvas // frame as invalid. returntrue;
}
if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { // We intentionally don't mark the root table frame as modified when a // subframe changes, even though the border collapse item for the root // frame is what paints the changed border. Marking the root frame as // modified would rebuild display items for the whole table area, and we // don't want that. returntrue;
}
if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { // Text overflow marker items are created with the wrapping block as their // frame, and have an index value to note which line they are created for. // Their rendering can change if the items on that line change, which may // not mark the block as modified. We rebuild them if we build any item on // the line, so we should always get new items if they might have changed // rendering, and it's easier to just use the new items rather than // computing if we actually need them. returntrue;
}
if (type == DisplayItemType::TYPE_SUBDOCUMENT ||
type == DisplayItemType::TYPE_STICKY_POSITION) { // nsDisplaySubDocument::mShouldFlatten can change without an invalidation // (and is the reason we unconditionally build the subdocument item), so // always use the new one to make sure we get the right value. // Same for |nsDisplayStickyPosition::mShouldFlatten|. returntrue;
}
if (type == DisplayItemType::TYPE_CARET) { // The caret can change position while still being owned by the same frame // and we don't invalidate in that case. Use the new version since the // changed bounds are needed for DLBI. returntrue;
}
if (type == DisplayItemType::TYPE_MASK ||
type == DisplayItemType::TYPE_FILTER ||
type == DisplayItemType::TYPE_SVG_WRAPPER) { // SVG items have some invalidation issues, see bugs 1494110 and 1494663. returntrue;
}
if (type == DisplayItemType::TYPE_TRANSFORM) { // Prerendering of transforms can change without frame invalidation. returntrue;
}
returnfalse;
}
RetainedDisplayList Finalize() { for (size_t i = 0; i < mOldDAG.Length(); i++) { if (mOldItems[i].IsUsed()) { continue;
}
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;
}
ProcessOldNode(item.mNode, std::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;
RetainedDisplayList* mOldList;
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
nsDisplayList mMergedItems;
DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
nsDisplayItem* mOuterItem; bool mResultIsModified;
};
if (item->GetChildren()) {
VerifyNotModified(item->GetChildren());
}
}
} #endif
/** * 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,
nsDisplayItem* aOuterItem) {
AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging);
if (!aOldList->IsEmpty()) { // If we still have items in the actual list, then it is because // PreProcessDisplayList decided that it was sure it can't be modified. We // can just use it directly, and throw any new items away.
static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) { for (nsDisplayItem* i : aFrame->DisplayItems()) { if (i->HasDeletedFrame() || i->Frame() != aFrame) { // The main frame for the display item has been deleted or the display // item belongs to another frame. continue;
}
if (i->HasChildren()) { returnstatic_cast<nsDisplayItem*>(i);
}
} return nullptr;
}
// Returns true if |aFrame| can store a display list building rect. // These limitations are necessary to guarantee that // 1) Just enough items are rebuilt to properly update display list // 2) Modified frames will be visited during a partial display list build. staticbool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame) { return aFrame != aBuilder->RootReferenceFrame() &&
aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() && // Split frames might have placeholders for modified frames in their // unmodified continuation frame.
!aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation();
}
// 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) {
nsRect placeholderOverflow = aOverflow; auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder,
placeholderOverflow); if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
placeholderOverflow = nsRect();
}
CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder,
currentFrame);
// Tracking AGRs for the placeholder processing is not necessary, as the // goal is to only modify the DisplayListBuildingData rect.
nsIFrame* 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. const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame(
currentFrame->GetParent(), placeholder->GetParent());
// 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,
¤tFrame); if (IsInPreserve3DContext(currentFrame)) { returnfalse;
}
MOZ_ASSERT(currentFrame);
// Check whether the current frame is a scrollable frame with display port.
nsRect displayPort;
ScrollContainerFrame* sf = do_QueryFrame(currentFrame);
nsIContent* content = sf ? currentFrame->GetContent() : nullptr;
if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) {
CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
// Get overflow 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->InkOverflowRectRelativeToSelf());
}
if (aOverflow.IsEmpty()) { break;
}
if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) {
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->GetBuildingRectForChildren(); if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) {
previousVisible -= wrapperItem->ToReferenceFrame();
}
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;
} elseif (data->mModifiedAGR != *aAGR) {
data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf();
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;
if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { returntrue;
}
// TODO: There is almost certainly a faster way of doing this, probably can be // combined with the ancestor walk for TransformFrameRectToAncestor.
nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame);
CRR_LOG("Processing frame %p with agr %p\n", aFrame, 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 = aFrame->InkOverflowRectRelativeToSelf();
// 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 (aFrame == aBuilder->GetCaretFrame()) {
overflow.UnionRect(overflow, aBuilder->GetCaretRect());
}
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", agrFrame);
*aOutModifiedAGR = agrFrame;
} elseif (agrFrame && *aOutModifiedAGR != agrFrame) {
CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); returnfalse;
}
} returntrue;
}
staticvoid AddFramesForContainingBlock(nsIFrame* aBlock, const nsFrameList& aFrames,
nsTArray<nsIFrame*>& aExtraFrames) { for (nsIFrame* f : aFrames) { if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) {
CRR_LOG("Adding invalid OOF %p\n", f);
aExtraFrames.AppendElement(f);
}
}
}
// Placeholder descendants of aFrame don't contribute to aFrame's overflow area. // Find all the containing blocks that might own placeholders under us, walk // their OOF frames list, and manually invalidate any frames that are // descendants of a modified frame (us, or another frame we'll get to soon). // This is combined with the work required for MarkFrameForDisplayIfVisible, // so that we can avoid an extra ancestor walk, and we can reuse the flag // to detect when we've already visited an ancestor (and thus all further // ancestors must also be visited). staticvoid FindContainingBlocks(nsIFrame* aFrame,
nsTArray<nsIFrame*>& aExtraFrames) { for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { if (f->ForceDescendIntoIfVisible()) { return;
}
f->SetForceDescendIntoIfVisible(true);
CRR_LOG("Considering OOFs for %p\n", f);
// This condition must match the condition in // nsLayoutUtils::GetParentOrPlaceholderFor which is used by // nsLayoutUtils::GetDisplayListParent if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
nsIFrame* parent = f->GetParent(); if (parent && !parent->ForceDescendIntoIfVisible()) { // If the GetDisplayListParent call is going to walk to a placeholder, // in rare cases the placeholder might be contained in a different // continuation from the oof. So we have to make sure to mark the oofs // parent. In the common case this doesn't make us do any extra work, // just changes the order in which we visit the frames since walking // through placeholders will walk through the parent, and we stop when // we find a ForceDescendIntoIfVisible bit set.
FindContainingBlocks(parent, aExtraFrames);
}
}
}
}
/** * 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,
nsIFrame** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps) {
CRR_LOG("Computing rebuild regions for %zu frames:\n",
aModifiedFrames.Length());
nsTArray<nsIFrame*> extraFrames; for (nsIFrame* f : aModifiedFrames) {
MOZ_ASSERT(f);
// Since we set modified to true on the extraFrames, add them to // aModifiedFrames so that it will get reverted.
aModifiedFrames.AppendElements(extraFrames);
for (nsIFrame* f : extraFrames) {
f->SetFrameIsModified(true);
bool RetainedDisplayListBuilder::ShouldBuildPartial(
nsTArray<nsIFrame*>& aModifiedFrames) { if (mList.IsEmpty()) { // Partial builds without a previous display list do not make sense.
Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList; returnfalse;
}
if (aModifiedFrames.Length() >
StaticPrefs::layout_display_list_rebuild_frame_limit()) { // Computing a dirty rect with too many modified frames can be slow.
Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit; returnfalse;
}
// We don't support retaining with overlay scrollbars, since they require // us to look at the display list and pick the highest z-index, which // we can't do during partial building. if (mBuilder.DisablePartialUpdates()) {
mBuilder.SetDisablePartialUpdates(false);
Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled; returnfalse;
}
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) {
Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; returnfalse;
}
// Detect root scroll frame and do a full rebuild for them too for the same // reasons as above, but also because top layer items should to be marked // modified if the root scroll frame is modified. Putting this check here // means we don't need to check everytime a frame is marked modified though. if (type == LayoutFrameType::ScrollContainer && f->GetParent() &&
!f->GetParent()->GetParent()) {
Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; returnfalse;
}
}
returntrue;
}
class AutoClearFramePropsArray { public: explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {}
AutoClearFramePropsArray() = default;
~AutoClearFramePropsArray() {
size_t len = mFrames.Length();
nsIFrame** elements = mFrames.Elements(); for (size_t i = 0; i < len; ++i) {
nsIFrame* f = elements[i];
DL_LOGV("RDL - Clearing modified flags for frame %p", f); if (f->HasOverrideDirtyRegion()) {
f->SetHasOverrideDirtyRegion(false);
f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect());
f->RemoveProperty(
nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
}
f->SetFrameIsModified(false);
f->SetHasModifiedDescendants(false);
}
}
/** * Iterates over the modified frames array and updates the frame tree flags * so that container frames know whether they have modified descendant frames. * Frames that were marked modified are added to |aOutFramesWithProps|, so that * the modified status can be cleared after the display list build.
*/ void MarkAllAncestorFrames(const nsTArray<nsIFrame*>& aModifiedFrames,
nsTArray<nsIFrame*>& aOutFramesWithProps) {
nsAutoString frameName;
DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length()); for (nsIFrame* frame : aModifiedFrames) { #ifdef DEBUG
frame->GetFrameName(frameName); #endif
DL_LOGV("RDL - Processing modified frame: %p (%s)", frame,
NS_ConvertUTF16toUTF8(frameName).get());
/** * Marks the given display item |aItem| as reuseable container, and updates the * bounds in case some child items were destroyed.
*/
MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem(
nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
aItem->SetPreProcessed();
if (aItem->HasChildren()) {
aItem->UpdateBounds(aBuilder);
}
bool IsSupportedFrameType(const nsIFrame* aFrame) { // The way table backgrounds are handled makes these frames incompatible with // this retained display list approach. if (aFrame->IsTableColFrame()) { returnfalse;
}
if (aFrame->IsTableColGroupFrame()) { returnfalse;
}
if (aFrame->IsTableRowFrame()) { returnfalse;
}
if (aFrame->IsTableRowGroupFrame()) { returnfalse;
}
if (aFrame->IsTableCellFrame()) { returnfalse;
}
// Everything else should work. returntrue;
}
bool IsReuseableStackingContextItem(nsDisplayItem* aItem) { if (!IsSupportedFrameType(aItem->Frame())) { returnfalse;
}
if (aParentReused) { // Keep the contents of the current container item linked. #ifdef DEBUG
RDLUtils::AssertDisplayItemUnmodified(item); #endif
aList->AppendToTop(item);
} elseif (isStackingContextItem) { // |item| is a stacking context item that can be reused.
ReuseStackingContextItem(aBuilder, item);
} else { // |item| is inside a container item that will be destroyed later.
DL_LOGD("%*s Deleted unused item %p", aDepth, "", item);
item->Destroy(aBuilder); continue;
}
if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
IncrementPresShellPaintCount(aBuilder, item);
}
}
}
if (mBuilder.ShouldSyncDecodeImages()) {
DL_LOGI("RDL - Sync decoding images");
MarkFramesWithItemsAndImagesModified(&mList);
}
mBuilder.InvalidateCaretFramesIfNeeded();
// We set the override dirty regions during ComputeRebuildRegion or in // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change // also marks the frame modified, so those regions are cleared here as well.
AutoClearFramePropsArray modifiedFrames(Data()->GetModifiedFrameCount());
AutoClearFramePropsArray framesWithProps(64);
GetModifiedAndFramesWithProps(&modifiedFrames.Frames(),
&framesWithProps.Frames());
if (!ShouldBuildPartial(modifiedFrames.Frames())) { // Do not allow partial builds if the |ShouldBuildPartial()| heuristic // fails.
mBuilder.SetPartialBuildFailed(true); return PartialUpdateResult::Failed;
}
// This is normally handled by EnterPresShell, but we skipped it so that we // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
ScrollContainerFrame* sf =
RootReferenceFrame()->PresShell()->GetRootScrollContainerFrame(); if (sf) {
nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); if (canvasFrame) {
mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame());
}
}
// |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. if (!simpleUpdate) {
Maybe<const ActiveScrolledRoot*> dummy; if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
result = PartialUpdateResult::Updated;
}
} else {
MOZ_ASSERT(mList.IsEmpty());
mList = std::move(modifiedDL);
mBuilder.ClearReuseableDisplayItems();
result = PartialUpdateResult::Updated;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.