/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=2 sw=2 et tw=78: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RetainedDisplayListBuilder.h" #include "nsSubDocumentFrame.h" /** * Code for doing display list building for a modified subset of the window, * and then merging it into the existing display list (for the full window). * * The approach primarily hinges on the observation that the ‘true’ ordering of * display items is represented by a DAG (only items that intersect in 2d space * have a defined ordering). Our display list is just one of a many possible linear * representations of this ordering. * * Each time a frame changes (gets a new style context, or has a size/position * change), we schedule a paint (as we do currently), but also reord the frame that * changed. * * When the next paint occurs we union the overflow areas (in screen space) of the * changed frames, and compute a rect/region that contains all changed items. We * then build a display list just for this subset of the screen and merge it into * the display list from last paint. * * Any items that exist in one list and not the other must not have a defined * ordering in the DAG, since they need to intersect to have an ordering and * we would have built both in the new list if they intersected. Given that, we * can align items that appear in both lists, and any items that appear between * matched items can be inserted into the merged list in any order. */ using namespace mozilla; void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) { if (!i->HasDeletedFrame() && i->CanBeReused() && !i->Frame()->IsFrameModified()) { // If we have existing cached geometry for this item, then check that for // whether we need to invalidate for a sync decode. If we don't, then // use the item's flags. DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i); bool invalidate = false; if (data && data->GetGeometry()) { invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages(); } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { invalidate = true; } if (invalidate) { i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); } } if (i->GetChildren()) { MarkFramesWithItemsAndImagesModified(i->GetChildren()); } } } bool IsAnyAncestorModified(nsIFrame* aFrame) { nsIFrame* f = aFrame; while (f) { if (f->IsFrameModified()) { return true; } f = nsLayoutUtils::GetCrossDocParentFrame(f); } return false; } // Removes any display items that belonged to a frame that was deleted, // and mark frames that belong to a different AGR so that get their // items built again. // TODO: We currently descend into all children even if we don't have an AGR // to mark, as child stacking contexts might. It would be nice if we could // jump into those immediately rather than walking the entire thing. void RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR) { nsDisplayList saved(&mBuilder); while (nsDisplayItem* i = aList->RemoveBottom()) { if (i->HasDeletedFrame() || !i->CanBeReused()) { i->Destroy(&mBuilder); continue; } nsIFrame* f = i->Frame(); if (i->GetChildren()) { AnimatedGeometryRoot *childAGR = aAGR; if (f->IsStackingContext()) { if (f->HasOverrideDirtyRegion()) { nsDisplayListBuilder::DisplayListBuildingData* data = f->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); if (data) { childAGR = data->mModifiedAGR; } } else { childAGR = nullptr; } } PreProcessDisplayList(i->GetChildren(), childAGR); } // TODO: We should be able to check the clipped bounds relative // to the common AGR (of both the existing item and the invalidated // frame) and determine if they can ever intersect. if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { mBuilder.MarkFrameForDisplayIfVisible(f); } // TODO: This is here because we sometimes reuse the previous display list // completely. For optimization, we could only restore the state for reused // display items. i->RestoreState(); saved.AppendToTop(i); } aList->AppendToTop(&saved); aList->RestoreState(); } bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond) { return aFirst->Frame() == aSecond->Frame() && aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey(); } struct DisplayItemKey { bool operator ==(const DisplayItemKey& aOther) const { return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey; } nsIFrame* mFrame; uint32_t mPerFrameKey; }; class DisplayItemHashEntry : public PLDHashEntryHdr { public: typedef DisplayItemKey KeyType; typedef const DisplayItemKey* KeyTypePointer; explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {} explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy)=default; ~DisplayItemHashEntry() = default; KeyType GetKey() const { return mKey; } bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; } static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { if (!aKey) return 0; return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); } enum { ALLOW_MEMMOVE = true }; DisplayItemKey mKey; }; template 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); } /** * Takes two display lists and merges them into an output list. * * The basic algorithm is: * * For-each item in the new list: * If the item has a matching item in the old list: * Remove items from the bottom of the old list until we reach the matching item: * Add valid items to the merged list, destroy invalid items. * Destroy the matching item from the old list. * Add the item from the new list into the merged list. * Add all remaining valid items from the old list into the merged list. * * If any item has a child display list, then we recurse into the merge * algorithm once we match up the new/old versions (if present). * * Example 1: * * Old List: A,B,C,D * New List: A,D * Invalidations: C,D * * We first match the A items, and add the new one to the merged list. * We then match the D items, copy B into the merged list, but not C * (since it's invalid). We then add the new D to the list and we're * finished. * * Merged List: A,B,D * * Example 2: * * Old List: A, B * New List, B, A * Invalidations: - * * This can happen because a prior merge might have changed the ordering * for non-intersecting items. * * We match the B items, and copy A from the old list into the merged list, * and then the new B into the merged list. * We now get to A in the new list, but A has already been added to the merged * list from the old list. This is fine since they must be identical (or we * would have invalidated A), so we detect it and just destroy the new * instance of A. * * Merged List: A, B */ void RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList, nsDisplayList* aOldList, nsDisplayList* aOutList) { nsDisplayList merged(&mBuilder); const auto ReuseItem = [&](nsDisplayItem* aItem) { merged.AppendToTop(aItem); aItem->SetReused(true); if (aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { IncrementSubDocPresShellPaintCount(aItem); } }; // Build a hashtable of items in the old list so we can look for them quickly. // We have similar data in the nsIFrame DisplayItems() property, but it doesn't // know which display list items are in, and we only want to match items in // this list. nsDataHashtable oldListLookup(aOldList->Count()); for (nsDisplayItem* i = aOldList->GetBottom(); i != nullptr; i = i->GetAbove()) { i->SetReused(false); if (!aNewList->IsEmpty()) { oldListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i); } } #ifdef DEBUG nsDataHashtable newListLookup(aNewList->Count()); for (nsDisplayItem* i = aNewList->GetBottom(); i != nullptr; i = i->GetAbove()) { if (newListLookup.Get({ i->Frame(), i->GetPerFrameKey() }, nullptr)) { MOZ_CRASH_UNSAFE_PRINTF("Duplicate display items detected!: %s(0x%p) type=%d key=%d", i->Name(), i->Frame(), static_cast(i->GetType()), i->GetPerFrameKey()); } newListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i); } #endif while (nsDisplayItem* newItem = aNewList->RemoveBottom()) { if (nsDisplayItem* oldItem = oldListLookup.Get({ newItem->Frame(), newItem->GetPerFrameKey() })) { if (oldItem->IsReused()) { // If there's a matching item in the old list, but we've already put it into the // merged list then stick with that one. Merge any child lists, and then delete the // new item. This solves example 2 from above. if (oldItem->GetChildren()) { MOZ_ASSERT(newItem->GetChildren()); MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), oldItem->GetChildren()); oldItem->UpdateBounds(&mBuilder); } if (oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { MergeLayerEventRegions(oldItem, newItem); } newItem->Destroy(&mBuilder); } else { // The new item has a matching counterpart in the old list, so copy all valid // items from the old list into the merged list until we get to the matched item. nsDisplayItem* old = nullptr; while ((old = aOldList->RemoveBottom()) && !IsSameItem(newItem, old)) { if (!IsAnyAncestorModified(old->FrameForInvalidation())) { ReuseItem(old); } else { oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() }); old->Destroy(&mBuilder); } } MOZ_ASSERT(old && IsSameItem(newItem, old)); MOZ_ASSERT(old == oldItem); // Recursively merge any child lists, destroy the old item and add // the new one to the list. if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS && !IsAnyAncestorModified(old->FrameForInvalidation())) { // Event regions items don't have anything interesting other than // the lists of regions and frames, so we have no need to use the // newer item. Always use the old item instead since we assume it's // likely to have the bigger lists and merging will be quicker. MergeLayerEventRegions(old, newItem); ReuseItem(old); newItem->Destroy(&mBuilder); } else { if (!IsAnyAncestorModified(old->FrameForInvalidation()) && old->GetChildren()) { MOZ_ASSERT(newItem->GetChildren()); MergeDisplayLists(newItem->GetChildren(), old->GetChildren(), newItem->GetChildren()); newItem->UpdateBounds(&mBuilder); } old->Destroy(&mBuilder); merged.AppendToTop(newItem); } } } else { // If there was no matching item in the old list, then we only need to // add the new item to the merged list. merged.AppendToTop(newItem); } } // Reuse the remaining valid items from the old display list. while (nsDisplayItem* old = aOldList->RemoveBottom()) { if (!IsAnyAncestorModified(old->FrameForInvalidation())) { ReuseItem(old); if (old->GetChildren()) { // We are calling MergeDisplayLists() to ensure that the display items // with modified or deleted children will be correctly handled. // Passing an empty new display list as an argument skips the merging // loop above and jumps back here. nsDisplayList empty(&mBuilder); MergeDisplayLists(&empty, old->GetChildren(), old->GetChildren()); old->UpdateBounds(&mBuilder); } if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { MergeLayerEventRegions(old, nullptr); } } else { old->Destroy(&mBuilder); } } aOutList->AppendToTop(&merged); } static void TakeAndAddModifiedFramesFromRootFrame(nsTArray& aFrames, nsIFrame* aRootFrame) { MOZ_ASSERT(aRootFrame); std::vector* frames = aRootFrame->GetProperty(nsIFrame::ModifiedFrameList()); if (!frames) { return; } for (WeakFrame& frame : *frames) { nsIFrame* f = frame.GetFrame(); if (f) { aFrames.AppendElement(f); } } frames->clear(); } static bool SubDocEnumCb(nsIDocument* aDocument, void* aData) { MOZ_ASSERT(aDocument); MOZ_ASSERT(aData); nsTArray* modifiedFrames = static_cast*>(aData); nsIPresShell* presShell = aDocument->GetShell(); nsIFrame* rootFrame = presShell ? presShell->GetRootFrame() : nullptr; if (rootFrame) { TakeAndAddModifiedFramesFromRootFrame(*modifiedFrames, rootFrame); } aDocument->EnumerateSubDocuments(SubDocEnumCb, aData); return true; } static nsTArray GetModifiedFrames(nsIFrame* aDisplayRootFrame) { MOZ_ASSERT(aDisplayRootFrame); nsTArray modifiedFrames; TakeAndAddModifiedFramesFromRootFrame(modifiedFrames, aDisplayRootFrame); nsIDocument* rootdoc = aDisplayRootFrame->PresContext()->Document(); if (rootdoc) { rootdoc->EnumerateSubDocuments(SubDocEnumCb, &modifiedFrames); } return modifiedFrames; } // ComputeRebuildRegion debugging // #define CRR_DEBUG 1 #if CRR_DEBUG # define CRR_LOG(...) printf_stderr(__VA_ARGS__) #else # define CRR_LOG(...) #endif /** * Given a list of frames that has been modified, computes the region that we need to * do display list building for in order to build all modified display items. * * When a modified frame is within a stacking context (with an existing display item), * then we only contribute to the build area within the stacking context, as well as forcing * display list building to descend to the stacking context. We don't need to add build * area outside of the stacking context (and force items above/below the stacking context * container item to be built), since just matching the position of the stacking context * container item is sufficient to ensure correct ordering during merging. * * We need to rebuild all items that might intersect with the modified frame, both now * and during async changes on the compositor. We do this by rebuilding the area covered * by the changed frame, as well as rebuilding all items that have a different (async) * AGR to the changed frame. If we have changes to multiple AGRs (within a stacking * context), then we rebuild that stacking context entirely. * * @param aModifiedFrames The list of modified frames. * @param aOutDirty The result region to use for display list building. * @param aOutModifiedAGR The modified AGR for the root stacking context. * @param aOutFramesWithProps The list of frames to which we attached partial build * data so that it can be cleaned up. * * @return true if we succesfully computed a partial rebuild region, false if a full * build is required. */ bool RetainedDisplayListBuilder::ComputeRebuildRegion(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); } // TODO: There is almost certainly a faster way of doing this, probably can be combined with the ancestor // walk for TransformFrameRectToAncestor. AnimatedGeometryRoot* agr = mBuilder.FindAnimatedGeometryRootFor(f)->GetAsyncAGR(); CRR_LOG("Processing frame %p with agr %p\n", f, agr->mFrame); // Convert the frame's overflow rect into the coordinate space // of the nearest stacking context that has an existing display item. // We store that as a dirty rect on that stacking context so that we build // all items that intersect the changed frame within the stacking context, // and then we use MarkFrameForDisplayIfVisible to make sure the stacking // context itself gets built. We don't need to build items that intersect outside // of the stacking context, since we know the stacking context item exists in // the old list, so we can trivially merge without needing other items. nsRect overflow = f->GetVisualOverflowRectRelativeToSelf(); nsIFrame* currentFrame = f; while (currentFrame != mBuilder.RootReferenceFrame()) { // Convert 'overflow' into the coordinate space of the nearest stacking context // or display port ancestor and update 'currentFrame' to point to that frame. overflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, overflow, mBuilder.RootReferenceFrame(), nullptr, nullptr, /* aStopAtStackingContextAndDisplayPort = */ true, ¤tFrame); MOZ_ASSERT(currentFrame); if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) { CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); nsIScrollableFrame* sf = do_QueryFrame(currentFrame); MOZ_ASSERT(sf); nsRect displayPort; DebugOnly hasDisplayPort = nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort, RelativeTo::ScrollPort); MOZ_ASSERT(hasDisplayPort); // get it relative to the scrollport (from the scrollframe) nsRect r = overflow - sf->GetScrollPortRect().TopLeft(); r.IntersectRect(r, displayPort); if (!r.IsEmpty()) { nsRect* rect = currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); if (!rect) { rect = new nsRect(); currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); currentFrame->SetHasOverrideDirtyRegion(true); } rect->UnionRect(*rect, r); aOutFramesWithProps->AppendElement(currentFrame); CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, r.width, r.height); // TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to // ensure that this displayport, plus any items that move relative to it get rebuilt, // and then not contribute to the root dirty area? overflow = sf->GetScrollPortRect(); } else { // Don't contribute to the root dirty area at all. overflow.SetEmpty(); break; } } if (currentFrame->IsStackingContext()) { CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); // If we found an intermediate stacking context with an existing display item // then we can store the dirty rect there and stop. If we couldn't find one then // we need to keep bubbling up to the next stacking context. if (currentFrame != mBuilder.RootReferenceFrame() && currentFrame->HasDisplayItems()) { mBuilder.MarkFrameForDisplayIfVisible(currentFrame); // Store the stacking context relative dirty area such // that display list building will pick it up when it // gets to it. nsDisplayListBuilder::DisplayListBuildingData* data = currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); if (!data) { data = new nsDisplayListBuilder::DisplayListBuildingData; currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingRect(), data); currentFrame->SetHasOverrideDirtyRegion(true); aOutFramesWithProps->AppendElement(currentFrame); } data->mDirtyRect.UnionRect(data->mDirtyRect, overflow); CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", overflow.x, overflow.y, overflow.width, overflow.height); if (!data->mModifiedAGR) { data->mModifiedAGR = agr; } else if (data->mModifiedAGR != agr) { data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf(); CRR_LOG("Found multiple modified AGRs within this stacking context, giving up\n"); } // Don't contribute to the root dirty area at all. agr = nullptr; overflow.SetEmpty(); break; } } } aOutDirty->UnionRect(*aOutDirty, overflow); CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, overflow.y, overflow.width, overflow.height); // If we get changed frames from multiple AGRS, then just give up as it gets really complex to // track which items would need to be marked in MarkFramesForDifferentAGR. if (!*aOutModifiedAGR) { *aOutModifiedAGR = agr; } else if (agr && *aOutModifiedAGR != agr) { CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); return false; } } return true; } bool RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop) { mBuilder.RemoveModifiedWindowDraggingRegion(); if (mBuilder.ShouldSyncDecodeImages()) { MarkFramesWithItemsAndImagesModified(&mList); } mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); nsTArray modifiedFrames = GetModifiedFrames(mBuilder.RootReferenceFrame()); if (mPreviousCaret != mBuilder.GetCaretFrame()) { if (mPreviousCaret) { mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret); } if (mBuilder.GetCaretFrame()) { mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame()); } mPreviousCaret = mBuilder.GetCaretFrame(); } nsRect modifiedDirty; AnimatedGeometryRoot* modifiedAGR = nullptr; nsTArray framesWithProps; bool merged = false; if (!mList.IsEmpty() && ComputeRebuildRegion(modifiedFrames, &modifiedDirty, &modifiedAGR, &framesWithProps)) { modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); PreProcessDisplayList(&mList, modifiedAGR); nsDisplayList modifiedDL(&mBuilder); if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) { mBuilder.SetDirtyRect(modifiedDirty); mBuilder.SetPartialUpdate(true); mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL); nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(), nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()), mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(), aBackstop); mBuilder.SetPartialUpdate(false); //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height); //nsFrame::PrintDisplayList(&builder, modifiedDL); } else { // TODO: We can also skip layer building and painting if // PreProcessDisplayList didn't end up changing anything // Invariant: display items should have their original state here. // printf_stderr("Skipping display list building since nothing needed to be done\n"); } // |modifiedDL| can sometimes be empty here. We still perform the // display list merging to prune unused items (for example, items that // are not visible anymore) from the old list. // TODO: Optimization opportunity. In this case, MergeDisplayLists() // unnecessarily creates a hashtable of the old items. MergeDisplayLists(&modifiedDL, &mList, &mList); //printf_stderr("Painting --- Merged list:\n"); //nsFrame::PrintDisplayList(&builder, list); merged = true; } mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); // TODO: Do we mark frames as modified during displaylist building? If // we do this isn't gonna work. for (nsIFrame* f : modifiedFrames) { if (f) { f->SetFrameIsModified(false); } } // Override dirty regions should only exist during this function. We set them up during // ComputeRebuildRegion, and clear them here. for (nsIFrame* f: framesWithProps) { f->SetHasOverrideDirtyRegion(false); f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect()); f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); } return merged; }