/* -*- 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 "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 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->GetDependentFrame()) { i->GetDependentFrame()->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; 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, 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. 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 void SwapAndRemove(nsTArray& 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& 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(aOldItem); nsDisplayLayerEventRegions* newItem = static_cast(aNewItem); nsTArray 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(aItem)->SubDocumentFrame(); MOZ_ASSERT(subDocFrame); nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); MOZ_ASSERT(presShell); mBuilder.IncrementPresShellPaintCount(presShell); } void UpdateASR(nsDisplayItem* aItem, Maybe& aContainerASR) { if (!aContainerASR) { return; } nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList(); if (!wrapList) { aItem->SetActiveScrolledRoot(aContainerASR.value()); return; } wrapList->SetActiveScrolledRoot( ActiveScrolledRoot::PickAncestor(wrapList->GetFrameActiveScrolledRoot(), aContainerASR.value())); } /** * 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, but don't copy A since it's also present in the new list * and then add the new B into the merged list. We then add A, and we're done. * * Merged List: B, A */ void RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList, nsDisplayList* aOldList, nsDisplayList* aOutList, Maybe& aOutContainerASR) { nsDisplayList merged; const auto UseItem = [&](nsDisplayItem* aItem) { const ActiveScrolledRoot* itemClipASR = aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr; const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant( itemClipASR, aItem->GetActiveScrolledRoot()); if (!aOutContainerASR) { aOutContainerASR = Some(finiteBoundsASR); } else { aOutContainerASR = Some(ActiveScrolledRoot::PickAncestor(aOutContainerASR.value(), finiteBoundsASR)); } merged.AppendToTop(aItem); }; const auto ReuseItem = [&](nsDisplayItem* aItem) { UseItem(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 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); } } nsDataHashtable newListLookup(aNewList->Count()); for (nsDisplayItem* i = aNewList->GetBottom(); i != nullptr; i = i->GetAbove()) { #ifdef DEBUG 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(i->GetType()), i->GetPerFrameKey()); } #endif newListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i); } while (nsDisplayItem* newItem = aNewList->RemoveBottom()) { if (nsDisplayItem* oldItem = oldListLookup.Get({ newItem->Frame(), newItem->GetPerFrameKey() })) { // The new item has a matching counterpart in the old list that we haven't yet reached, // so copy all valid items from the old list into the merged list until we get to the // matched item. if (!oldItem->IsReused()) { nsDisplayItem* old = nullptr; while ((old = aOldList->RemoveBottom()) && !IsSameItem(newItem, old)) { if (IsAnyAncestorModified(old->FrameForInvalidation())) { // The old item is invalid, discard it. oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() }); old->Destroy(&mBuilder); } else if (newListLookup.Get({ old->Frame(), old->GetPerFrameKey() })) { // The old item is also in the new list, but we haven't got to it yet. // Mark that we've found it, and we'll deal with it when we get to the new // entry. old->SetReused(true); } else { // Recurse into the child list (without a matching new list) to // ensure that we find and remove any invalidated items. if (old->GetChildren()) { nsDisplayList empty; Maybe containerASRForChildren; MergeDisplayLists(&empty, old->GetChildren(), old->GetChildren(), containerASRForChildren); UpdateASR(old, containerASRForChildren); old->UpdateBounds(&mBuilder); } ReuseItem(old); } } 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 (oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS && !IsAnyAncestorModified(oldItem->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(oldItem, newItem); ReuseItem(oldItem); newItem->Destroy(&mBuilder); } else { if (!IsAnyAncestorModified(oldItem->FrameForInvalidation()) && oldItem->GetChildren()) { MOZ_ASSERT(newItem->GetChildren()); Maybe containerASRForChildren; MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), newItem->GetChildren(), containerASRForChildren); UpdateASR(newItem, containerASRForChildren); newItem->UpdateBounds(&mBuilder); } oldItem->Destroy(&mBuilder); UseItem(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. UseItem(newItem); } } // Reuse the remaining valid items from the old display list. while (nsDisplayItem* old = aOldList->RemoveBottom()) { if (!IsAnyAncestorModified(old->FrameForInvalidation())) { 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; Maybe containerASRForChildren; MergeDisplayLists(&empty, old->GetChildren(), old->GetChildren(), containerASRForChildren); UpdateASR(old, containerASRForChildren); old->UpdateBounds(&mBuilder); } if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { MergeLayerEventRegions(old, nullptr); } ReuseItem(old); } else { old->Destroy(&mBuilder); } } aOutList->AppendToTop(&merged); } static void TakeAndAddModifiedFramesFromRootFrame(nsTArray& aFrames, nsIFrame* aRootFrame) { MOZ_ASSERT(aRootFrame); nsTArray* frames = aRootFrame->GetProperty(nsIFrame::ModifiedFrameList()); if (!frames) { return; } for (nsIFrame* f : *frames) { if (f) { aFrames.AppendElement(f); } } frames->Clear(); } struct CbData { nsDisplayListBuilder* builder; nsTArray modifiedFrames; }; static bool SubDocEnumCb(nsIDocument* aDocument, void* aData) { MOZ_ASSERT(aDocument); MOZ_ASSERT(aData); CbData* data = static_cast(aData); // 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) { nsView* rootView = presShell->GetViewManager()->GetRootView(); MOZ_ASSERT(rootView); // There should be an anonymous inner view between the root view // of the subdoc, and the view for the nsSubDocumentFrame. nsView* innerView = rootView->GetParent(); MOZ_ASSERT(innerView); nsView* subDocView = innerView->GetParent(); MOZ_ASSERT(subDocView); nsIFrame* subDocFrame = subDocView->GetFrame(); if (subDocFrame) { nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame); MOZ_ASSERT(subdocumentFrame); presShell = subdocumentFrame->GetSubdocumentPresShellForPainting( data->builder->IsIgnoringPaintSuppression() ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION : 0); nsIFrame* rootFrame = presShell ? presShell->GetRootFrame() : nullptr; if (rootFrame) { TakeAndAddModifiedFramesFromRootFrame(data->modifiedFrames, rootFrame); } } } aDocument->EnumerateSubDocuments(SubDocEnumCb, aData); return true; } static nsTArray GetModifiedFrames(nsDisplayListBuilder* aBuilder) { MOZ_ASSERT(aBuilder->RootReferenceFrame()); CbData data; data.builder = aBuilder; TakeAndAddModifiedFramesFromRootFrame(data.modifiedFrames, aBuilder->RootReferenceFrame()); nsIDocument* rootdoc = aBuilder->RootReferenceFrame()->PresContext()->Document(); if (rootdoc) { rootdoc->EnumerateSubDocuments(SubDocEnumCb, &data); } return Move(data.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(nsTArray& aModifiedFrames, nsRect* aOutDirty, AnimatedGeometryRoot** aOutModifiedAGR, nsTArray* aOutFramesWithProps) { CRR_LOG("Computing rebuild regions for %d frames:\n", aModifiedFrames.size()); 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(); nsIFrame* currentFrame = f; while (currentFrame != mBuilder.RootReferenceFrame()) { // 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 = currentFrame; while (currentFrame->Extend3DContext() || currentFrame->Combines3DTransformWithAncestors()) { last = currentFrame; currentFrame = currentFrame->GetParent(); } if (last != currentFrame) { overflow = last->GetVisualOverflowRectRelativeToParent(); } // 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 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, mBuilder.RootReferenceFrame()); // 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; } /* * A simple early exit heuristic to avoid slow partial display list rebuilds. */ static bool ShouldBuildPartial(nsTArray& 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& aFrames) { for (nsIFrame* f : aFrames) { if (f->HasOverrideDirtyRegion()) { f->SetHasOverrideDirtyRegion(false); f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect()); f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); } f->SetFrameIsModified(false); } } void RetainedDisplayListBuilder::ClearModifiedFrameProps() { nsTArray modifiedFrames = GetModifiedFrames(&mBuilder); ClearFrameProps(modifiedFrames); } bool RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop) { mBuilder.RemoveModifiedWindowDraggingRegion(); if (mBuilder.ShouldSyncDecodeImages()) { MarkFramesWithItemsAndImagesModified(&mList); } mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); nsTArray modifiedFrames = GetModifiedFrames(&mBuilder); // Do not allow partial builds if the retained display list is empty, or if // ShouldBuildPartial heuristic fails. const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames); if (mPreviousCaret != mBuilder.GetCaretFrame()) { if (mPreviousCaret) { if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) { modifiedFrames.AppendElement(mPreviousCaret); } } if (mBuilder.GetCaretFrame()) { if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) { modifiedFrames.AppendElement(mBuilder.GetCaretFrame()); } } mPreviousCaret = mBuilder.GetCaretFrame(); } nsRect modifiedDirty; AnimatedGeometryRoot* modifiedAGR = nullptr; nsTArray framesWithProps; bool merged = false; if (shouldBuildPartial && ComputeRebuildRegion(modifiedFrames, &modifiedDirty, &modifiedAGR, &framesWithProps)) { modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); PreProcessDisplayList(&mList, modifiedAGR); nsDisplayList modifiedDL; 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(&mBuilder, 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. Maybe dummy; MergeDisplayLists(&modifiedDL, &mList, &mList, dummy); //printf_stderr("Painting --- Merged list:\n"); //nsFrame::PrintDisplayList(&mBuilder, mList); merged = true; } mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); // 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. ClearFrameProps(modifiedFrames); ClearFrameProps(framesWithProps); return merged; }