/src/mozilla-central/layout/painting/RetainedDisplayListBuilder.cpp
Line | Count | Source (jump to first uncovered line) |
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 "DisplayListChecker.h" |
11 | | #include "gfxPrefs.h" |
12 | | #include "nsPlaceholderFrame.h" |
13 | | #include "nsSubDocumentFrame.h" |
14 | | #include "nsViewManager.h" |
15 | | #include "nsCanvasFrame.h" |
16 | | |
17 | | /** |
18 | | * Code for doing display list building for a modified subset of the window, |
19 | | * and then merging it into the existing display list (for the full window). |
20 | | * |
21 | | * The approach primarily hinges on the observation that the ‘true’ ordering of |
22 | | * display items is represented by a DAG (only items that intersect in 2d space |
23 | | * have a defined ordering). Our display list is just one of a many possible |
24 | | * linear representations of this ordering. |
25 | | * |
26 | | * Each time a frame changes (gets a new ComputedStyle, or has a size/position |
27 | | * change), we schedule a paint (as we do currently), but also reord the frame |
28 | | * that changed. |
29 | | * |
30 | | * When the next paint occurs we union the overflow areas (in screen space) of |
31 | | * the changed frames, and compute a rect/region that contains all changed |
32 | | * items. We then build a display list just for this subset of the screen and |
33 | | * merge it into the display list from last paint. |
34 | | * |
35 | | * Any items that exist in one list and not the other must not have a defined |
36 | | * ordering in the DAG, since they need to intersect to have an ordering and |
37 | | * we would have built both in the new list if they intersected. Given that, we |
38 | | * can align items that appear in both lists, and any items that appear between |
39 | | * matched items can be inserted into the merged list in any order. |
40 | | */ |
41 | | |
42 | | using namespace mozilla; |
43 | | |
44 | | RetainedDisplayListData* |
45 | | GetRetainedDisplayListData(nsIFrame* aRootFrame) |
46 | 0 | { |
47 | 0 | RetainedDisplayListData* data = |
48 | 0 | aRootFrame->GetProperty(RetainedDisplayListData::DisplayListData()); |
49 | 0 |
|
50 | 0 | return data; |
51 | 0 | } |
52 | | |
53 | | RetainedDisplayListData* |
54 | | GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame) |
55 | 0 | { |
56 | 0 | RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame); |
57 | 0 |
|
58 | 0 | if (!data) { |
59 | 0 | data = new RetainedDisplayListData(); |
60 | 0 | aRootFrame->SetProperty(RetainedDisplayListData::DisplayListData(), data); |
61 | 0 | } |
62 | 0 |
|
63 | 0 | MOZ_ASSERT(data); |
64 | 0 | return data; |
65 | 0 | } |
66 | | |
67 | | static void |
68 | | MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) |
69 | 0 | { |
70 | 0 | for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) { |
71 | 0 | if (!i->HasDeletedFrame() && i->CanBeReused() && |
72 | 0 | !i->Frame()->IsFrameModified()) { |
73 | 0 | // If we have existing cached geometry for this item, then check that for |
74 | 0 | // whether we need to invalidate for a sync decode. If we don't, then |
75 | 0 | // use the item's flags. |
76 | 0 | DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i); |
77 | 0 | // XXX: handle webrender case |
78 | 0 | bool invalidate = false; |
79 | 0 | if (data && data->GetGeometry()) { |
80 | 0 | invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages(); |
81 | 0 | } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { |
82 | 0 | invalidate = true; |
83 | 0 | } |
84 | 0 |
|
85 | 0 | if (invalidate) { |
86 | 0 | i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); |
87 | 0 | if (i->GetDependentFrame()) { |
88 | 0 | i->GetDependentFrame()->MarkNeedsDisplayItemRebuild(); |
89 | 0 | } |
90 | 0 | } |
91 | 0 | } |
92 | 0 | if (i->GetChildren()) { |
93 | 0 | MarkFramesWithItemsAndImagesModified(i->GetChildren()); |
94 | 0 | } |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | static AnimatedGeometryRoot* |
99 | | SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR) |
100 | 0 | { |
101 | 0 | if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) { |
102 | 0 | return aParentAGR; |
103 | 0 | } |
104 | 0 | |
105 | 0 | if (!aFrame->HasOverrideDirtyRegion()) { |
106 | 0 | return nullptr; |
107 | 0 | } |
108 | 0 | |
109 | 0 | nsDisplayListBuilder::DisplayListBuildingData* data = |
110 | 0 | aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); |
111 | 0 |
|
112 | 0 | return data && data->mModifiedAGR ? data->mModifiedAGR.get() : nullptr; |
113 | 0 | } |
114 | | |
115 | | // Removes any display items that belonged to a frame that was deleted, |
116 | | // and mark frames that belong to a different AGR so that get their |
117 | | // items built again. |
118 | | // TODO: We currently descend into all children even if we don't have an AGR |
119 | | // to mark, as child stacking contexts might. It would be nice if we could |
120 | | // jump into those immediately rather than walking the entire thing. |
121 | | bool |
122 | | RetainedDisplayListBuilder::PreProcessDisplayList(RetainedDisplayList* aList, |
123 | | AnimatedGeometryRoot* aAGR, |
124 | | uint32_t aCallerKey, |
125 | | uint32_t aNestingDepth) |
126 | 0 | { |
127 | 0 | // The DAG merging algorithm does not have strong mechanisms in place to keep |
128 | 0 | // the complexity of the resulting DAG under control. In some cases we can |
129 | 0 | // build up edges very quickly. Detect those cases and force a full display |
130 | 0 | // list build if we hit them. |
131 | 0 | static const uint32_t kMaxEdgeRatio = 5; |
132 | 0 | bool initializeDAG = !aList->mDAG.Length(); |
133 | 0 | if (!initializeDAG && aList->mDAG.mDirectPredecessorList.Length() > |
134 | 0 | (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { |
135 | 0 | return false; |
136 | 0 | } |
137 | 0 | |
138 | 0 | MOZ_RELEASE_ASSERT(initializeDAG || aList->mDAG.Length() == aList->Count()); |
139 | 0 |
|
140 | 0 | nsDisplayList saved; |
141 | 0 | aList->mOldItems.SetCapacity(aList->Count()); |
142 | 0 | MOZ_RELEASE_ASSERT(aList->mOldItems.IsEmpty()); |
143 | 0 | while (nsDisplayItem* item = aList->RemoveBottom()) { |
144 | 0 | #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED |
145 | 0 | item->mMergedItem = false; |
146 | 0 | item->mPreProcessedItem = true; |
147 | 0 | #endif |
148 | 0 |
|
149 | 0 | if (item->HasDeletedFrame() || !item->CanBeReused()) { |
150 | 0 | size_t i = aList->mOldItems.Length(); |
151 | 0 | aList->mOldItems.AppendElement(OldItemInfo(nullptr)); |
152 | 0 | item->Destroy(&mBuilder); |
153 | 0 |
|
154 | 0 | if (initializeDAG) { |
155 | 0 | if (i == 0) { |
156 | 0 | aList->mDAG.AddNode(Span<const MergedListIndex>()); |
157 | 0 | } else { |
158 | 0 | MergedListIndex previous(i - 1); |
159 | 0 | aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1)); |
160 | 0 | } |
161 | 0 | } |
162 | 0 | continue; |
163 | 0 | } |
164 | 0 |
|
165 | 0 | size_t i = aList->mOldItems.Length(); |
166 | 0 | aList->mOldItems.AppendElement(OldItemInfo(item)); |
167 | 0 | item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth); |
168 | 0 | if (initializeDAG) { |
169 | 0 | if (i == 0) { |
170 | 0 | aList->mDAG.AddNode(Span<const MergedListIndex>()); |
171 | 0 | } else { |
172 | 0 | MergedListIndex previous(i - 1); |
173 | 0 | aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1)); |
174 | 0 | } |
175 | 0 | } |
176 | 0 |
|
177 | 0 | nsIFrame* f = item->Frame(); |
178 | 0 |
|
179 | 0 | if (item->GetChildren()) { |
180 | 0 | if (!PreProcessDisplayList(item->GetChildren(), |
181 | 0 | SelectAGRForFrame(f, aAGR), |
182 | 0 | item->GetPerFrameKey(), |
183 | 0 | aNestingDepth + 1)) { |
184 | 0 | return false; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | |
188 | 0 | // TODO: We should be able to check the clipped bounds relative |
189 | 0 | // to the common AGR (of both the existing item and the invalidated |
190 | 0 | // frame) and determine if they can ever intersect. |
191 | 0 | if (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { |
192 | 0 | mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame()); |
193 | 0 | } |
194 | 0 |
|
195 | 0 | // TODO: This is here because we sometimes reuse the previous display list |
196 | 0 | // completely. For optimization, we could only restore the state for reused |
197 | 0 | // display items. |
198 | 0 | item->RestoreState(); |
199 | 0 | } |
200 | 0 | MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length()); |
201 | 0 | aList->RestoreState(); |
202 | 0 | return true; |
203 | 0 | } |
204 | | |
205 | | void |
206 | | RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount( |
207 | | nsDisplayItem* aItem) |
208 | 0 | { |
209 | 0 | MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); |
210 | 0 |
|
211 | 0 | nsSubDocumentFrame* subDocFrame = |
212 | 0 | static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame(); |
213 | 0 | MOZ_ASSERT(subDocFrame); |
214 | 0 |
|
215 | 0 | nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); |
216 | 0 | MOZ_ASSERT(presShell); |
217 | 0 |
|
218 | 0 | mBuilder.IncrementPresShellPaintCount(presShell); |
219 | 0 | } |
220 | | |
221 | | static bool |
222 | | AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame = nullptr) |
223 | 0 | { |
224 | 0 | for (nsIFrame* f = aFrame; f; |
225 | 0 | f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { |
226 | 0 | if (f->IsFrameModified()) { |
227 | 0 | return true; |
228 | 0 | } |
229 | 0 | |
230 | 0 | if (aStopAtFrame && f == aStopAtFrame) { |
231 | 0 | break; |
232 | 0 | } |
233 | 0 | } |
234 | 0 |
|
235 | 0 | return false; |
236 | 0 | } |
237 | | |
238 | | static void |
239 | | UpdateASR(nsDisplayItem* aItem, Maybe<const ActiveScrolledRoot*>& aContainerASR) |
240 | 0 | { |
241 | 0 | if (!aContainerASR) { |
242 | 0 | return; |
243 | 0 | } |
244 | 0 | |
245 | 0 | nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList(); |
246 | 0 | if (!wrapList) { |
247 | 0 | aItem->SetActiveScrolledRoot(aContainerASR.value()); |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | |
251 | 0 | wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor( |
252 | 0 | wrapList->GetFrameActiveScrolledRoot(), aContainerASR.value())); |
253 | 0 | } |
254 | | |
255 | | void |
256 | | OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, |
257 | | MergedListIndex aIndex) |
258 | 0 | { |
259 | 0 | AddedToMergedList(aIndex); |
260 | 0 | } |
261 | | |
262 | | void |
263 | | OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, |
264 | | nsTArray<MergedListIndex>&& aDirectPredecessors) |
265 | 0 | { |
266 | 0 | MOZ_ASSERT(!IsUsed()); |
267 | 0 | mUsed = mDiscarded = true; |
268 | 0 | mDirectPredecessors = std::move(aDirectPredecessors); |
269 | 0 | if (mItem) { |
270 | 0 | mItem->Destroy(aBuilder->Builder()); |
271 | 0 | } |
272 | 0 | mItem = nullptr; |
273 | 0 | } |
274 | | |
275 | | bool |
276 | | OldItemInfo::IsChanged() |
277 | 0 | { |
278 | 0 | return !mItem || mItem->HasDeletedFrame() || !mItem->CanBeReused(); |
279 | 0 | } |
280 | | |
281 | | /** |
282 | | * A C++ implementation of Markus Stange's merge-dags algorithm. |
283 | | * https://github.com/mstange/merge-dags |
284 | | * |
285 | | * MergeState handles combining a new list of display items into an existing |
286 | | * DAG and computes the new DAG in a single pass. |
287 | | * Each time we add a new item, we resolve all dependencies for it, so that the |
288 | | * resulting list and DAG are built in topological ordering. |
289 | | */ |
290 | | class MergeState |
291 | | { |
292 | | public: |
293 | | MergeState(RetainedDisplayListBuilder* aBuilder, |
294 | | RetainedDisplayList& aOldList, |
295 | | uint32_t aOuterKey) |
296 | | : mBuilder(aBuilder) |
297 | | , mOldList(&aOldList) |
298 | | , mOldItems(std::move(aOldList.mOldItems)) |
299 | | , mOldDAG(std::move( |
300 | | *reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(&aOldList.mDAG))) |
301 | | , mOuterKey(aOuterKey) |
302 | | , mResultIsModified(false) |
303 | 0 | { |
304 | 0 | mMergedDAG.EnsureCapacityFor(mOldDAG); |
305 | 0 | MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length()); |
306 | 0 | } |
307 | | |
308 | | MergedListIndex ProcessItemFromNewList( |
309 | | nsDisplayItem* aNewItem, |
310 | | const Maybe<MergedListIndex>& aPreviousItem) |
311 | 0 | { |
312 | 0 | OldListIndex oldIndex; |
313 | 0 | if (!HasModifiedFrame(aNewItem) && |
314 | 0 | HasMatchingItemInOldList(aNewItem, &oldIndex)) { |
315 | 0 | nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem; |
316 | 0 | MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() == |
317 | 0 | aNewItem->GetPerFrameKey() && |
318 | 0 | oldItem->Frame() == aNewItem->Frame()); |
319 | 0 | if (!mOldItems[oldIndex.val].IsChanged()) { |
320 | 0 | MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed()); |
321 | 0 | nsDisplayItem* destItem; |
322 | 0 | if (ShouldUseNewItem(aNewItem)) { |
323 | 0 | destItem = aNewItem; |
324 | 0 | } else { |
325 | 0 | destItem = oldItem; |
326 | 0 | // The building rect can depend on the overflow rect (when the parent |
327 | 0 | // frame is position:fixed), which can change without invalidating |
328 | 0 | // the frame/items. If we're using the old item, copy the building |
329 | 0 | // rect across from the new item. |
330 | 0 | oldItem->SetBuildingRect(aNewItem->GetBuildingRect()); |
331 | 0 | } |
332 | 0 |
|
333 | 0 | if (aNewItem->GetChildren()) { |
334 | 0 | Maybe<const ActiveScrolledRoot*> containerASRForChildren; |
335 | 0 | if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(), |
336 | 0 | oldItem->GetChildren(), |
337 | 0 | destItem->GetChildren(), |
338 | 0 | containerASRForChildren, |
339 | 0 | aNewItem->GetPerFrameKey())) { |
340 | 0 | destItem->InvalidateCachedChildInfo(); |
341 | 0 | mResultIsModified = true; |
342 | 0 | } |
343 | 0 | UpdateASR(destItem, containerASRForChildren); |
344 | 0 | destItem->UpdateBounds(mBuilder->Builder()); |
345 | 0 | } |
346 | 0 |
|
347 | 0 | AutoTArray<MergedListIndex, 2> directPredecessors = |
348 | 0 | ProcessPredecessorsOfOldNode(oldIndex); |
349 | 0 | MergedListIndex newIndex = AddNewNode( |
350 | 0 | destItem, Some(oldIndex), directPredecessors, aPreviousItem); |
351 | 0 | mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex); |
352 | 0 | if (destItem == aNewItem) { |
353 | 0 | oldItem->Destroy(mBuilder->Builder()); |
354 | 0 | } else { |
355 | 0 | aNewItem->Destroy(mBuilder->Builder()); |
356 | 0 | } |
357 | 0 | return newIndex; |
358 | 0 | } |
359 | 0 | } |
360 | 0 | mResultIsModified = true; |
361 | 0 | return AddNewNode( |
362 | 0 | aNewItem, Nothing(), Span<MergedListIndex>(), aPreviousItem); |
363 | 0 | } |
364 | | |
365 | | bool ShouldUseNewItem(nsDisplayItem* aNewItem) |
366 | 0 | { |
367 | 0 | // Generally we want to use the old item when the frame isn't marked as |
368 | 0 | // modified so that any cached information on the item (or referencing the |
369 | 0 | // item) gets retained. Quite a few FrameLayerBuilder performance |
370 | 0 | // improvements benefit by this. Sometimes, however, we can end up where the |
371 | 0 | // new item paints something different from the old item, even though we |
372 | 0 | // haven't modified the frame, and it's hard to fix. In these cases we just |
373 | 0 | // always use the new item to be safe. |
374 | 0 | DisplayItemType type = aNewItem->GetType(); |
375 | 0 | if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR || |
376 | 0 | type == DisplayItemType::TYPE_SOLID_COLOR) { |
377 | 0 | // The canvas background color item can paint the color from another |
378 | 0 | // frame, and even though we schedule a paint, we don't mark the canvas |
379 | 0 | // frame as invalid. |
380 | 0 | return true; |
381 | 0 | } |
382 | 0 | |
383 | 0 | if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { |
384 | 0 | // We intentionally don't mark the root table frame as modified when a |
385 | 0 | // subframe changes, even though the border collapse item for the root |
386 | 0 | // frame is what paints the changed border. Marking the root frame as |
387 | 0 | // modified would rebuild display items for the whole table area, and we |
388 | 0 | // don't want that. |
389 | 0 | return true; |
390 | 0 | } |
391 | 0 | |
392 | 0 | if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { |
393 | 0 | // Text overflow marker items are created with the wrapping block as their |
394 | 0 | // frame, and have an index value to note which line they are created for. |
395 | 0 | // Their rendering can change if the items on that line change, which may |
396 | 0 | // not mark the block as modified. We rebuild them if we build any item on |
397 | 0 | // the line, so we should always get new items if they might have changed |
398 | 0 | // rendering, and it's easier to just use the new items rather than |
399 | 0 | // computing if we actually need them. |
400 | 0 | return true; |
401 | 0 | } |
402 | 0 | |
403 | 0 | if (type == DisplayItemType::TYPE_SUBDOCUMENT) { |
404 | 0 | // nsDisplaySubDocument::mShouldFlatten can change without an invalidation |
405 | 0 | // (and is the reason we unconditionally build the subdocument item), so |
406 | 0 | // always use the new one to make sure we get the right value. |
407 | 0 | return true; |
408 | 0 | } |
409 | 0 | |
410 | 0 | if (type == DisplayItemType::TYPE_CARET) { |
411 | 0 | // The caret can change position while still being owned by the same frame |
412 | 0 | // and we don't invalidate in that case. Use the new version since the |
413 | 0 | // changed bounds are needed for DLBI. |
414 | 0 | return true; |
415 | 0 | } |
416 | 0 | |
417 | 0 | return false; |
418 | 0 | } |
419 | | |
420 | | RetainedDisplayList Finalize() |
421 | 0 | { |
422 | 0 | for (size_t i = 0; i < mOldDAG.Length(); i++) { |
423 | 0 | if (mOldItems[i].IsUsed()) { |
424 | 0 | continue; |
425 | 0 | } |
426 | 0 | |
427 | 0 | AutoTArray<MergedListIndex, 2> directPredecessors = |
428 | 0 | ResolveNodeIndexesOldToMerged( |
429 | 0 | mOldDAG.GetDirectPredecessors(OldListIndex(i))); |
430 | 0 | ProcessOldNode(OldListIndex(i), std::move(directPredecessors)); |
431 | 0 | } |
432 | 0 |
|
433 | 0 | RetainedDisplayList result; |
434 | 0 | result.AppendToTop(&mMergedItems); |
435 | 0 | result.mDAG = std::move(mMergedDAG); |
436 | 0 | MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Count()); |
437 | 0 | return result; |
438 | 0 | } |
439 | | |
440 | | bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) |
441 | 0 | { |
442 | 0 | nsIFrame::DisplayItemArray* items = |
443 | 0 | aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); |
444 | 0 | // Look for an item that matches aItem's frame and per-frame-key, but isn't |
445 | 0 | // the same item. |
446 | 0 | for (nsDisplayItem* i : *items) { |
447 | 0 | if (i != aItem && i->Frame() == aItem->Frame() && |
448 | 0 | i->GetPerFrameKey() == aItem->GetPerFrameKey()) { |
449 | 0 | if (i->GetOldListIndex(mOldList, mOuterKey, aOutIndex)) { |
450 | 0 | return true; |
451 | 0 | } |
452 | 0 | } |
453 | 0 | } |
454 | 0 | return false; |
455 | 0 | } |
456 | | |
457 | | bool HasModifiedFrame(nsDisplayItem* aItem) |
458 | 0 | { |
459 | 0 | return AnyContentAncestorModified(aItem->FrameForInvalidation()); |
460 | 0 | } |
461 | | |
462 | | void UpdateContainerASR(nsDisplayItem* aItem) |
463 | 0 | { |
464 | 0 | const ActiveScrolledRoot* itemClipASR = |
465 | 0 | aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr; |
466 | 0 |
|
467 | 0 | const ActiveScrolledRoot* finiteBoundsASR = |
468 | 0 | ActiveScrolledRoot::PickDescendant(itemClipASR, |
469 | 0 | aItem->GetActiveScrolledRoot()); |
470 | 0 | if (!mContainerASR) { |
471 | 0 | mContainerASR = Some(finiteBoundsASR); |
472 | 0 | } else { |
473 | 0 | mContainerASR = Some(ActiveScrolledRoot::PickAncestor( |
474 | 0 | mContainerASR.value(), finiteBoundsASR)); |
475 | 0 | } |
476 | 0 | } |
477 | | |
478 | | MergedListIndex AddNewNode( |
479 | | nsDisplayItem* aItem, |
480 | | const Maybe<OldListIndex>& aOldIndex, |
481 | | Span<const MergedListIndex> aDirectPredecessors, |
482 | | const Maybe<MergedListIndex>& aExtraDirectPredecessor) |
483 | 0 | { |
484 | 0 | UpdateContainerASR(aItem); |
485 | 0 |
|
486 | 0 | #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED |
487 | 0 | nsIFrame::DisplayItemArray* items = |
488 | 0 | aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); |
489 | 0 | for (nsDisplayItem* i : *items) { |
490 | 0 | if (i->Frame() == aItem->Frame() && |
491 | 0 | i->GetPerFrameKey() == aItem->GetPerFrameKey()) { |
492 | 0 | MOZ_DIAGNOSTIC_ASSERT(!i->mMergedItem); |
493 | 0 | } |
494 | 0 | } |
495 | 0 |
|
496 | 0 | aItem->mMergedItem = true; |
497 | 0 | aItem->mPreProcessedItem = false; |
498 | 0 | #endif |
499 | 0 |
|
500 | 0 | mMergedItems.AppendToTop(aItem); |
501 | 0 | MergedListIndex newIndex = |
502 | 0 | mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); |
503 | 0 | return newIndex; |
504 | 0 | } |
505 | | |
506 | | void ProcessOldNode(OldListIndex aNode, |
507 | | nsTArray<MergedListIndex>&& aDirectPredecessors) |
508 | 0 | { |
509 | 0 | nsDisplayItem* item = mOldItems[aNode.val].mItem; |
510 | 0 | if (mOldItems[aNode.val].IsChanged() || HasModifiedFrame(item)) { |
511 | 0 | mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors)); |
512 | 0 | mResultIsModified = true; |
513 | 0 | } else { |
514 | 0 | if (item->GetChildren()) { |
515 | 0 | Maybe<const ActiveScrolledRoot*> containerASRForChildren; |
516 | 0 | nsDisplayList empty; |
517 | 0 | if (mBuilder->MergeDisplayLists(&empty, |
518 | 0 | item->GetChildren(), |
519 | 0 | item->GetChildren(), |
520 | 0 | containerASRForChildren, |
521 | 0 | item->GetPerFrameKey())) { |
522 | 0 | item->InvalidateCachedChildInfo(); |
523 | 0 | mResultIsModified = true; |
524 | 0 | } |
525 | 0 | UpdateASR(item, containerASRForChildren); |
526 | 0 | item->UpdateBounds(mBuilder->Builder()); |
527 | 0 | } |
528 | 0 | if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { |
529 | 0 | mBuilder->IncrementSubDocPresShellPaintCount(item); |
530 | 0 | } |
531 | 0 | item->SetReused(true); |
532 | 0 | mOldItems[aNode.val].AddedToMergedList( |
533 | 0 | AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing())); |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | struct PredecessorStackItem |
538 | | { |
539 | | PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors) |
540 | | : mNode(aNode) |
541 | | , mDirectPredecessors(aPredecessors) |
542 | | , mCurrentPredecessorIndex(0) |
543 | 0 | { |
544 | 0 | } |
545 | | |
546 | | bool IsFinished() |
547 | 0 | { |
548 | 0 | return mCurrentPredecessorIndex == mDirectPredecessors.Length(); |
549 | 0 | } |
550 | | |
551 | | OldListIndex GetAndIncrementCurrentPredecessor() |
552 | 0 | { |
553 | 0 | return mDirectPredecessors[mCurrentPredecessorIndex++]; |
554 | 0 | } |
555 | | |
556 | | OldListIndex mNode; |
557 | | Span<OldListIndex> mDirectPredecessors; |
558 | | size_t mCurrentPredecessorIndex; |
559 | | }; |
560 | | |
561 | | AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode( |
562 | | OldListIndex aNode) |
563 | 0 | { |
564 | 0 | AutoTArray<PredecessorStackItem, 256> mStack; |
565 | 0 | mStack.AppendElement( |
566 | 0 | PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode))); |
567 | 0 |
|
568 | 0 | while (true) { |
569 | 0 | if (mStack.LastElement().IsFinished()) { |
570 | 0 | // If we've finished processing all the entries in the current set, then |
571 | 0 | // pop it off the processing stack and process it. |
572 | 0 | PredecessorStackItem item = mStack.PopLastElement(); |
573 | 0 | AutoTArray<MergedListIndex, 2> result = |
574 | 0 | ResolveNodeIndexesOldToMerged(item.mDirectPredecessors); |
575 | 0 |
|
576 | 0 | if (mStack.IsEmpty()) { |
577 | 0 | return result; |
578 | 0 | } |
579 | 0 | |
580 | 0 | ProcessOldNode(item.mNode, std::move(result)); |
581 | 0 | } else { |
582 | 0 | // Grab the current predecessor, push predecessors of that onto the |
583 | 0 | // processing stack (if it hasn't already been processed), and then |
584 | 0 | // advance to the next entry. |
585 | 0 | OldListIndex currentIndex = |
586 | 0 | mStack.LastElement().GetAndIncrementCurrentPredecessor(); |
587 | 0 | if (!mOldItems[currentIndex.val].IsUsed()) { |
588 | 0 | mStack.AppendElement(PredecessorStackItem( |
589 | 0 | currentIndex, mOldDAG.GetDirectPredecessors(currentIndex))); |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | 0 | } |
594 | | |
595 | | AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged( |
596 | | Span<OldListIndex> aDirectPredecessors) |
597 | 0 | { |
598 | 0 | AutoTArray<MergedListIndex, 2> result; |
599 | 0 | result.SetCapacity(aDirectPredecessors.Length()); |
600 | 0 | for (OldListIndex index : aDirectPredecessors) { |
601 | 0 | OldItemInfo& oldItem = mOldItems[index.val]; |
602 | 0 | if (oldItem.IsDiscarded()) { |
603 | 0 | for (MergedListIndex inner : oldItem.mDirectPredecessors) { |
604 | 0 | if (!result.Contains(inner)) { |
605 | 0 | result.AppendElement(inner); |
606 | 0 | } |
607 | 0 | } |
608 | 0 | } else { |
609 | 0 | result.AppendElement(oldItem.mIndex); |
610 | 0 | } |
611 | 0 | } |
612 | 0 | return result; |
613 | 0 | } |
614 | | |
615 | | RetainedDisplayListBuilder* mBuilder; |
616 | | RetainedDisplayList* mOldList; |
617 | | Maybe<const ActiveScrolledRoot*> mContainerASR; |
618 | | nsTArray<OldItemInfo> mOldItems; |
619 | | DirectedAcyclicGraph<OldListUnits> mOldDAG; |
620 | | // Unfortunately we can't use strong typing for the hashtables |
621 | | // since they internally encode the type with the mOps pointer, |
622 | | // and assert when we try swap the contents |
623 | | nsDisplayList mMergedItems; |
624 | | DirectedAcyclicGraph<MergedListUnits> mMergedDAG; |
625 | | uint32_t mOuterKey; |
626 | | bool mResultIsModified; |
627 | | }; |
628 | | |
629 | | /** |
630 | | * Takes two display lists and merges them into an output list. |
631 | | * |
632 | | * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a |
633 | | * maximum of one direct predecessor and one direct successor per node). We add |
634 | | * the two DAGs together, and then output the topological sorted ordering as the |
635 | | * final display list. |
636 | | * |
637 | | * Once we've merged a list, we then retain the DAG (as part of the |
638 | | * RetainedDisplayList object) to use for future merges. |
639 | | */ |
640 | | bool |
641 | | RetainedDisplayListBuilder::MergeDisplayLists( |
642 | | nsDisplayList* aNewList, |
643 | | RetainedDisplayList* aOldList, |
644 | | RetainedDisplayList* aOutList, |
645 | | mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, |
646 | | uint32_t aOuterKey) |
647 | 0 | { |
648 | 0 | MergeState merge(this, *aOldList, aOuterKey); |
649 | 0 |
|
650 | 0 | Maybe<MergedListIndex> previousItemIndex; |
651 | 0 | while (nsDisplayItem* item = aNewList->RemoveBottom()) { |
652 | 0 | previousItemIndex = |
653 | 0 | Some(merge.ProcessItemFromNewList(item, previousItemIndex)); |
654 | 0 | } |
655 | 0 |
|
656 | 0 | *aOutList = merge.Finalize(); |
657 | 0 | aOutContainerASR = merge.mContainerASR; |
658 | 0 | return merge.mResultIsModified; |
659 | 0 | } |
660 | | |
661 | | static void |
662 | | TakeAndAddModifiedAndFramesWithPropsFromRootFrame( |
663 | | nsDisplayListBuilder* aBuilder, |
664 | | nsTArray<nsIFrame*>* aModifiedFrames, |
665 | | nsTArray<nsIFrame*>* aFramesWithProps, |
666 | | nsIFrame* aRootFrame) |
667 | 0 | { |
668 | 0 | MOZ_ASSERT(aRootFrame); |
669 | 0 |
|
670 | 0 | RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame); |
671 | 0 |
|
672 | 0 | if (!data) { |
673 | 0 | return; |
674 | 0 | } |
675 | 0 | |
676 | 0 | for (auto it = data->Iterator(); !it.Done(); it.Next()) { |
677 | 0 | nsIFrame* frame = it.Key(); |
678 | 0 | const RetainedDisplayListData::FrameFlags& flags = it.Data(); |
679 | 0 |
|
680 | 0 | if (flags & RetainedDisplayListData::FrameFlags::Modified) { |
681 | 0 | aModifiedFrames->AppendElement(frame); |
682 | 0 | } |
683 | 0 |
|
684 | 0 | if (flags & RetainedDisplayListData::FrameFlags::HasProps) { |
685 | 0 | aFramesWithProps->AppendElement(frame); |
686 | 0 | } |
687 | 0 |
|
688 | 0 | if (flags & RetainedDisplayListData::FrameFlags::HadWillChange) { |
689 | 0 | aBuilder->RemoveFromWillChangeBudget(frame); |
690 | 0 | } |
691 | 0 | } |
692 | 0 |
|
693 | 0 | data->Clear(); |
694 | 0 | } |
695 | | |
696 | | struct CbData |
697 | | { |
698 | | nsDisplayListBuilder* builder; |
699 | | nsTArray<nsIFrame*>* modifiedFrames; |
700 | | nsTArray<nsIFrame*>* framesWithProps; |
701 | | }; |
702 | | |
703 | | static nsIFrame* |
704 | | GetRootFrameForPainting(nsDisplayListBuilder* aBuilder, nsIDocument* aDocument) |
705 | 0 | { |
706 | 0 | // Although this is the actual subdocument, it might not be |
707 | 0 | // what painting uses. Walk up to the nsSubDocumentFrame owning |
708 | 0 | // us, and then ask that which subdoc it's going to paint. |
709 | 0 |
|
710 | 0 | nsIPresShell* presShell = aDocument->GetShell(); |
711 | 0 | if (!presShell) { |
712 | 0 | return nullptr; |
713 | 0 | } |
714 | 0 | nsView* rootView = presShell->GetViewManager()->GetRootView(); |
715 | 0 | if (!rootView) { |
716 | 0 | return nullptr; |
717 | 0 | } |
718 | 0 | |
719 | 0 | // There should be an anonymous inner view between the root view |
720 | 0 | // of the subdoc, and the view for the nsSubDocumentFrame. |
721 | 0 | nsView* innerView = rootView->GetParent(); |
722 | 0 | if (!innerView) { |
723 | 0 | return nullptr; |
724 | 0 | } |
725 | 0 | |
726 | 0 | nsView* subDocView = innerView->GetParent(); |
727 | 0 | if (!subDocView) { |
728 | 0 | return nullptr; |
729 | 0 | } |
730 | 0 | |
731 | 0 | nsIFrame* subDocFrame = subDocView->GetFrame(); |
732 | 0 | if (!subDocFrame) { |
733 | 0 | return nullptr; |
734 | 0 | } |
735 | 0 | |
736 | 0 | nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame); |
737 | 0 | MOZ_ASSERT(subdocumentFrame); |
738 | 0 | presShell = subdocumentFrame->GetSubdocumentPresShellForPainting( |
739 | 0 | aBuilder->IsIgnoringPaintSuppression() |
740 | 0 | ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION |
741 | 0 | : 0); |
742 | 0 | return presShell ? presShell->GetRootFrame() : nullptr; |
743 | 0 | } |
744 | | |
745 | | static bool |
746 | | SubDocEnumCb(nsIDocument* aDocument, void* aData) |
747 | 0 | { |
748 | 0 | MOZ_ASSERT(aDocument); |
749 | 0 | MOZ_ASSERT(aData); |
750 | 0 |
|
751 | 0 | CbData* data = static_cast<CbData*>(aData); |
752 | 0 |
|
753 | 0 | nsIFrame* rootFrame = GetRootFrameForPainting(data->builder, aDocument); |
754 | 0 | if (rootFrame) { |
755 | 0 | TakeAndAddModifiedAndFramesWithPropsFromRootFrame( |
756 | 0 | data->builder, data->modifiedFrames, data->framesWithProps, rootFrame); |
757 | 0 |
|
758 | 0 | nsIDocument* innerDoc = rootFrame->PresShell()->GetDocument(); |
759 | 0 | if (innerDoc) { |
760 | 0 | innerDoc->EnumerateSubDocuments(SubDocEnumCb, aData); |
761 | 0 | } |
762 | 0 | } |
763 | 0 | return true; |
764 | 0 | } |
765 | | |
766 | | static void |
767 | | GetModifiedAndFramesWithProps(nsDisplayListBuilder* aBuilder, |
768 | | nsTArray<nsIFrame*>* aOutModifiedFrames, |
769 | | nsTArray<nsIFrame*>* aOutFramesWithProps) |
770 | 0 | { |
771 | 0 | nsIFrame* rootFrame = aBuilder->RootReferenceFrame(); |
772 | 0 | MOZ_ASSERT(rootFrame); |
773 | 0 |
|
774 | 0 | TakeAndAddModifiedAndFramesWithPropsFromRootFrame( |
775 | 0 | aBuilder, aOutModifiedFrames, aOutFramesWithProps, rootFrame); |
776 | 0 |
|
777 | 0 | nsIDocument* rootdoc = rootFrame->PresContext()->Document(); |
778 | 0 | if (rootdoc) { |
779 | 0 | CbData data = { aBuilder, aOutModifiedFrames, aOutFramesWithProps }; |
780 | 0 |
|
781 | 0 | rootdoc->EnumerateSubDocuments(SubDocEnumCb, &data); |
782 | 0 | } |
783 | 0 | } |
784 | | |
785 | | // ComputeRebuildRegion debugging |
786 | | // #define CRR_DEBUG 1 |
787 | | #if CRR_DEBUG |
788 | | #define CRR_LOG(...) printf_stderr(__VA_ARGS__) |
789 | | #else |
790 | | #define CRR_LOG(...) |
791 | | #endif |
792 | | |
793 | | static nsDisplayItem* |
794 | | GetFirstDisplayItemWithChildren(nsIFrame* aFrame) |
795 | 0 | { |
796 | 0 | nsIFrame::DisplayItemArray* items = |
797 | 0 | aFrame->GetProperty(nsIFrame::DisplayItems()); |
798 | 0 | if (!items) { |
799 | 0 | return nullptr; |
800 | 0 | } |
801 | 0 | |
802 | 0 | for (nsDisplayItem* i : *items) { |
803 | 0 | if (i->GetChildren()) { |
804 | 0 | return i; |
805 | 0 | } |
806 | 0 | } |
807 | 0 | return nullptr; |
808 | 0 | } |
809 | | |
810 | | static bool |
811 | | IsInPreserve3DContext(const nsIFrame* aFrame) |
812 | 0 | { |
813 | 0 | return aFrame->Extend3DContext() || |
814 | 0 | aFrame->Combines3DTransformWithAncestors(); |
815 | 0 | } |
816 | | |
817 | | static bool |
818 | | ProcessFrameInternal(nsIFrame* aFrame, |
819 | | nsDisplayListBuilder& aBuilder, |
820 | | AnimatedGeometryRoot** aAGR, |
821 | | nsRect& aOverflow, |
822 | | nsIFrame* aStopAtFrame, |
823 | | nsTArray<nsIFrame*>& aOutFramesWithProps, |
824 | | const bool aStopAtStackingContext) |
825 | 0 | { |
826 | 0 | nsIFrame* currentFrame = aFrame; |
827 | 0 |
|
828 | 0 | while (currentFrame != aStopAtFrame) { |
829 | 0 | CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", |
830 | 0 | currentFrame, |
831 | 0 | !aStopAtStackingContext, |
832 | 0 | aOverflow.x, |
833 | 0 | aOverflow.y, |
834 | 0 | aOverflow.width, |
835 | 0 | aOverflow.height); |
836 | 0 |
|
837 | 0 | // If the current frame is an OOF frame, DisplayListBuildingData needs to be |
838 | 0 | // set on all the ancestor stacking contexts of the placeholder frame, up |
839 | 0 | // to the containing block of the OOF frame. This is done to ensure that the |
840 | 0 | // content that might be behind the OOF frame is built for merging. |
841 | 0 | nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) |
842 | 0 | ? currentFrame->GetPlaceholderFrame() |
843 | 0 | : nullptr; |
844 | 0 |
|
845 | 0 | if (placeholder) { |
846 | 0 | // The rect aOverflow is in the coordinate space of the containing block. |
847 | 0 | // Convert it to a coordinate space of the placeholder frame. |
848 | 0 | nsRect placeholderOverflow = |
849 | 0 | aOverflow + currentFrame->GetOffsetTo(placeholder); |
850 | 0 |
|
851 | 0 | CRR_LOG("Processing placeholder %p for OOF frame %p\n", |
852 | 0 | placeholder, |
853 | 0 | currentFrame); |
854 | 0 |
|
855 | 0 | CRR_LOG("OOF frame draw area: %d %d %d %d\n", |
856 | 0 | placeholderOverflow.x, |
857 | 0 | placeholderOverflow.y, |
858 | 0 | placeholderOverflow.width, |
859 | 0 | placeholderOverflow.height); |
860 | 0 |
|
861 | 0 | // Tracking AGRs for the placeholder processing is not necessary, as the |
862 | 0 | // goal is to only modify the DisplayListBuildingData rect. |
863 | 0 | AnimatedGeometryRoot* dummyAGR = nullptr; |
864 | 0 |
|
865 | 0 | // Find a common ancestor frame to handle frame continuations. |
866 | 0 | // TODO: It might be possible to write a more specific and efficient |
867 | 0 | // function for this. |
868 | 0 | nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame( |
869 | 0 | currentFrame->GetParent(), placeholder->GetParent()); |
870 | 0 |
|
871 | 0 | if (!ProcessFrameInternal(placeholder, |
872 | 0 | aBuilder, |
873 | 0 | &dummyAGR, |
874 | 0 | placeholderOverflow, |
875 | 0 | ancestor, |
876 | 0 | aOutFramesWithProps, |
877 | 0 | false)) { |
878 | 0 | return false; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | |
882 | 0 | // Convert 'aOverflow' into the coordinate space of the nearest stacking |
883 | 0 | // context or display port ancestor and update 'currentFrame' to point to |
884 | 0 | // that frame. |
885 | 0 | aOverflow = nsLayoutUtils::TransformFrameRectToAncestor( |
886 | 0 | currentFrame, |
887 | 0 | aOverflow, |
888 | 0 | aStopAtFrame, |
889 | 0 | nullptr, |
890 | 0 | nullptr, |
891 | 0 | /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, |
892 | 0 | ¤tFrame); |
893 | 0 | if (IsInPreserve3DContext(currentFrame)) { |
894 | 0 | return false; |
895 | 0 | } |
896 | 0 | |
897 | 0 | MOZ_ASSERT(currentFrame); |
898 | 0 |
|
899 | 0 | if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) { |
900 | 0 | CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); |
901 | 0 | nsIScrollableFrame* sf = do_QueryFrame(currentFrame); |
902 | 0 | MOZ_ASSERT(sf); |
903 | 0 | nsRect displayPort; |
904 | 0 | DebugOnly<bool> hasDisplayPort = nsLayoutUtils::GetDisplayPort( |
905 | 0 | currentFrame->GetContent(), &displayPort, RelativeTo::ScrollPort); |
906 | 0 | MOZ_ASSERT(hasDisplayPort); |
907 | 0 | // get it relative to the scrollport (from the scrollframe) |
908 | 0 | nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft(); |
909 | 0 | r.IntersectRect(r, displayPort); |
910 | 0 | if (!r.IsEmpty()) { |
911 | 0 | nsRect* rect = currentFrame->GetProperty( |
912 | 0 | nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); |
913 | 0 | if (!rect) { |
914 | 0 | rect = new nsRect(); |
915 | 0 | currentFrame->SetProperty( |
916 | 0 | nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); |
917 | 0 | currentFrame->SetHasOverrideDirtyRegion(true); |
918 | 0 | aOutFramesWithProps.AppendElement(currentFrame); |
919 | 0 | } |
920 | 0 | rect->UnionRect(*rect, r); |
921 | 0 | CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", |
922 | 0 | r.x, |
923 | 0 | r.y, |
924 | 0 | r.width, |
925 | 0 | r.height); |
926 | 0 |
|
927 | 0 | // TODO: Can we just use MarkFrameForDisplayIfVisible, plus |
928 | 0 | // MarkFramesForDifferentAGR to ensure that this displayport, plus any |
929 | 0 | // items that move relative to it get rebuilt, and then not contribute |
930 | 0 | // to the root dirty area? |
931 | 0 | aOverflow = sf->GetScrollPortRect(); |
932 | 0 | } else { |
933 | 0 | // Don't contribute to the root dirty area at all. |
934 | 0 | aOverflow.SetEmpty(); |
935 | 0 | } |
936 | 0 | } else { |
937 | 0 | aOverflow.IntersectRect( |
938 | 0 | aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf()); |
939 | 0 | } |
940 | 0 |
|
941 | 0 | if (aOverflow.IsEmpty()) { |
942 | 0 | break; |
943 | 0 | } |
944 | 0 | |
945 | 0 | if (currentFrame != aBuilder.RootReferenceFrame() && |
946 | 0 | currentFrame->IsStackingContext() && |
947 | 0 | currentFrame->IsFixedPosContainingBlock()) { |
948 | 0 | CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); |
949 | 0 | // If we found an intermediate stacking context with an existing display |
950 | 0 | // item then we can store the dirty rect there and stop. If we couldn't |
951 | 0 | // find one then we need to keep bubbling up to the next stacking context. |
952 | 0 | nsDisplayItem* wrapperItem = |
953 | 0 | GetFirstDisplayItemWithChildren(currentFrame); |
954 | 0 | if (!wrapperItem) { |
955 | 0 | continue; |
956 | 0 | } |
957 | 0 | |
958 | 0 | // Store the stacking context relative dirty area such |
959 | 0 | // that display list building will pick it up when it |
960 | 0 | // gets to it. |
961 | 0 | nsDisplayListBuilder::DisplayListBuildingData* data = |
962 | 0 | currentFrame->GetProperty( |
963 | 0 | nsDisplayListBuilder::DisplayListBuildingRect()); |
964 | 0 | if (!data) { |
965 | 0 | data = new nsDisplayListBuilder::DisplayListBuildingData(); |
966 | 0 | currentFrame->SetProperty( |
967 | 0 | nsDisplayListBuilder::DisplayListBuildingRect(), data); |
968 | 0 | currentFrame->SetHasOverrideDirtyRegion(true); |
969 | 0 | aOutFramesWithProps.AppendElement(currentFrame); |
970 | 0 | } |
971 | 0 | CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", |
972 | 0 | aOverflow.x, |
973 | 0 | aOverflow.y, |
974 | 0 | aOverflow.width, |
975 | 0 | aOverflow.height); |
976 | 0 | data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow); |
977 | 0 |
|
978 | 0 | if (!aStopAtStackingContext) { |
979 | 0 | // Continue ascending the frame tree until we reach aStopAtFrame. |
980 | 0 | continue; |
981 | 0 | } |
982 | 0 | |
983 | 0 | // Grab the visible (display list building) rect for children of this |
984 | 0 | // wrapper item and convert into into coordinate relative to the current |
985 | 0 | // frame. |
986 | 0 | nsRect previousVisible = wrapperItem->GetBuildingRectForChildren(); |
987 | 0 | if (wrapperItem->ReferenceFrameForChildren() == |
988 | 0 | wrapperItem->ReferenceFrame()) { |
989 | 0 | previousVisible -= wrapperItem->ToReferenceFrame(); |
990 | 0 | } else { |
991 | 0 | MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() == |
992 | 0 | wrapperItem->Frame()); |
993 | 0 | } |
994 | 0 |
|
995 | 0 | if (!previousVisible.Contains(aOverflow)) { |
996 | 0 | // If the overflow area of the changed frame isn't contained within the |
997 | 0 | // old item, then we might change the size of the item and need to |
998 | 0 | // update its sorting accordingly. Keep propagating the overflow area up |
999 | 0 | // so that we build intersecting items for sorting. |
1000 | 0 | continue; |
1001 | 0 | } |
1002 | 0 | |
1003 | 0 | if (!data->mModifiedAGR) { |
1004 | 0 | data->mModifiedAGR = *aAGR; |
1005 | 0 | } else if (data->mModifiedAGR != *aAGR) { |
1006 | 0 | data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf(); |
1007 | 0 | CRR_LOG("Found multiple modified AGRs within this stacking context, " |
1008 | 0 | "giving up\n"); |
1009 | 0 | } |
1010 | 0 |
|
1011 | 0 | // Don't contribute to the root dirty area at all. |
1012 | 0 | aOverflow.SetEmpty(); |
1013 | 0 | *aAGR = nullptr; |
1014 | 0 |
|
1015 | 0 | break; |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 | return true; |
1019 | 0 | } |
1020 | | |
1021 | | bool |
1022 | | RetainedDisplayListBuilder::ProcessFrame( |
1023 | | nsIFrame* aFrame, |
1024 | | nsDisplayListBuilder& aBuilder, |
1025 | | nsIFrame* aStopAtFrame, |
1026 | | nsTArray<nsIFrame*>& aOutFramesWithProps, |
1027 | | const bool aStopAtStackingContext, |
1028 | | nsRect* aOutDirty, |
1029 | | AnimatedGeometryRoot** aOutModifiedAGR) |
1030 | 0 | { |
1031 | 0 | if (aFrame->HasOverrideDirtyRegion()) { |
1032 | 0 | aOutFramesWithProps.AppendElement(aFrame); |
1033 | 0 | } |
1034 | 0 |
|
1035 | 0 | if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { |
1036 | 0 | return true; |
1037 | 0 | } |
1038 | 0 | |
1039 | 0 | // TODO: There is almost certainly a faster way of doing this, probably can be |
1040 | 0 | // combined with the ancestor walk for TransformFrameRectToAncestor. |
1041 | 0 | AnimatedGeometryRoot* agr = |
1042 | 0 | aBuilder.FindAnimatedGeometryRootFor(aFrame)->GetAsyncAGR(); |
1043 | 0 |
|
1044 | 0 | CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame); |
1045 | 0 |
|
1046 | 0 | // Convert the frame's overflow rect into the coordinate space |
1047 | 0 | // of the nearest stacking context that has an existing display item. |
1048 | 0 | // We store that as a dirty rect on that stacking context so that we build |
1049 | 0 | // all items that intersect the changed frame within the stacking context, |
1050 | 0 | // and then we use MarkFrameForDisplayIfVisible to make sure the stacking |
1051 | 0 | // context itself gets built. We don't need to build items that intersect |
1052 | 0 | // outside of the stacking context, since we know the stacking context item |
1053 | 0 | // exists in the old list, so we can trivially merge without needing other |
1054 | 0 | // items. |
1055 | 0 | nsRect overflow = aFrame->GetVisualOverflowRectRelativeToSelf(); |
1056 | 0 |
|
1057 | 0 | // If the modified frame is also a caret frame, include the caret area. |
1058 | 0 | // This is needed because some frames (for example text frames without text) |
1059 | 0 | // might have an empty overflow rect. |
1060 | 0 | if (aFrame == aBuilder.GetCaretFrame()) { |
1061 | 0 | overflow.UnionRect(overflow, aBuilder.GetCaretRect()); |
1062 | 0 | } |
1063 | 0 |
|
1064 | 0 | if (!ProcessFrameInternal(aFrame, |
1065 | 0 | aBuilder, |
1066 | 0 | &agr, |
1067 | 0 | overflow, |
1068 | 0 | aStopAtFrame, |
1069 | 0 | aOutFramesWithProps, |
1070 | 0 | aStopAtStackingContext)) { |
1071 | 0 | return false; |
1072 | 0 | } |
1073 | 0 | |
1074 | 0 | if (!overflow.IsEmpty()) { |
1075 | 0 | aOutDirty->UnionRect(*aOutDirty, overflow); |
1076 | 0 | CRR_LOG("Adding area to root draw area: %d %d %d %d\n", |
1077 | 0 | overflow.x, |
1078 | 0 | overflow.y, |
1079 | 0 | overflow.width, |
1080 | 0 | overflow.height); |
1081 | 0 |
|
1082 | 0 | // If we get changed frames from multiple AGRS, then just give up as it gets |
1083 | 0 | // really complex to track which items would need to be marked in |
1084 | 0 | // MarkFramesForDifferentAGR. |
1085 | 0 | if (!*aOutModifiedAGR) { |
1086 | 0 | CRR_LOG("Setting %p as root stacking context AGR\n", agr); |
1087 | 0 | *aOutModifiedAGR = agr; |
1088 | 0 | } else if (agr && *aOutModifiedAGR != agr) { |
1089 | 0 | CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); |
1090 | 0 | return false; |
1091 | 0 | } |
1092 | 0 | } |
1093 | 0 | return true; |
1094 | 0 | } |
1095 | | |
1096 | | static void |
1097 | | AddFramesForContainingBlock(nsIFrame* aBlock, |
1098 | | const nsFrameList& aFrames, |
1099 | | nsTArray<nsIFrame*>& aExtraFrames) |
1100 | 0 | { |
1101 | 0 | for (nsIFrame* f : aFrames) { |
1102 | 0 | if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) { |
1103 | 0 | CRR_LOG("Adding invalid OOF %p\n", f); |
1104 | 0 | aExtraFrames.AppendElement(f); |
1105 | 0 | } |
1106 | 0 | } |
1107 | 0 | } |
1108 | | |
1109 | | // Placeholder descendants of aFrame don't contribute to aFrame's overflow area. |
1110 | | // Find all the containing blocks that might own placeholders under us, walk |
1111 | | // their OOF frames list, and manually invalidate any frames that are |
1112 | | // descendants of a modified frame (us, or another frame we'll get to soon). |
1113 | | // This is combined with the work required for MarkFrameForDisplayIfVisible, |
1114 | | // so that we can avoid an extra ancestor walk, and we can reuse the flag |
1115 | | // to detect when we've already visited an ancestor (and thus all further |
1116 | | // ancestors must also be visited). |
1117 | | void |
1118 | | FindContainingBlocks(nsIFrame* aFrame, nsTArray<nsIFrame*>& aExtraFrames) |
1119 | 0 | { |
1120 | 0 | for (nsIFrame* f = aFrame; f; |
1121 | 0 | f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { |
1122 | 0 | if (f->ForceDescendIntoIfVisible()) |
1123 | 0 | return; |
1124 | 0 | f->SetForceDescendIntoIfVisible(true); |
1125 | 0 | CRR_LOG("Considering OOFs for %p\n", f); |
1126 | 0 |
|
1127 | 0 | AddFramesForContainingBlock( |
1128 | 0 | f, f->GetChildList(nsIFrame::kFloatList), aExtraFrames); |
1129 | 0 | AddFramesForContainingBlock( |
1130 | 0 | f, f->GetChildList(f->GetAbsoluteListID()), aExtraFrames); |
1131 | 0 | } |
1132 | 0 | } |
1133 | | |
1134 | | /** |
1135 | | * Given a list of frames that has been modified, computes the region that we |
1136 | | * need to do display list building for in order to build all modified display |
1137 | | * items. |
1138 | | * |
1139 | | * When a modified frame is within a stacking context (with an existing display |
1140 | | * item), then we only contribute to the build area within the stacking context, |
1141 | | * as well as forcing display list building to descend to the stacking context. |
1142 | | * We don't need to add build area outside of the stacking context (and force |
1143 | | * items above/below the stacking context container item to be built), since |
1144 | | * just matching the position of the stacking context container item is |
1145 | | * sufficient to ensure correct ordering during merging. |
1146 | | * |
1147 | | * We need to rebuild all items that might intersect with the modified frame, |
1148 | | * both now and during async changes on the compositor. We do this by rebuilding |
1149 | | * the area covered by the changed frame, as well as rebuilding all items that |
1150 | | * have a different (async) AGR to the changed frame. If we have changes to |
1151 | | * multiple AGRs (within a stacking context), then we rebuild that stacking |
1152 | | * context entirely. |
1153 | | * |
1154 | | * @param aModifiedFrames The list of modified frames. |
1155 | | * @param aOutDirty The result region to use for display list building. |
1156 | | * @param aOutModifiedAGR The modified AGR for the root stacking context. |
1157 | | * @param aOutFramesWithProps The list of frames to which we attached partial |
1158 | | * build data so that it can be cleaned up. |
1159 | | * |
1160 | | * @return true if we succesfully computed a partial rebuild region, false if a |
1161 | | * full build is required. |
1162 | | */ |
1163 | | bool |
1164 | | RetainedDisplayListBuilder::ComputeRebuildRegion( |
1165 | | nsTArray<nsIFrame*>& aModifiedFrames, |
1166 | | nsRect* aOutDirty, |
1167 | | AnimatedGeometryRoot** aOutModifiedAGR, |
1168 | | nsTArray<nsIFrame*>& aOutFramesWithProps) |
1169 | 0 | { |
1170 | 0 | CRR_LOG("Computing rebuild regions for %zu frames:\n", |
1171 | 0 | aModifiedFrames.Length()); |
1172 | 0 | nsTArray<nsIFrame*> extraFrames; |
1173 | 0 | for (nsIFrame* f : aModifiedFrames) { |
1174 | 0 | MOZ_ASSERT(f); |
1175 | 0 |
|
1176 | 0 | mBuilder.AddFrameMarkedForDisplayIfVisible(f); |
1177 | 0 | FindContainingBlocks(f, extraFrames); |
1178 | 0 |
|
1179 | 0 | if (!ProcessFrame(f, |
1180 | 0 | mBuilder, |
1181 | 0 | mBuilder.RootReferenceFrame(), |
1182 | 0 | aOutFramesWithProps, |
1183 | 0 | true, |
1184 | 0 | aOutDirty, |
1185 | 0 | aOutModifiedAGR)) { |
1186 | 0 | return false; |
1187 | 0 | } |
1188 | 0 | } |
1189 | 0 |
|
1190 | 0 | for (nsIFrame* f : extraFrames) { |
1191 | 0 | mBuilder.MarkFrameModifiedDuringBuilding(f); |
1192 | 0 |
|
1193 | 0 | if (!ProcessFrame(f, |
1194 | 0 | mBuilder, |
1195 | 0 | mBuilder.RootReferenceFrame(), |
1196 | 0 | aOutFramesWithProps, |
1197 | 0 | true, |
1198 | 0 | aOutDirty, |
1199 | 0 | aOutModifiedAGR)) { |
1200 | 0 | return false; |
1201 | 0 | } |
1202 | 0 | } |
1203 | 0 |
|
1204 | 0 | return true; |
1205 | 0 | } |
1206 | | |
1207 | | /* |
1208 | | * A simple early exit heuristic to avoid slow partial display list rebuilds. |
1209 | | */ |
1210 | | static bool |
1211 | | ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames) |
1212 | 0 | { |
1213 | 0 | if (aModifiedFrames.Length() > gfxPrefs::LayoutRebuildFrameLimit()) { |
1214 | 0 | return false; |
1215 | 0 | } |
1216 | 0 | |
1217 | 0 | for (nsIFrame* f : aModifiedFrames) { |
1218 | 0 | MOZ_ASSERT(f); |
1219 | 0 |
|
1220 | 0 | const LayoutFrameType type = f->Type(); |
1221 | 0 |
|
1222 | 0 | // If we have any modified frames of the following types, it is likely that |
1223 | 0 | // doing a partial rebuild of the display list will be slower than doing a |
1224 | 0 | // full rebuild. |
1225 | 0 | // This is because these frames either intersect or may intersect with most |
1226 | 0 | // of the page content. This is either due to display port size or different |
1227 | 0 | // async AGR. |
1228 | 0 | if (type == LayoutFrameType::Viewport || |
1229 | 0 | type == LayoutFrameType::PageContent || |
1230 | 0 | type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) { |
1231 | 0 | return false; |
1232 | 0 | } |
1233 | 0 | } |
1234 | 0 |
|
1235 | 0 | return true; |
1236 | 0 | } |
1237 | | |
1238 | | static void |
1239 | | ClearFrameProps(nsTArray<nsIFrame*>& aFrames) |
1240 | 0 | { |
1241 | 0 | for (nsIFrame* f : aFrames) { |
1242 | 0 | if (f->HasOverrideDirtyRegion()) { |
1243 | 0 | f->SetHasOverrideDirtyRegion(false); |
1244 | 0 | f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect()); |
1245 | 0 | f->DeleteProperty( |
1246 | 0 | nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); |
1247 | 0 | } |
1248 | 0 |
|
1249 | 0 | f->SetFrameIsModified(false); |
1250 | 0 | } |
1251 | 0 | } |
1252 | | |
1253 | | class AutoClearFramePropsArray |
1254 | | { |
1255 | | public: |
1256 | | explicit AutoClearFramePropsArray(size_t aCapacity) |
1257 | | : mFrames(aCapacity) |
1258 | 0 | { |
1259 | 0 | } |
1260 | | |
1261 | 0 | AutoClearFramePropsArray() = default; |
1262 | | |
1263 | 0 | ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); } |
1264 | | |
1265 | 0 | nsTArray<nsIFrame*>& Frames() { return mFrames; } |
1266 | | |
1267 | 0 | bool IsEmpty() const { return mFrames.IsEmpty(); } |
1268 | | |
1269 | | private: |
1270 | | nsTArray<nsIFrame*> mFrames; |
1271 | | }; |
1272 | | |
1273 | | void |
1274 | | RetainedDisplayListBuilder::ClearFramesWithProps() |
1275 | 0 | { |
1276 | 0 | AutoClearFramePropsArray modifiedFrames; |
1277 | 0 | AutoClearFramePropsArray framesWithProps; |
1278 | 0 | GetModifiedAndFramesWithProps( |
1279 | 0 | &mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames()); |
1280 | 0 | } |
1281 | | |
1282 | | auto |
1283 | | RetainedDisplayListBuilder::AttemptPartialUpdate( |
1284 | | nscolor aBackstop, |
1285 | | mozilla::DisplayListChecker* aChecker) -> PartialUpdateResult |
1286 | 0 | { |
1287 | 0 | mBuilder.RemoveModifiedWindowRegions(); |
1288 | 0 | mBuilder.ClearWindowOpaqueRegion(); |
1289 | 0 |
|
1290 | 0 | if (mBuilder.ShouldSyncDecodeImages()) { |
1291 | 0 | MarkFramesWithItemsAndImagesModified(&mList); |
1292 | 0 | } |
1293 | 0 |
|
1294 | 0 | mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); |
1295 | 0 |
|
1296 | 0 | // We set the override dirty regions during ComputeRebuildRegion or in |
1297 | 0 | // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also |
1298 | 0 | // marks the frame modified, so those regions are cleared here as well. |
1299 | 0 | AutoClearFramePropsArray modifiedFrames(64); |
1300 | 0 | AutoClearFramePropsArray framesWithProps; |
1301 | 0 | GetModifiedAndFramesWithProps( |
1302 | 0 | &mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames()); |
1303 | 0 |
|
1304 | 0 | // Do not allow partial builds if the retained display list is empty, or if |
1305 | 0 | // ShouldBuildPartial heuristic fails. |
1306 | 0 | bool shouldBuildPartial = |
1307 | 0 | !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames()); |
1308 | 0 |
|
1309 | 0 | // We don't support retaining with overlay scrollbars, since they require |
1310 | 0 | // us to look at the display list and pick the highest z-index, which |
1311 | 0 | // we can't do during partial building. |
1312 | 0 | if (mBuilder.DisablePartialUpdates()) { |
1313 | 0 | shouldBuildPartial = false; |
1314 | 0 | mBuilder.SetDisablePartialUpdates(false); |
1315 | 0 | } |
1316 | 0 |
|
1317 | 0 | if (mPreviousCaret != mBuilder.GetCaretFrame()) { |
1318 | 0 | if (mPreviousCaret) { |
1319 | 0 | if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) { |
1320 | 0 | modifiedFrames.Frames().AppendElement(mPreviousCaret); |
1321 | 0 | } |
1322 | 0 | } |
1323 | 0 |
|
1324 | 0 | if (mBuilder.GetCaretFrame()) { |
1325 | 0 | if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) { |
1326 | 0 | modifiedFrames.Frames().AppendElement(mBuilder.GetCaretFrame()); |
1327 | 0 | } |
1328 | 0 | } |
1329 | 0 |
|
1330 | 0 | mPreviousCaret = mBuilder.GetCaretFrame(); |
1331 | 0 | } |
1332 | 0 |
|
1333 | 0 | nsRect modifiedDirty; |
1334 | 0 | AnimatedGeometryRoot* modifiedAGR = nullptr; |
1335 | 0 | if (!shouldBuildPartial || |
1336 | 0 | !ComputeRebuildRegion(modifiedFrames.Frames(), |
1337 | 0 | &modifiedDirty, |
1338 | 0 | &modifiedAGR, |
1339 | 0 | framesWithProps.Frames()) || |
1340 | 0 | !PreProcessDisplayList(&mList, modifiedAGR)) { |
1341 | 0 | mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); |
1342 | 0 | mList.DeleteAll(&mBuilder); |
1343 | 0 | return PartialUpdateResult::Failed; |
1344 | 0 | } |
1345 | 0 | |
1346 | 0 | // This is normally handled by EnterPresShell, but we skipped it so that we |
1347 | 0 | // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion. |
1348 | 0 | nsIScrollableFrame* sf = mBuilder.RootReferenceFrame() |
1349 | 0 | ->PresShell() |
1350 | 0 | ->GetRootScrollFrameAsScrollable(); |
1351 | 0 | if (sf) { |
1352 | 0 | nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); |
1353 | 0 | if (canvasFrame) { |
1354 | 0 | mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, |
1355 | 0 | mBuilder.RootReferenceFrame()); |
1356 | 0 | } |
1357 | 0 | } |
1358 | 0 |
|
1359 | 0 | modifiedDirty.IntersectRect( |
1360 | 0 | modifiedDirty, |
1361 | 0 | mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); |
1362 | 0 |
|
1363 | 0 | PartialUpdateResult result = PartialUpdateResult::NoChange; |
1364 | 0 | if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) { |
1365 | 0 | result = PartialUpdateResult::Updated; |
1366 | 0 | } |
1367 | 0 |
|
1368 | 0 | mBuilder.SetDirtyRect(modifiedDirty); |
1369 | 0 | mBuilder.SetPartialUpdate(true); |
1370 | 0 |
|
1371 | 0 | nsDisplayList modifiedDL; |
1372 | 0 | mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext( |
1373 | 0 | &mBuilder, &modifiedDL); |
1374 | 0 | if (!modifiedDL.IsEmpty()) { |
1375 | 0 | nsLayoutUtils::AddExtraBackgroundItems( |
1376 | 0 | mBuilder, |
1377 | 0 | modifiedDL, |
1378 | 0 | mBuilder.RootReferenceFrame(), |
1379 | 0 | nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()), |
1380 | 0 | mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(), |
1381 | 0 | aBackstop); |
1382 | 0 | } |
1383 | 0 | mBuilder.SetPartialUpdate(false); |
1384 | 0 |
|
1385 | 0 | if (mBuilder.PartialBuildFailed()) { |
1386 | 0 | mBuilder.SetPartialBuildFailed(false); |
1387 | 0 | mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); |
1388 | 0 | mList.DeleteAll(&mBuilder); |
1389 | 0 | modifiedDL.DeleteAll(&mBuilder); |
1390 | 0 | return PartialUpdateResult::Failed; |
1391 | 0 | } |
1392 | 0 | |
1393 | 0 | if (aChecker) { |
1394 | 0 | aChecker->Set(&modifiedDL, "TM"); |
1395 | 0 | } |
1396 | 0 |
|
1397 | 0 | // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", |
1398 | 0 | // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, |
1399 | 0 | // modifiedDirty.height); |
1400 | 0 | // nsFrame::PrintDisplayList(&mBuilder, modifiedDL); |
1401 | 0 |
|
1402 | 0 | // |modifiedDL| can sometimes be empty here. We still perform the |
1403 | 0 | // display list merging to prune unused items (for example, items that |
1404 | 0 | // are not visible anymore) from the old list. |
1405 | 0 | // TODO: Optimization opportunity. In this case, MergeDisplayLists() |
1406 | 0 | // unnecessarily creates a hashtable of the old items. |
1407 | 0 | // TODO: Ideally we could skip this if result is NoChange, but currently when |
1408 | 0 | // we call RestoreState on nsDisplayWrapList it resets the clip to the base |
1409 | 0 | // clip, and we need the UpdateBounds call (within MergeDisplayLists) to |
1410 | 0 | // move it to the correct inner clip. |
1411 | 0 | Maybe<const ActiveScrolledRoot*> dummy; |
1412 | 0 | if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { |
1413 | 0 | result = PartialUpdateResult::Updated; |
1414 | 0 | } |
1415 | 0 |
|
1416 | 0 | // printf_stderr("Painting --- Merged list:\n"); |
1417 | 0 | // nsFrame::PrintDisplayList(&mBuilder, mList); |
1418 | 0 |
|
1419 | 0 | mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); |
1420 | 0 | return result; |
1421 | 0 | } |