RetainedDisplayListBuilder.cpp (65055B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 */ 7 8 #include "RetainedDisplayListBuilder.h" 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/AutoRestore.h" 12 #include "mozilla/DisplayPortUtils.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/ProfilerLabels.h" 15 #include "mozilla/ScrollContainerFrame.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "nsCanvasFrame.h" 18 #include "nsIFrame.h" 19 #include "nsIFrameInlines.h" 20 #include "nsPlaceholderFrame.h" 21 #include "nsSubDocumentFrame.h" 22 23 /** 24 * Code for doing display list building for a modified subset of the window, 25 * and then merging it into the existing display list (for the full window). 26 * 27 * The approach primarily hinges on the observation that the 'true' ordering 28 * of display items is represented by a DAG (only items that intersect in 2d 29 * space have a defined ordering). Our display list is just one of a many 30 * possible linear representations of this ordering. 31 * 32 * Each time a frame changes (gets a new ComputedStyle, or has a size/position 33 * change), we schedule a paint (as we do currently), but also reord the frame 34 * that changed. 35 * 36 * When the next paint occurs we union the overflow areas (in screen space) of 37 * the changed frames, and compute a rect/region that contains all changed 38 * items. We then build a display list just for this subset of the screen and 39 * merge it into the display list from last paint. 40 * 41 * Any items that exist in one list and not the other must not have a defined 42 * ordering in the DAG, since they need to intersect to have an ordering and 43 * we would have built both in the new list if they intersected. Given that, we 44 * can align items that appear in both lists, and any items that appear between 45 * matched items can be inserted into the merged list in any order. 46 * 47 * Frames that are a stacking context, containing blocks for position:fixed 48 * descendants, and don't have any continuations (see 49 * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with 50 * separate retaining decisions made. 51 * 52 * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest 53 * ancestor frame which can be moved asynchronously on the compositor thread. 54 * These are currently nsDisplayItems which return true from CanMoveAsync 55 * (animated nsDisplayTransform and nsDisplayStickyPosition) and 56 * ActiveScrolledRoots. 57 * 58 * For each context that we run the retaining algorithm, there can only be 59 * mutations to one AnimatedGeometryRoot. This is because we are unable to 60 * reason about intersections of items that might then move relative to each 61 * other without RDL running again. If there are mutations to multiple 62 * AnimatedGeometryRoots, then we bail out and rebuild all the items in the 63 * context. 64 * 65 * Otherwise, when mutations are restricted to a single AGR, we pre-process the 66 * old display list and mark the frames for all existing (unmodified!) items 67 * that belong to a different AGR and ensure that we rebuild those items for 68 * correct sorting with the modified ones. 69 */ 70 71 namespace mozilla { 72 73 RetainedDisplayListData::RetainedDisplayListData() 74 : mModifiedFrameLimit( 75 StaticPrefs::layout_display_list_rebuild_frame_limit()) {} 76 77 void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) { 78 MOZ_ASSERT(!aFrame->IsFrameModified()); 79 Flags(aFrame) += RetainedDisplayListData::FrameFlag::Modified; 80 aFrame->SetFrameIsModified(true); 81 mModifiedFrameCount++; 82 } 83 84 static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { 85 for (nsDisplayItem* i : *aList) { 86 if (!i->HasDeletedFrame() && i->CanBeReused() && 87 !i->Frame()->IsFrameModified()) { 88 // If we have existing cached geometry for this item, then check that for 89 // whether we need to invalidate for a sync decode. If we don't, then 90 // use the item's flags. 91 // XXX: handle webrender case by looking up retained data for the item 92 // and checking InvalidateForSyncDecodeImages 93 bool invalidate = false; 94 if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { 95 invalidate = true; 96 } 97 98 if (invalidate) { 99 DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name()); 100 i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); 101 if (i->GetDependentFrame()) { 102 i->GetDependentFrame()->MarkNeedsDisplayItemRebuild(); 103 } 104 } 105 } 106 if (i->GetChildren()) { 107 MarkFramesWithItemsAndImagesModified(i->GetChildren()); 108 } 109 } 110 } 111 112 static nsIFrame* SelectAGRForFrame(nsIFrame* aFrame, nsIFrame* aParentAGR) { 113 if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) { 114 return aParentAGR; 115 } 116 117 if (!aFrame->HasOverrideDirtyRegion()) { 118 return nullptr; 119 } 120 121 nsDisplayListBuilder::DisplayListBuildingData* data = 122 aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); 123 124 return data && data->mModifiedAGR ? data->mModifiedAGR : nullptr; 125 } 126 127 void RetainedDisplayListBuilder::AddSizeOfIncludingThis( 128 nsWindowSizes& aSizes) const { 129 aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this); 130 mBuilder.AddSizeOfExcludingThis(aSizes); 131 mList.AddSizeOfExcludingThis(aSizes); 132 } 133 134 bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { 135 nsIFrame* f = aFrame; 136 while (f) { 137 if (f->IsFrameModified()) { 138 return true; 139 } 140 141 if (aStopAtFrame && f == aStopAtFrame) { 142 break; 143 } 144 145 f = nsLayoutUtils::GetDisplayListParent(f); 146 } 147 148 return false; 149 } 150 151 // Removes any display items that belonged to a frame that was deleted, 152 // and mark frames that belong to a different AGR so that get their 153 // items built again. 154 // TODO: We currently descend into all children even if we don't have an AGR 155 // to mark, as child stacking contexts might. It would be nice if we could 156 // jump into those immediately rather than walking the entire thing. 157 bool RetainedDisplayListBuilder::PreProcessDisplayList( 158 RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, 159 nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, 160 nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth, 161 bool aKeepLinked) { 162 // The DAG merging algorithm does not have strong mechanisms in place to keep 163 // the complexity of the resulting DAG under control. In some cases we can 164 // build up edges very quickly. Detect those cases and force a full display 165 // list build if we hit them. 166 static const uint32_t kMaxEdgeRatio = 5; 167 const bool initializeDAG = !aList->mDAG.Length(); 168 if (!aKeepLinked && !initializeDAG && 169 aList->mDAG.mDirectPredecessorList.Length() > 170 (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { 171 return false; 172 } 173 174 // If we had aKeepLinked=true for this list on the previous paint, then 175 // mOldItems will already be initialized as it won't have been consumed during 176 // a merge. 177 const bool initializeOldItems = aList->mOldItems.IsEmpty(); 178 if (initializeOldItems) { 179 aList->mOldItems.SetCapacity(aList->Length()); 180 } else { 181 MOZ_RELEASE_ASSERT(!initializeDAG); 182 } 183 184 MOZ_RELEASE_ASSERT( 185 initializeDAG || 186 aList->mDAG.Length() == 187 (initializeOldItems ? aList->Length() : aList->mOldItems.Length())); 188 189 nsDisplayList out(Builder()); 190 191 size_t i = 0; 192 while (nsDisplayItem* item = aList->RemoveBottom()) { 193 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 194 item->SetMergedPreProcessed(false, true); 195 #endif 196 197 // If we have a previously initialized old items list, then it can differ 198 // from the current list due to items removed for having a deleted frame. 199 // We can't easily remove these, since the DAG has entries for those indices 200 // and it's hard to rewrite in-place. 201 // Skip over entries with no current item to keep the iterations in sync. 202 if (!initializeOldItems) { 203 while (!aList->mOldItems[i].mItem) { 204 i++; 205 } 206 } 207 208 if (initializeDAG) { 209 if (i == 0) { 210 aList->mDAG.AddNode(Span<const MergedListIndex>()); 211 } else { 212 MergedListIndex previous(i - 1); 213 aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1)); 214 } 215 } 216 217 if (!item->CanBeReused() || item->HasDeletedFrame() || 218 AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { 219 if (initializeOldItems) { 220 aList->mOldItems.AppendElement(OldItemInfo(nullptr)); 221 } else { 222 MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item); 223 aList->mOldItems[i].mItem = nullptr; 224 } 225 226 item->Destroy(&mBuilder); 227 Metrics()->mRemovedItems++; 228 229 i++; 230 aUpdated = PartialUpdateResult::Updated; 231 continue; 232 } 233 234 if (initializeOldItems) { 235 aList->mOldItems.AppendElement(OldItemInfo(item)); 236 } 237 238 // If we're not going to keep the list linked, then this old item entry 239 // is the only pointer to the item. Let it know that it now strongly 240 // owns the item, so it can destroy it if it goes away. 241 aList->mOldItems[i].mOwnsItem = !aKeepLinked; 242 243 item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth); 244 245 nsIFrame* f = item->Frame(); 246 247 if (item->GetChildren()) { 248 // If children inside this list were invalid, then we'd have walked the 249 // ancestors and set ForceDescendIntoVisible on the current frame. If an 250 // ancestor is modified, then we'll throw this away entirely. Either way, 251 // we won't need to run merging on this sublist, and we can keep the items 252 // linked into their display list. 253 // The caret can move without invalidating, but we always set the force 254 // descend into frame state bit on that frame, so check for that too. 255 // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make 256 // us think future siblings need to be merged, even though we don't really 257 // need to. 258 bool keepLinked = aKeepLinked; 259 nsIFrame* invalid = item->FrameForInvalidation(); 260 if (!invalid->ForceDescendIntoIfVisible() && 261 !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { 262 keepLinked = true; 263 } 264 265 // If this item's frame is an AGR (can be moved asynchronously by the 266 // compositor), then use that frame for descendants. Also pass the ASR 267 // for that item, so that descendants can compare to see if any new 268 // ASRs have been pushed since. 269 nsIFrame* asyncAncestor = aAsyncAncestor; 270 const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR; 271 if (item->CanMoveAsync()) { 272 asyncAncestor = item->Frame(); 273 asyncAncestorASR = item->GetActiveScrolledRoot(); 274 } 275 276 if (!PreProcessDisplayList( 277 item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated, 278 asyncAncestor, asyncAncestorASR, item->Frame(), 279 item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) { 280 MOZ_RELEASE_ASSERT( 281 !aKeepLinked, 282 "Can't early return since we need to move the out list back"); 283 return false; 284 } 285 } 286 287 // TODO: We should be able to check the clipped bounds relative 288 // to the common AGR (of both the existing item and the invalidated 289 // frame) and determine if they can ever intersect. 290 // TODO: We only really need to build the ancestor container item that is a 291 // sibling of the changed thing to get correct ordering. The changed content 292 // is a frame though, and it's hard to map that to container items in this 293 // list. 294 // If an ancestor display item is an AGR, and our ASR matches the ASR 295 // of that item, then there can't have been any new ASRs pushed since that 296 // item, so that item is our AGR. Otherwise, our AGR is our ASR. 297 // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot 298 // should be the same or a descendant and also non-null. Unfortunately an 299 // RDL bug means this can be wrong for sticky items after a partial update, 300 // so we have to work around it. Bug 1730749 and bug 1730826 should resolve 301 // this. 302 nsIFrame* agrFrame = nullptr; 303 const ActiveScrolledRoot* asr = item->GetNearestScrollASR(); 304 if (aAsyncAncestorASR == asr || !asr) { 305 agrFrame = aAsyncAncestor; 306 } else { 307 auto* scrollContainerFrame = asr->ScrollFrame(); 308 if (MOZ_UNLIKELY(!scrollContainerFrame)) { 309 MOZ_DIAGNOSTIC_ASSERT(false); 310 gfxCriticalNoteOnce << "Found null mScrollContainerFrame in asr"; 311 return false; 312 } 313 agrFrame = scrollContainerFrame->GetScrolledFrame(); 314 } 315 316 if (aAGR && agrFrame != aAGR) { 317 mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame()); 318 } 319 320 // If we're going to keep this linked list and not merge it, then mark the 321 // item as used and put it back into the list. 322 if (aKeepLinked) { 323 item->SetReused(true); 324 if (item->GetChildren()) { 325 item->UpdateBounds(Builder()); 326 } 327 if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { 328 IncrementSubDocPresShellPaintCount(item); 329 } 330 out.AppendToTop(item); 331 } 332 i++; 333 } 334 335 MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length()); 336 337 if (aKeepLinked) { 338 aList->AppendToTop(&out); 339 } 340 341 return true; 342 } 343 344 void IncrementPresShellPaintCount(nsDisplayListBuilder* aBuilder, 345 nsDisplayItem* aItem) { 346 MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); 347 348 nsSubDocumentFrame* subDocFrame = 349 static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame(); 350 MOZ_ASSERT(subDocFrame); 351 352 PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); 353 MOZ_ASSERT(presShell); 354 355 aBuilder->IncrementPresShellPaintCount(presShell); 356 } 357 358 void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount( 359 nsDisplayItem* aItem) { 360 IncrementPresShellPaintCount(&mBuilder, aItem); 361 } 362 363 static Maybe<const ActiveScrolledRoot*> SelectContainerASR( 364 const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR, 365 Maybe<const ActiveScrolledRoot*>& aContainerASR) { 366 const ActiveScrolledRoot* itemClipASR = 367 aClipChain ? aClipChain->mASR : nullptr; 368 369 MOZ_DIAGNOSTIC_ASSERT(!aClipChain || aClipChain->mOnStack || !itemClipASR || 370 itemClipASR->mFrame); 371 372 const ActiveScrolledRoot* finiteBoundsASR = 373 ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR); 374 375 if (!aContainerASR) { 376 return Some(finiteBoundsASR); 377 } 378 379 return Some( 380 ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR)); 381 } 382 383 static void UpdateASR(nsDisplayItem* aItem, 384 Maybe<const ActiveScrolledRoot*>& aContainerASR) { 385 const Maybe<const ActiveScrolledRoot*> frameASR = 386 aItem->GetBaseASRForAncestorOfContainedASR(); 387 if (!frameASR) { 388 return; 389 } 390 391 if (!aContainerASR) { 392 aItem->SetActiveScrolledRoot(*frameASR); 393 return; 394 } 395 396 aItem->SetActiveScrolledRoot( 397 ActiveScrolledRoot::PickAncestor(*frameASR, *aContainerASR)); 398 } 399 400 static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) { 401 aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot()); 402 } 403 404 OldItemInfo::OldItemInfo(nsDisplayItem* aItem) 405 : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) { 406 if (mItem) { 407 // Clear cached modified frame state when adding an item to the old list. 408 mItem->SetModifiedFrame(false); 409 } 410 } 411 412 void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, 413 MergedListIndex aIndex) { 414 AddedToMergedList(aIndex); 415 } 416 417 void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, 418 nsTArray<MergedListIndex>&& aDirectPredecessors) { 419 MOZ_ASSERT(!IsUsed()); 420 mUsed = mDiscarded = true; 421 mDirectPredecessors = std::move(aDirectPredecessors); 422 if (mItem) { 423 MOZ_ASSERT(mOwnsItem); 424 mItem->Destroy(aBuilder->Builder()); 425 aBuilder->Metrics()->mRemovedItems++; 426 } 427 mItem = nullptr; 428 } 429 430 bool OldItemInfo::IsChanged() { 431 return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame(); 432 } 433 434 /** 435 * A C++ implementation of Markus Stange's merge-dags algorithm. 436 * https://github.com/mstange/merge-dags 437 * 438 * MergeState handles combining a new list of display items into an existing 439 * DAG and computes the new DAG in a single pass. 440 * Each time we add a new item, we resolve all dependencies for it, so that the 441 * resulting list and DAG are built in topological ordering. 442 */ 443 class MergeState { 444 public: 445 MergeState(RetainedDisplayListBuilder* aBuilder, 446 RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem) 447 : mBuilder(aBuilder), 448 mOldList(&aOldList), 449 mOldItems(std::move(aOldList.mOldItems)), 450 mOldDAG( 451 std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>( 452 &aOldList.mDAG))), 453 mMergedItems(aBuilder->Builder()), 454 mOuterItem(aOuterItem), 455 mResultIsModified(false) { 456 mMergedDAG.EnsureCapacityFor(mOldDAG); 457 MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length()); 458 } 459 460 Maybe<MergedListIndex> ProcessItemFromNewList( 461 nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) { 462 OldListIndex oldIndex; 463 MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() == 464 HasModifiedFrame(aNewItem)); 465 if (!aNewItem->HasModifiedFrame() && 466 HasMatchingItemInOldList(aNewItem, &oldIndex)) { 467 mBuilder->Metrics()->mRebuiltItems++; 468 nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem; 469 MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() == 470 aNewItem->GetPerFrameKey() && 471 oldItem->Frame() == aNewItem->Frame()); 472 if (!mOldItems[oldIndex.val].IsChanged()) { 473 MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed()); 474 nsDisplayItem* destItem; 475 if (ShouldUseNewItem(aNewItem)) { 476 destItem = aNewItem; 477 } else { 478 destItem = oldItem; 479 // The building rect can depend on the overflow rect (when the parent 480 // frame is position:fixed), which can change without invalidating 481 // the frame/items. If we're using the old item, copy the building 482 // rect across from the new item. 483 oldItem->SetBuildingRect(aNewItem->GetBuildingRect()); 484 } 485 486 MergeChildLists(aNewItem, oldItem, destItem); 487 488 AutoTArray<MergedListIndex, 2> directPredecessors = 489 ProcessPredecessorsOfOldNode(oldIndex); 490 MergedListIndex newIndex = AddNewNode( 491 destItem, Some(oldIndex), directPredecessors, aPreviousItem); 492 mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex); 493 if (destItem == aNewItem) { 494 oldItem->Destroy(mBuilder->Builder()); 495 } else { 496 aNewItem->Destroy(mBuilder->Builder()); 497 } 498 return Some(newIndex); 499 } 500 } 501 mResultIsModified = true; 502 return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(), 503 aPreviousItem)); 504 } 505 506 void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem, 507 nsDisplayItem* aOutItem) { 508 if (!aOutItem->GetChildren()) { 509 return; 510 } 511 512 Maybe<const ActiveScrolledRoot*> containerASRForChildren; 513 nsDisplayList empty(mBuilder->Builder()); 514 const bool modified = mBuilder->MergeDisplayLists( 515 aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(), 516 aOutItem->GetChildren(), containerASRForChildren, aOutItem); 517 if (modified) { 518 aOutItem->InvalidateCachedChildInfo(mBuilder->Builder()); 519 UpdateASR(aOutItem, containerASRForChildren); 520 mResultIsModified = true; 521 } else if (aOutItem == aNewItem) { 522 // If nothing changed, but we copied the contents across to 523 // the new item, then also copy the ASR data. 524 CopyASR(aOldItem, aNewItem); 525 } 526 // Ideally we'd only UpdateBounds if something changed, but 527 // nsDisplayWrapList also uses this to update the clip chain for the 528 // current ASR, which gets reset during RestoreState(), so we always need 529 // to run it again. 530 aOutItem->UpdateBounds(mBuilder->Builder()); 531 532 if (aOutItem->GetType() == DisplayItemType::TYPE_TRANSFORM) { 533 MOZ_ASSERT(!aNewItem || 534 aNewItem->GetType() == DisplayItemType::TYPE_TRANSFORM); 535 MOZ_ASSERT(aOldItem->GetType() == DisplayItemType::TYPE_TRANSFORM); 536 static_cast<nsDisplayTransform*>(aOutItem)->SetContainsASRs( 537 static_cast<nsDisplayTransform*>(aOldItem)->GetContainsASRs() || 538 (aNewItem 539 ? static_cast<nsDisplayTransform*>(aNewItem)->GetContainsASRs() 540 : false)); 541 } 542 } 543 544 bool ShouldUseNewItem(nsDisplayItem* aNewItem) { 545 // Generally we want to use the old item when the frame isn't marked as 546 // modified so that any cached information on the item (or referencing the 547 // item) gets retained. Quite a few FrameLayerBuilder performance 548 // improvements benefit by this. Sometimes, however, we can end up where the 549 // new item paints something different from the old item, even though we 550 // haven't modified the frame, and it's hard to fix. In these cases we just 551 // always use the new item to be safe. 552 DisplayItemType type = aNewItem->GetType(); 553 if (type == DisplayItemType::TYPE_SOLID_COLOR) { 554 // The canvas background color item can paint the color from another 555 // frame, and even though we schedule a paint, we don't mark the canvas 556 // frame as invalid. 557 return true; 558 } 559 560 if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { 561 // We intentionally don't mark the root table frame as modified when a 562 // subframe changes, even though the border collapse item for the root 563 // frame is what paints the changed border. Marking the root frame as 564 // modified would rebuild display items for the whole table area, and we 565 // don't want that. 566 return true; 567 } 568 569 if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { 570 // Text overflow marker items are created with the wrapping block as their 571 // frame, and have an index value to note which line they are created for. 572 // Their rendering can change if the items on that line change, which may 573 // not mark the block as modified. We rebuild them if we build any item on 574 // the line, so we should always get new items if they might have changed 575 // rendering, and it's easier to just use the new items rather than 576 // computing if we actually need them. 577 return true; 578 } 579 580 if (type == DisplayItemType::TYPE_SUBDOCUMENT || 581 type == DisplayItemType::TYPE_STICKY_POSITION) { 582 // nsDisplaySubDocument::mShouldFlatten can change without an invalidation 583 // (and is the reason we unconditionally build the subdocument item), so 584 // always use the new one to make sure we get the right value. 585 // Same for |nsDisplayStickyPosition::mShouldFlatten|. 586 return true; 587 } 588 589 if (type == DisplayItemType::TYPE_CARET) { 590 // The caret can change position while still being owned by the same frame 591 // and we don't invalidate in that case. Use the new version since the 592 // changed bounds are needed for DLBI. 593 return true; 594 } 595 596 if (type == DisplayItemType::TYPE_MASK || 597 type == DisplayItemType::TYPE_FILTER || 598 type == DisplayItemType::TYPE_SVG_WRAPPER) { 599 // SVG items have some invalidation issues, see bugs 1494110 and 1494663. 600 return true; 601 } 602 603 if (type == DisplayItemType::TYPE_TRANSFORM) { 604 // Prerendering of transforms can change without frame invalidation. 605 return true; 606 } 607 608 return false; 609 } 610 611 RetainedDisplayList Finalize() { 612 for (size_t i = 0; i < mOldDAG.Length(); i++) { 613 if (mOldItems[i].IsUsed()) { 614 continue; 615 } 616 617 AutoTArray<MergedListIndex, 2> directPredecessors = 618 ResolveNodeIndexesOldToMerged( 619 mOldDAG.GetDirectPredecessors(OldListIndex(i))); 620 ProcessOldNode(OldListIndex(i), std::move(directPredecessors)); 621 } 622 623 RetainedDisplayList result(mBuilder->Builder()); 624 result.AppendToTop(&mMergedItems); 625 result.mDAG = std::move(mMergedDAG); 626 MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Length()); 627 return result; 628 } 629 630 bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) { 631 // Look for an item that matches aItem's frame and per-frame-key, but isn't 632 // the same item. 633 uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0; 634 nsIFrame* frame = aItem->Frame(); 635 for (nsDisplayItem* i : frame->DisplayItems()) { 636 if (i != aItem && i->Frame() == frame && 637 i->GetPerFrameKey() == aItem->GetPerFrameKey()) { 638 if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) { 639 return true; 640 } 641 } 642 } 643 return false; 644 } 645 646 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 647 bool HasModifiedFrame(nsDisplayItem* aItem) { 648 nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr; 649 return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame); 650 } 651 #endif 652 653 void UpdateContainerASR(nsDisplayItem* aItem) { 654 mContainerASR = SelectContainerASR( 655 aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR); 656 } 657 658 MergedListIndex AddNewNode( 659 nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex, 660 Span<const MergedListIndex> aDirectPredecessors, 661 const Maybe<MergedListIndex>& aExtraDirectPredecessor) { 662 if (aItem->GetType() != DisplayItemType::TYPE_VT_CAPTURE) { 663 UpdateContainerASR(aItem); 664 } 665 aItem->NotifyUsed(mBuilder->Builder()); 666 667 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 668 for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) { 669 if (i->Frame() == aItem->Frame() && 670 i->GetPerFrameKey() == aItem->GetPerFrameKey()) { 671 MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem()); 672 } 673 } 674 675 aItem->SetMergedPreProcessed(true, false); 676 #endif 677 678 mMergedItems.AppendToTop(aItem); 679 mBuilder->Metrics()->mTotalItems++; 680 681 MergedListIndex newIndex = 682 mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); 683 return newIndex; 684 } 685 686 void ProcessOldNode(OldListIndex aNode, 687 nsTArray<MergedListIndex>&& aDirectPredecessors) { 688 nsDisplayItem* item = mOldItems[aNode.val].mItem; 689 if (mOldItems[aNode.val].IsChanged()) { 690 mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors)); 691 mResultIsModified = true; 692 } else { 693 MergeChildLists(nullptr, item, item); 694 695 if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { 696 mBuilder->IncrementSubDocPresShellPaintCount(item); 697 } 698 item->SetReused(true); 699 mBuilder->Metrics()->mReusedItems++; 700 mOldItems[aNode.val].AddedToMergedList( 701 AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing())); 702 } 703 } 704 705 struct PredecessorStackItem { 706 PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors) 707 : mNode(aNode), 708 mDirectPredecessors(aPredecessors), 709 mCurrentPredecessorIndex(0) {} 710 711 bool IsFinished() { 712 return mCurrentPredecessorIndex == mDirectPredecessors.Length(); 713 } 714 715 OldListIndex GetAndIncrementCurrentPredecessor() { 716 return mDirectPredecessors[mCurrentPredecessorIndex++]; 717 } 718 719 OldListIndex mNode; 720 Span<OldListIndex> mDirectPredecessors; 721 size_t mCurrentPredecessorIndex; 722 }; 723 724 AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode( 725 OldListIndex aNode) { 726 AutoTArray<PredecessorStackItem, 256> mStack; 727 mStack.AppendElement( 728 PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode))); 729 730 while (true) { 731 if (mStack.LastElement().IsFinished()) { 732 // If we've finished processing all the entries in the current set, then 733 // pop it off the processing stack and process it. 734 PredecessorStackItem item = mStack.PopLastElement(); 735 AutoTArray<MergedListIndex, 2> result = 736 ResolveNodeIndexesOldToMerged(item.mDirectPredecessors); 737 738 if (mStack.IsEmpty()) { 739 return result; 740 } 741 742 ProcessOldNode(item.mNode, std::move(result)); 743 } else { 744 // Grab the current predecessor, push predecessors of that onto the 745 // processing stack (if it hasn't already been processed), and then 746 // advance to the next entry. 747 OldListIndex currentIndex = 748 mStack.LastElement().GetAndIncrementCurrentPredecessor(); 749 if (!mOldItems[currentIndex.val].IsUsed()) { 750 mStack.AppendElement(PredecessorStackItem( 751 currentIndex, mOldDAG.GetDirectPredecessors(currentIndex))); 752 } 753 } 754 } 755 } 756 757 AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged( 758 Span<OldListIndex> aDirectPredecessors) { 759 AutoTArray<MergedListIndex, 2> result; 760 result.SetCapacity(aDirectPredecessors.Length()); 761 for (OldListIndex index : aDirectPredecessors) { 762 OldItemInfo& oldItem = mOldItems[index.val]; 763 if (oldItem.IsDiscarded()) { 764 for (MergedListIndex inner : oldItem.mDirectPredecessors) { 765 if (!result.Contains(inner)) { 766 result.AppendElement(inner); 767 } 768 } 769 } else { 770 result.AppendElement(oldItem.mIndex); 771 } 772 } 773 return result; 774 } 775 776 RetainedDisplayListBuilder* mBuilder; 777 RetainedDisplayList* mOldList; 778 Maybe<const ActiveScrolledRoot*> mContainerASR; 779 nsTArray<OldItemInfo> mOldItems; 780 DirectedAcyclicGraph<OldListUnits> mOldDAG; 781 // Unfortunately we can't use strong typing for the hashtables 782 // since they internally encode the type with the mOps pointer, 783 // and assert when we try swap the contents 784 nsDisplayList mMergedItems; 785 DirectedAcyclicGraph<MergedListUnits> mMergedDAG; 786 nsDisplayItem* mOuterItem; 787 bool mResultIsModified; 788 }; 789 790 #ifdef DEBUG 791 void VerifyNotModified(nsDisplayList* aList) { 792 for (nsDisplayItem* item : *aList) { 793 MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); 794 795 if (item->GetChildren()) { 796 VerifyNotModified(item->GetChildren()); 797 } 798 } 799 } 800 #endif 801 802 /** 803 * Takes two display lists and merges them into an output list. 804 * 805 * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a 806 * maximum of one direct predecessor and one direct successor per node). We add 807 * the two DAGs together, and then output the topological sorted ordering as the 808 * final display list. 809 * 810 * Once we've merged a list, we then retain the DAG (as part of the 811 * RetainedDisplayList object) to use for future merges. 812 */ 813 bool RetainedDisplayListBuilder::MergeDisplayLists( 814 nsDisplayList* aNewList, RetainedDisplayList* aOldList, 815 RetainedDisplayList* aOutList, 816 mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, 817 nsDisplayItem* aOuterItem) { 818 AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging); 819 820 if (!aOldList->IsEmpty()) { 821 // If we still have items in the actual list, then it is because 822 // PreProcessDisplayList decided that it was sure it can't be modified. We 823 // can just use it directly, and throw any new items away. 824 825 aNewList->DeleteAll(&mBuilder); 826 #ifdef DEBUG 827 VerifyNotModified(aOldList); 828 #endif 829 830 if (aOldList != aOutList) { 831 *aOutList = std::move(*aOldList); 832 } 833 834 return false; 835 } 836 837 MergeState merge(this, *aOldList, aOuterItem); 838 839 Maybe<MergedListIndex> previousItemIndex; 840 for (nsDisplayItem* item : aNewList->TakeItems()) { 841 Metrics()->mNewItems++; 842 previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex); 843 } 844 845 *aOutList = merge.Finalize(); 846 aOutContainerASR = merge.mContainerASR; 847 return merge.mResultIsModified; 848 } 849 850 void RetainedDisplayListBuilder::GetModifiedAndFramesWithProps( 851 nsTArray<nsIFrame*>* aOutModifiedFrames, 852 nsTArray<nsIFrame*>* aOutFramesWithProps) { 853 for (auto it = Data()->ConstIterator(); !it.Done(); it.Next()) { 854 nsIFrame* frame = it.Key(); 855 const RetainedDisplayListData::FrameFlags& flags = it.Data(); 856 857 if (flags.contains(RetainedDisplayListData::FrameFlag::Modified)) { 858 aOutModifiedFrames->AppendElement(frame); 859 } 860 861 if (flags.contains(RetainedDisplayListData::FrameFlag::HasProps)) { 862 aOutFramesWithProps->AppendElement(frame); 863 } 864 865 if (flags.contains(RetainedDisplayListData::FrameFlag::HadWillChange)) { 866 Builder()->RemoveFromWillChangeBudgets(frame); 867 } 868 } 869 870 Data()->Clear(); 871 } 872 873 // ComputeRebuildRegion debugging 874 // #define CRR_DEBUG 1 875 #if CRR_DEBUG 876 # define CRR_LOG(...) printf_stderr(__VA_ARGS__) 877 #else 878 # define CRR_LOG(...) 879 #endif 880 881 static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) { 882 for (nsDisplayItem* i : aFrame->DisplayItems()) { 883 if (i->HasDeletedFrame() || i->Frame() != aFrame) { 884 // The main frame for the display item has been deleted or the display 885 // item belongs to another frame. 886 continue; 887 } 888 889 if (i->HasChildren()) { 890 return static_cast<nsDisplayItem*>(i); 891 } 892 } 893 return nullptr; 894 } 895 896 static bool IsInPreserve3DContext(const nsIFrame* aFrame) { 897 return aFrame->Extend3DContext() || 898 aFrame->Combines3DTransformWithAncestors(); 899 } 900 901 // Returns true if |aFrame| can store a display list building rect. 902 // These limitations are necessary to guarantee that 903 // 1) Just enough items are rebuilt to properly update display list 904 // 2) Modified frames will be visited during a partial display list build. 905 static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder, 906 nsIFrame* aFrame) { 907 return aFrame != aBuilder->RootReferenceFrame() && 908 aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() && 909 // Split frames might have placeholders for modified frames in their 910 // unmodified continuation frame. 911 !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation(); 912 } 913 914 static bool ProcessFrameInternal(nsIFrame* aFrame, 915 nsDisplayListBuilder* aBuilder, 916 nsIFrame** aAGR, nsRect& aOverflow, 917 const nsIFrame* aStopAtFrame, 918 nsTArray<nsIFrame*>& aOutFramesWithProps, 919 const bool aStopAtStackingContext) { 920 nsIFrame* currentFrame = aFrame; 921 922 while (currentFrame != aStopAtFrame) { 923 CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", 924 currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y, 925 aOverflow.width, aOverflow.height); 926 927 // If the current frame is an OOF frame, DisplayListBuildingData needs to be 928 // set on all the ancestor stacking contexts of the placeholder frame, up 929 // to the containing block of the OOF frame. This is done to ensure that the 930 // content that might be behind the OOF frame is built for merging. 931 nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) 932 ? currentFrame->GetPlaceholderFrame() 933 : nullptr; 934 935 if (placeholder) { 936 nsRect placeholderOverflow = aOverflow; 937 auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder, 938 placeholderOverflow); 939 if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { 940 placeholderOverflow = nsRect(); 941 } 942 943 CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder, 944 currentFrame); 945 946 CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x, 947 placeholderOverflow.y, placeholderOverflow.width, 948 placeholderOverflow.height); 949 950 // Tracking AGRs for the placeholder processing is not necessary, as the 951 // goal is to only modify the DisplayListBuildingData rect. 952 nsIFrame* dummyAGR = nullptr; 953 954 // Find a common ancestor frame to handle frame continuations. 955 // TODO: It might be possible to write a more specific and efficient 956 // function for this. 957 const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame( 958 currentFrame->GetParent(), placeholder->GetParent()); 959 960 if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR, 961 placeholderOverflow, ancestor, 962 aOutFramesWithProps, false)) { 963 return false; 964 } 965 } 966 967 // Convert 'aOverflow' into the coordinate space of the nearest stacking 968 // context or display port ancestor and update 'currentFrame' to point to 969 // that frame. 970 aOverflow = nsLayoutUtils::TransformFrameRectToAncestor( 971 currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr, 972 /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, 973 ¤tFrame); 974 if (IsInPreserve3DContext(currentFrame)) { 975 return false; 976 } 977 978 MOZ_ASSERT(currentFrame); 979 980 // Check whether the current frame is a scrollable frame with display port. 981 nsRect displayPort; 982 ScrollContainerFrame* sf = do_QueryFrame(currentFrame); 983 nsIContent* content = sf ? currentFrame->GetContent() : nullptr; 984 985 if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) { 986 CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); 987 988 // Get overflow relative to the scrollport (from the scrollframe) 989 nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft(); 990 r.IntersectRect(r, displayPort); 991 if (!r.IsEmpty()) { 992 nsRect* rect = currentFrame->GetProperty( 993 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); 994 if (!rect) { 995 rect = new nsRect(); 996 currentFrame->SetProperty( 997 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); 998 currentFrame->SetHasOverrideDirtyRegion(true); 999 aOutFramesWithProps.AppendElement(currentFrame); 1000 } 1001 rect->UnionRect(*rect, r); 1002 CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, 1003 r.width, r.height); 1004 1005 // TODO: Can we just use MarkFrameForDisplayIfVisible, plus 1006 // MarkFramesForDifferentAGR to ensure that this displayport, plus any 1007 // items that move relative to it get rebuilt, and then not contribute 1008 // to the root dirty area? 1009 aOverflow = sf->GetScrollPortRect(); 1010 } else { 1011 // Don't contribute to the root dirty area at all. 1012 aOverflow.SetEmpty(); 1013 } 1014 } else { 1015 aOverflow.IntersectRect(aOverflow, 1016 currentFrame->InkOverflowRectRelativeToSelf()); 1017 } 1018 1019 if (aOverflow.IsEmpty()) { 1020 break; 1021 } 1022 1023 if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) { 1024 CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); 1025 // If we found an intermediate stacking context with an existing display 1026 // item then we can store the dirty rect there and stop. If we couldn't 1027 // find one then we need to keep bubbling up to the next stacking context. 1028 nsDisplayItem* wrapperItem = 1029 GetFirstDisplayItemWithChildren(currentFrame); 1030 if (!wrapperItem) { 1031 continue; 1032 } 1033 1034 // Store the stacking context relative dirty area such 1035 // that display list building will pick it up when it 1036 // gets to it. 1037 nsDisplayListBuilder::DisplayListBuildingData* data = 1038 currentFrame->GetProperty( 1039 nsDisplayListBuilder::DisplayListBuildingRect()); 1040 if (!data) { 1041 data = new nsDisplayListBuilder::DisplayListBuildingData(); 1042 currentFrame->SetProperty( 1043 nsDisplayListBuilder::DisplayListBuildingRect(), data); 1044 currentFrame->SetHasOverrideDirtyRegion(true); 1045 aOutFramesWithProps.AppendElement(currentFrame); 1046 } 1047 CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", 1048 aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height); 1049 data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow); 1050 1051 if (!aStopAtStackingContext) { 1052 // Continue ascending the frame tree until we reach aStopAtFrame. 1053 continue; 1054 } 1055 1056 // Grab the visible (display list building) rect for children of this 1057 // wrapper item and convert into into coordinate relative to the current 1058 // frame. 1059 nsRect previousVisible = wrapperItem->GetBuildingRectForChildren(); 1060 if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) { 1061 previousVisible -= wrapperItem->ToReferenceFrame(); 1062 } 1063 1064 if (!previousVisible.Contains(aOverflow)) { 1065 // If the overflow area of the changed frame isn't contained within the 1066 // old item, then we might change the size of the item and need to 1067 // update its sorting accordingly. Keep propagating the overflow area up 1068 // so that we build intersecting items for sorting. 1069 continue; 1070 } 1071 1072 if (!data->mModifiedAGR) { 1073 data->mModifiedAGR = *aAGR; 1074 } else if (data->mModifiedAGR != *aAGR) { 1075 data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf(); 1076 CRR_LOG( 1077 "Found multiple modified AGRs within this stacking context, " 1078 "giving up\n"); 1079 } 1080 1081 // Don't contribute to the root dirty area at all. 1082 aOverflow.SetEmpty(); 1083 *aAGR = nullptr; 1084 1085 break; 1086 } 1087 } 1088 return true; 1089 } 1090 1091 bool RetainedDisplayListBuilder::ProcessFrame( 1092 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame, 1093 nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext, 1094 nsRect* aOutDirty, nsIFrame** aOutModifiedAGR) { 1095 if (aFrame->HasOverrideDirtyRegion()) { 1096 aOutFramesWithProps.AppendElement(aFrame); 1097 } 1098 1099 if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { 1100 return true; 1101 } 1102 1103 // TODO: There is almost certainly a faster way of doing this, probably can be 1104 // combined with the ancestor walk for TransformFrameRectToAncestor. 1105 nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame); 1106 1107 CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame); 1108 1109 // Convert the frame's overflow rect into the coordinate space 1110 // of the nearest stacking context that has an existing display item. 1111 // We store that as a dirty rect on that stacking context so that we build 1112 // all items that intersect the changed frame within the stacking context, 1113 // and then we use MarkFrameForDisplayIfVisible to make sure the stacking 1114 // context itself gets built. We don't need to build items that intersect 1115 // outside of the stacking context, since we know the stacking context item 1116 // exists in the old list, so we can trivially merge without needing other 1117 // items. 1118 nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); 1119 1120 // If the modified frame is also a caret frame, include the caret area. 1121 // This is needed because some frames (for example text frames without text) 1122 // might have an empty overflow rect. 1123 if (aFrame == aBuilder->GetCaretFrame()) { 1124 overflow.UnionRect(overflow, aBuilder->GetCaretRect()); 1125 } 1126 1127 if (!ProcessFrameInternal(aFrame, aBuilder, &agrFrame, overflow, aStopAtFrame, 1128 aOutFramesWithProps, aStopAtStackingContext)) { 1129 return false; 1130 } 1131 1132 if (!overflow.IsEmpty()) { 1133 aOutDirty->UnionRect(*aOutDirty, overflow); 1134 CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, 1135 overflow.y, overflow.width, overflow.height); 1136 1137 // If we get changed frames from multiple AGRS, then just give up as it gets 1138 // really complex to track which items would need to be marked in 1139 // MarkFramesForDifferentAGR. 1140 if (!*aOutModifiedAGR) { 1141 CRR_LOG("Setting %p as root stacking context AGR\n", agrFrame); 1142 *aOutModifiedAGR = agrFrame; 1143 } else if (agrFrame && *aOutModifiedAGR != agrFrame) { 1144 CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); 1145 return false; 1146 } 1147 } 1148 return true; 1149 } 1150 1151 static void AddFramesForContainingBlock(nsIFrame* aBlock, 1152 const nsFrameList& aFrames, 1153 nsTArray<nsIFrame*>& aExtraFrames) { 1154 for (nsIFrame* f : aFrames) { 1155 if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) { 1156 CRR_LOG("Adding invalid OOF %p\n", f); 1157 aExtraFrames.AppendElement(f); 1158 } 1159 } 1160 } 1161 1162 // Placeholder descendants of aFrame don't contribute to aFrame's overflow area. 1163 // Find all the containing blocks that might own placeholders under us, walk 1164 // their OOF frames list, and manually invalidate any frames that are 1165 // descendants of a modified frame (us, or another frame we'll get to soon). 1166 // This is combined with the work required for MarkFrameForDisplayIfVisible, 1167 // so that we can avoid an extra ancestor walk, and we can reuse the flag 1168 // to detect when we've already visited an ancestor (and thus all further 1169 // ancestors must also be visited). 1170 static void FindContainingBlocks(nsIFrame* aFrame, 1171 nsTArray<nsIFrame*>& aExtraFrames) { 1172 for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { 1173 if (f->ForceDescendIntoIfVisible()) { 1174 return; 1175 } 1176 f->SetForceDescendIntoIfVisible(true); 1177 CRR_LOG("Considering OOFs for %p\n", f); 1178 1179 AddFramesForContainingBlock(f, f->GetChildList(FrameChildListID::Float), 1180 aExtraFrames); 1181 AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()), 1182 aExtraFrames); 1183 1184 // This condition must match the condition in 1185 // nsLayoutUtils::GetParentOrPlaceholderFor which is used by 1186 // nsLayoutUtils::GetDisplayListParent 1187 if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { 1188 nsIFrame* parent = f->GetParent(); 1189 if (parent && !parent->ForceDescendIntoIfVisible()) { 1190 // If the GetDisplayListParent call is going to walk to a placeholder, 1191 // in rare cases the placeholder might be contained in a different 1192 // continuation from the oof. So we have to make sure to mark the oofs 1193 // parent. In the common case this doesn't make us do any extra work, 1194 // just changes the order in which we visit the frames since walking 1195 // through placeholders will walk through the parent, and we stop when 1196 // we find a ForceDescendIntoIfVisible bit set. 1197 FindContainingBlocks(parent, aExtraFrames); 1198 } 1199 } 1200 } 1201 } 1202 1203 /** 1204 * Given a list of frames that has been modified, computes the region that we 1205 * need to do display list building for in order to build all modified display 1206 * items. 1207 * 1208 * When a modified frame is within a stacking context (with an existing display 1209 * item), then we only contribute to the build area within the stacking context, 1210 * as well as forcing display list building to descend to the stacking context. 1211 * We don't need to add build area outside of the stacking context (and force 1212 * items above/below the stacking context container item to be built), since 1213 * just matching the position of the stacking context container item is 1214 * sufficient to ensure correct ordering during merging. 1215 * 1216 * We need to rebuild all items that might intersect with the modified frame, 1217 * both now and during async changes on the compositor. We do this by rebuilding 1218 * the area covered by the changed frame, as well as rebuilding all items that 1219 * have a different (async) AGR to the changed frame. If we have changes to 1220 * multiple AGRs (within a stacking context), then we rebuild that stacking 1221 * context entirely. 1222 * 1223 * @param aModifiedFrames The list of modified frames. 1224 * @param aOutDirty The result region to use for display list building. 1225 * @param aOutModifiedAGR The modified AGR for the root stacking context. 1226 * @param aOutFramesWithProps The list of frames to which we attached partial 1227 * build data so that it can be cleaned up. 1228 * 1229 * @return true if we succesfully computed a partial rebuild region, false if a 1230 * full build is required. 1231 */ 1232 bool RetainedDisplayListBuilder::ComputeRebuildRegion( 1233 nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty, 1234 nsIFrame** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps) { 1235 CRR_LOG("Computing rebuild regions for %zu frames:\n", 1236 aModifiedFrames.Length()); 1237 nsTArray<nsIFrame*> extraFrames; 1238 for (nsIFrame* f : aModifiedFrames) { 1239 MOZ_ASSERT(f); 1240 1241 mBuilder.AddFrameMarkedForDisplayIfVisible(f); 1242 FindContainingBlocks(f, extraFrames); 1243 1244 if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, 1245 true, aOutDirty, aOutModifiedAGR)) { 1246 return false; 1247 } 1248 } 1249 1250 // Since we set modified to true on the extraFrames, add them to 1251 // aModifiedFrames so that it will get reverted. 1252 aModifiedFrames.AppendElements(extraFrames); 1253 1254 for (nsIFrame* f : extraFrames) { 1255 f->SetFrameIsModified(true); 1256 1257 if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, 1258 true, aOutDirty, aOutModifiedAGR)) { 1259 return false; 1260 } 1261 } 1262 1263 return true; 1264 } 1265 1266 bool RetainedDisplayListBuilder::ShouldBuildPartial( 1267 nsTArray<nsIFrame*>& aModifiedFrames) { 1268 // We don't support retaining with overlay scrollbars, since they require 1269 // us to look at the display list and pick the highest z-index, which 1270 // we can't do during partial building. 1271 if (mBuilder.DisablePartialUpdates()) { 1272 mBuilder.SetDisablePartialUpdates(false); 1273 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled; 1274 return false; 1275 } 1276 1277 if (mList.IsEmpty()) { 1278 // Partial builds without a previous display list do not make sense. 1279 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList; 1280 return false; 1281 } 1282 1283 if (aModifiedFrames.Length() > 1284 StaticPrefs::layout_display_list_rebuild_frame_limit()) { 1285 // Computing a dirty rect with too many modified frames can be slow. 1286 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit; 1287 return false; 1288 } 1289 1290 for (nsIFrame* f : aModifiedFrames) { 1291 MOZ_ASSERT(f); 1292 1293 const LayoutFrameType type = f->Type(); 1294 1295 // If we have any modified frames of the following types, it is likely that 1296 // doing a partial rebuild of the display list will be slower than doing a 1297 // full rebuild. 1298 // This is because these frames either intersect or may intersect with most 1299 // of the page content. This is either due to display port size or different 1300 // async AGR. 1301 if (type == LayoutFrameType::Viewport || 1302 type == LayoutFrameType::PageContent || 1303 type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) { 1304 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; 1305 return false; 1306 } 1307 1308 // Detect root scroll frame and do a full rebuild for them too for the same 1309 // reasons as above, but also because top layer items should to be marked 1310 // modified if the root scroll frame is modified. Putting this check here 1311 // means we don't need to check everytime a frame is marked modified though. 1312 if (type == LayoutFrameType::ScrollContainer && f->GetParent() && 1313 !f->GetParent()->GetParent()) { 1314 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; 1315 return false; 1316 } 1317 } 1318 1319 return true; 1320 } 1321 1322 class AutoClearFramePropsArray { 1323 public: 1324 explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {} 1325 AutoClearFramePropsArray() = default; 1326 ~AutoClearFramePropsArray() { 1327 size_t len = mFrames.Length(); 1328 nsIFrame** elements = mFrames.Elements(); 1329 for (size_t i = 0; i < len; ++i) { 1330 nsIFrame* f = elements[i]; 1331 DL_LOGV("RDL - Clearing modified flags for frame %p", f); 1332 if (f->HasOverrideDirtyRegion()) { 1333 f->SetHasOverrideDirtyRegion(false); 1334 f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect()); 1335 f->RemoveProperty( 1336 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); 1337 } 1338 f->SetFrameIsModified(false); 1339 f->SetHasModifiedDescendants(false); 1340 } 1341 } 1342 1343 nsTArray<nsIFrame*>& Frames() { return mFrames; } 1344 bool IsEmpty() const { return mFrames.IsEmpty(); } 1345 1346 private: 1347 nsTArray<nsIFrame*> mFrames; 1348 }; 1349 1350 void RetainedDisplayListBuilder::ClearFramesWithProps() { 1351 AutoClearFramePropsArray modifiedFrames(Data()->GetModifiedFrameCount()); 1352 AutoClearFramePropsArray framesWithProps; 1353 GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), 1354 &framesWithProps.Frames()); 1355 } 1356 1357 void RetainedDisplayListBuilder::ClearRetainedData() { 1358 DL_LOGI("(%p) RDL - Clearing retained display list builder data", this); 1359 List()->DeleteAll(Builder()); 1360 ClearFramesWithProps(); 1361 ClearReuseableDisplayItems(); 1362 } 1363 1364 namespace RDLUtils { 1365 1366 MOZ_NEVER_INLINE_DEBUG void AssertFrameSubtreeUnmodified( 1367 const nsIFrame* aFrame) { 1368 MOZ_ASSERT(!aFrame->IsFrameModified()); 1369 MOZ_ASSERT(!aFrame->HasModifiedDescendants()); 1370 1371 for (const auto& childList : aFrame->ChildLists()) { 1372 for (nsIFrame* child : childList.mList) { 1373 AssertFrameSubtreeUnmodified(child); 1374 } 1375 } 1376 } 1377 1378 MOZ_NEVER_INLINE_DEBUG void AssertDisplayListUnmodified(nsDisplayList* aList) { 1379 for (nsDisplayItem* item : *aList) { 1380 AssertDisplayItemUnmodified(item); 1381 } 1382 } 1383 1384 MOZ_NEVER_INLINE_DEBUG void AssertDisplayItemUnmodified(nsDisplayItem* aItem) { 1385 MOZ_ASSERT(!aItem->HasDeletedFrame()); 1386 MOZ_ASSERT(!AnyContentAncestorModified(aItem->FrameForInvalidation())); 1387 1388 if (aItem->GetChildren()) { 1389 AssertDisplayListUnmodified(aItem->GetChildren()); 1390 } 1391 } 1392 1393 } // namespace RDLUtils 1394 1395 namespace RDL { 1396 1397 void MarkAncestorFrames(nsIFrame* aFrame, 1398 nsTArray<nsIFrame*>& aOutFramesWithProps) { 1399 nsIFrame* frame = nsLayoutUtils::GetDisplayListParent(aFrame); 1400 while (frame && !frame->HasModifiedDescendants()) { 1401 aOutFramesWithProps.AppendElement(frame); 1402 frame->SetHasModifiedDescendants(true); 1403 frame = nsLayoutUtils::GetDisplayListParent(frame); 1404 } 1405 } 1406 1407 /** 1408 * Iterates over the modified frames array and updates the frame tree flags 1409 * so that container frames know whether they have modified descendant frames. 1410 * Frames that were marked modified are added to |aOutFramesWithProps|, so that 1411 * the modified status can be cleared after the display list build. 1412 */ 1413 void MarkAllAncestorFrames(const nsTArray<nsIFrame*>& aModifiedFrames, 1414 nsTArray<nsIFrame*>& aOutFramesWithProps) { 1415 nsAutoString frameName; 1416 DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length()); 1417 for (nsIFrame* frame : aModifiedFrames) { 1418 #ifdef DEBUG 1419 frame->GetFrameName(frameName); 1420 #endif 1421 DL_LOGV("RDL - Processing modified frame: %p (%s)", frame, 1422 NS_ConvertUTF16toUTF8(frameName).get()); 1423 1424 MarkAncestorFrames(frame, aOutFramesWithProps); 1425 } 1426 } 1427 1428 /** 1429 * Marks the given display item |aItem| as reuseable container, and updates the 1430 * bounds in case some child items were destroyed. 1431 */ 1432 MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem( 1433 nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { 1434 aItem->SetPreProcessed(); 1435 1436 if (aItem->HasChildren()) { 1437 aItem->UpdateBounds(aBuilder); 1438 } 1439 1440 aBuilder->AddReusableDisplayItem(aItem); 1441 DL_LOGD("Reusing display item %p", aItem); 1442 } 1443 1444 bool IsSupportedFrameType(const nsIFrame* aFrame) { 1445 // The way table backgrounds are handled makes these frames incompatible with 1446 // this retained display list approach. 1447 if (aFrame->IsTableColFrame()) { 1448 return false; 1449 } 1450 1451 if (aFrame->IsTableColGroupFrame()) { 1452 return false; 1453 } 1454 1455 if (aFrame->IsTableRowFrame()) { 1456 return false; 1457 } 1458 1459 if (aFrame->IsTableRowGroupFrame()) { 1460 return false; 1461 } 1462 1463 if (aFrame->IsTableCellFrame()) { 1464 return false; 1465 } 1466 1467 // Everything else should work. 1468 return true; 1469 } 1470 1471 bool IsReuseableStackingContextItem(nsDisplayItem* aItem) { 1472 if (!IsSupportedFrameType(aItem->Frame())) { 1473 return false; 1474 } 1475 1476 if (!aItem->IsReusable()) { 1477 return false; 1478 } 1479 1480 const nsIFrame* frame = aItem->FrameForInvalidation(); 1481 return !frame->HasModifiedDescendants() && !frame->GetPrevContinuation() && 1482 !frame->GetNextContinuation(); 1483 } 1484 1485 /** 1486 * Recursively visits every display item of the display list and destroys all 1487 * display items that depend on deleted or modified frames. 1488 * The stacking context display items for unmodified frame subtrees are kept 1489 * linked and collected in given |aOutItems| array. 1490 */ 1491 void CollectStackingContextItems(nsDisplayListBuilder* aBuilder, 1492 nsDisplayList* aList, nsIFrame* aOuterFrame, 1493 int aDepth = 0, bool aParentReused = false) { 1494 for (nsDisplayItem* item : aList->TakeItems()) { 1495 if (DL_LOG_TEST(LogLevel::Debug)) { 1496 DL_LOGD( 1497 "%*s Preprocessing item %p (%s) (frame: %p) " 1498 "(children: %zu) (depth: %d) (parentReused: %d)", 1499 aDepth, "", item, item->Name(), 1500 item->HasDeletedFrame() ? nullptr : item->Frame(), 1501 item->GetChildren() ? item->GetChildren()->Length() : 0, aDepth, 1502 aParentReused); 1503 } 1504 1505 if (!item->CanBeReused() || item->HasDeletedFrame() || 1506 AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { 1507 DL_LOGD("%*s Deleted modified or temporary item %p", aDepth, "", item); 1508 item->Destroy(aBuilder); 1509 continue; 1510 } 1511 1512 MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); 1513 MOZ_ASSERT(!item->IsPreProcessed()); 1514 item->InvalidateCachedChildInfo(aBuilder); 1515 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1516 item->SetMergedPreProcessed(false, true); 1517 #endif 1518 item->SetReused(true); 1519 1520 const bool isStackingContextItem = IsReuseableStackingContextItem(item); 1521 1522 if (item->GetChildren()) { 1523 CollectStackingContextItems(aBuilder, item->GetChildren(), item->Frame(), 1524 aDepth + 1, 1525 aParentReused || isStackingContextItem); 1526 } 1527 1528 if (aParentReused) { 1529 // Keep the contents of the current container item linked. 1530 #ifdef DEBUG 1531 RDLUtils::AssertDisplayItemUnmodified(item); 1532 #endif 1533 aList->AppendToTop(item); 1534 } else if (isStackingContextItem) { 1535 // |item| is a stacking context item that can be reused. 1536 ReuseStackingContextItem(aBuilder, item); 1537 } else { 1538 // |item| is inside a container item that will be destroyed later. 1539 DL_LOGD("%*s Deleted unused item %p", aDepth, "", item); 1540 item->Destroy(aBuilder); 1541 continue; 1542 } 1543 1544 if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { 1545 IncrementPresShellPaintCount(aBuilder, item); 1546 } 1547 } 1548 } 1549 1550 } // namespace RDL 1551 1552 bool RetainedDisplayListBuilder::TrySimpleUpdate( 1553 const nsTArray<nsIFrame*>& aModifiedFrames, 1554 nsTArray<nsIFrame*>& aOutFramesWithProps) { 1555 if (!mBuilder.IsReusingStackingContextItems()) { 1556 return false; 1557 } 1558 1559 RDL::MarkAllAncestorFrames(aModifiedFrames, aOutFramesWithProps); 1560 RDL::CollectStackingContextItems(&mBuilder, &mList, RootReferenceFrame()); 1561 1562 return true; 1563 } 1564 1565 PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate( 1566 nscolor aBackstop) { 1567 DL_LOGI("(%p) RDL - AttemptPartialUpdate, root frame: %p", this, 1568 RootReferenceFrame()); 1569 1570 mBuilder.RemoveModifiedWindowRegions(); 1571 1572 if (mBuilder.ShouldSyncDecodeImages()) { 1573 DL_LOGI("RDL - Sync decoding images"); 1574 MarkFramesWithItemsAndImagesModified(&mList); 1575 } 1576 1577 mBuilder.InvalidateCaretFramesIfNeeded(); 1578 1579 // We set the override dirty regions during ComputeRebuildRegion or in 1580 // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change 1581 // also marks the frame modified, so those regions are cleared here as well. 1582 AutoClearFramePropsArray modifiedFrames(Data()->GetModifiedFrameCount()); 1583 AutoClearFramePropsArray framesWithProps(64); 1584 GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), 1585 &framesWithProps.Frames()); 1586 1587 if (!ShouldBuildPartial(modifiedFrames.Frames())) { 1588 // Do not allow partial builds if the |ShouldBuildPartial()| heuristic 1589 // fails. 1590 mBuilder.SetPartialBuildFailed(true); 1591 return PartialUpdateResult::Failed; 1592 } 1593 1594 nsRect modifiedDirty; 1595 nsDisplayList modifiedDL(&mBuilder); 1596 nsIFrame* modifiedAGR = nullptr; 1597 PartialUpdateResult result = PartialUpdateResult::NoChange; 1598 const bool simpleUpdate = 1599 TrySimpleUpdate(modifiedFrames.Frames(), framesWithProps.Frames()); 1600 1601 mBuilder.EnterPresShell(RootReferenceFrame()); 1602 1603 if (!simpleUpdate) { 1604 if (!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, 1605 &modifiedAGR, framesWithProps.Frames()) || 1606 !PreProcessDisplayList(&mList, modifiedAGR, result, 1607 RootReferenceFrame(), nullptr)) { 1608 DL_LOGI("RDL - Partial update aborted"); 1609 mBuilder.SetPartialBuildFailed(true); 1610 mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); 1611 mList.DeleteAll(&mBuilder); 1612 return PartialUpdateResult::Failed; 1613 } 1614 } else { 1615 modifiedDirty = mBuilder.GetVisibleRect(); 1616 } 1617 1618 // This is normally handled by EnterPresShell, but we skipped it so that we 1619 // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion. 1620 ScrollContainerFrame* sf = 1621 RootReferenceFrame()->PresShell()->GetRootScrollContainerFrame(); 1622 if (sf) { 1623 nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); 1624 if (canvasFrame) { 1625 mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame()); 1626 } 1627 } 1628 1629 nsRect rootOverflow = RootOverflowRect(); 1630 modifiedDirty.IntersectRect(modifiedDirty, rootOverflow); 1631 1632 mBuilder.SetDirtyRect(modifiedDirty); 1633 mBuilder.SetPartialUpdate(true); 1634 mBuilder.SetPartialBuildFailed(false); 1635 1636 DL_LOGI("RDL - Starting display list build"); 1637 RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, 1638 &modifiedDL); 1639 DL_LOGI("RDL - Finished display list build"); 1640 1641 if (!modifiedDL.IsEmpty()) { 1642 nsLayoutUtils::AddExtraBackgroundItems( 1643 &mBuilder, &modifiedDL, RootReferenceFrame(), 1644 nsRect(nsPoint(0, 0), rootOverflow.Size()), rootOverflow, aBackstop); 1645 } 1646 mBuilder.SetPartialUpdate(false); 1647 1648 if (mBuilder.PartialBuildFailed()) { 1649 DL_LOGI("RDL - Partial update failed!"); 1650 mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); 1651 mBuilder.ClearReuseableDisplayItems(); 1652 mList.DeleteAll(&mBuilder); 1653 modifiedDL.DeleteAll(&mBuilder); 1654 Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content; 1655 return PartialUpdateResult::Failed; 1656 } 1657 1658 // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", 1659 // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, 1660 // modifiedDirty.height); 1661 // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL); 1662 1663 // |modifiedDL| can sometimes be empty here. We still perform the 1664 // display list merging to prune unused items (for example, items that 1665 // are not visible anymore) from the old list. 1666 // TODO: Optimization opportunity. In this case, MergeDisplayLists() 1667 // unnecessarily creates a hashtable of the old items. 1668 // TODO: Ideally we could skip this if result is NoChange, but currently when 1669 // we call RestoreState on nsDisplayWrapList it resets the clip to the base 1670 // clip, and we need the UpdateBounds call (within MergeDisplayLists) to 1671 // move it to the correct inner clip. 1672 if (!simpleUpdate) { 1673 Maybe<const ActiveScrolledRoot*> dummy; 1674 if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { 1675 result = PartialUpdateResult::Updated; 1676 } 1677 } else { 1678 MOZ_ASSERT(mList.IsEmpty()); 1679 mList = std::move(modifiedDL); 1680 mBuilder.ClearReuseableDisplayItems(); 1681 result = PartialUpdateResult::Updated; 1682 } 1683 1684 #if 0 1685 if (DL_LOG_TEST(LogLevel::Verbose)) { 1686 printf_stderr("Painting --- Display list:\n"); 1687 nsIFrame::PrintDisplayList(&mBuilder, mList); 1688 } 1689 #endif 1690 1691 mBuilder.LeavePresShell(RootReferenceFrame(), List()); 1692 return result; 1693 } 1694 1695 nsRect RetainedDisplayListBuilder::RootOverflowRect() const { 1696 const nsIFrame* rootReferenceFrame = RootReferenceFrame(); 1697 nsRect rootOverflowRect = rootReferenceFrame->InkOverflowRectRelativeToSelf(); 1698 const nsPresContext* presContext = rootReferenceFrame->PresContext(); 1699 if (!rootReferenceFrame->GetParent() && 1700 presContext->IsRootContentDocumentCrossProcess() && 1701 presContext->HasDynamicToolbar()) { 1702 rootOverflowRect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( 1703 presContext, rootOverflowRect.Size())); 1704 } 1705 1706 return rootOverflowRect; 1707 } 1708 1709 } // namespace mozilla