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 #include "mozilla/layers/ClipManager.h"
8
9 #include "DisplayItemClipChain.h"
10 #include "FrameMetrics.h"
11 #include "mozilla/layers/StackingContextHelper.h"
12 #include "mozilla/layers/WebRenderLayerManager.h"
13 #include "mozilla/webrender/WebRenderAPI.h"
14 #include "nsDisplayList.h"
15 #include "nsStyleStructInlines.h"
16 #include "UnitTransforms.h"
17
18 // clang-format off
19 #define CLIP_LOG(...)
20 //#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__)
21 //#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__)
22 // clang-format on
23
24 namespace mozilla {
25 namespace layers {
26
ClipManager()27 ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {}
28
BeginBuild(WebRenderLayerManager * aManager,wr::DisplayListBuilder & aBuilder)29 void ClipManager::BeginBuild(WebRenderLayerManager* aManager,
30 wr::DisplayListBuilder& aBuilder) {
31 MOZ_ASSERT(!mManager);
32 mManager = aManager;
33 MOZ_ASSERT(!mBuilder);
34 mBuilder = &aBuilder;
35 MOZ_ASSERT(mCacheStack.empty());
36 mCacheStack.emplace();
37 MOZ_ASSERT(mASROverride.empty());
38 MOZ_ASSERT(mItemClipStack.empty());
39 }
40
EndBuild()41 void ClipManager::EndBuild() {
42 mBuilder = nullptr;
43 mManager = nullptr;
44 mCacheStack.pop();
45 MOZ_ASSERT(mCacheStack.empty());
46 MOZ_ASSERT(mASROverride.empty());
47 MOZ_ASSERT(mItemClipStack.empty());
48 }
49
BeginList(const StackingContextHelper & aStackingContext)50 void ClipManager::BeginList(const StackingContextHelper& aStackingContext) {
51 if (aStackingContext.AffectsClipPositioning()) {
52 if (aStackingContext.ReferenceFrameId()) {
53 PushOverrideForASR(
54 mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR,
55 aStackingContext.ReferenceFrameId().ref());
56 } else {
57 // Start a new cache
58 mCacheStack.emplace();
59 }
60 }
61
62 ItemClips clips(nullptr, nullptr, false);
63 if (!mItemClipStack.empty()) {
64 clips.CopyOutputsFrom(mItemClipStack.top());
65 }
66
67 if (aStackingContext.ReferenceFrameId()) {
68 clips.mScrollId = aStackingContext.ReferenceFrameId().ref();
69 }
70
71 mItemClipStack.push(clips);
72 }
73
EndList(const StackingContextHelper & aStackingContext)74 void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
75 MOZ_ASSERT(!mItemClipStack.empty());
76 mBuilder->SetClipChainLeaf(Nothing());
77 mItemClipStack.pop();
78
79 if (aStackingContext.AffectsClipPositioning()) {
80 if (aStackingContext.ReferenceFrameId()) {
81 PopOverrideForASR(mItemClipStack.empty() ? nullptr
82 : mItemClipStack.top().mASR);
83 } else {
84 MOZ_ASSERT(!mCacheStack.empty());
85 mCacheStack.pop();
86 }
87 }
88 }
89
PushOverrideForASR(const ActiveScrolledRoot * aASR,const wr::WrSpatialId & aSpatialId)90 void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
91 const wr::WrSpatialId& aSpatialId) {
92 Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
93 MOZ_ASSERT(spaceAndClip.isSome());
94
95 CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
96 ToString(aSpatialId.id).c_str());
97
98 auto it =
99 mASROverride.insert({spaceAndClip->space, std::stack<wr::WrSpatialId>()});
100 it.first->second.push(aSpatialId);
101
102 // Start a new cache
103 mCacheStack.emplace();
104 }
105
PopOverrideForASR(const ActiveScrolledRoot * aASR)106 void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
107 MOZ_ASSERT(!mCacheStack.empty());
108 mCacheStack.pop();
109
110 Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
111 MOZ_ASSERT(spaceAndClip.isSome());
112
113 auto it = mASROverride.find(spaceAndClip->space);
114 CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
115 ToString(it->second.top().id).c_str());
116
117 it->second.pop();
118 if (it->second.empty()) {
119 mASROverride.erase(it);
120 }
121 }
122
SpatialIdAfterOverride(const wr::WrSpatialId & aSpatialId)123 wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
124 const wr::WrSpatialId& aSpatialId) {
125 auto it = mASROverride.find(aSpatialId);
126 if (it == mASROverride.end()) {
127 return aSpatialId;
128 }
129 MOZ_ASSERT(!it->second.empty());
130 CLIP_LOG("Overriding %zu with %s\n", aSpatialId.id,
131 ToString(it->second.top().id).c_str());
132
133 return it->second.top();
134 }
135
SwitchItem(nsDisplayItem * aItem)136 wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayItem* aItem) {
137 const DisplayItemClipChain* clip = aItem->GetClipChain();
138 const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
139 CLIP_LOG("processing item %p (%s) asr %p\n", aItem,
140 DisplayItemTypeName(aItem->GetType()), asr);
141
142 DisplayItemType type = aItem->GetType();
143 if (type == DisplayItemType::TYPE_STICKY_POSITION) {
144 // For sticky position items, the ASR is computed differently depending
145 // on whether the item has a fixed descendant or not. But for WebRender
146 // purposes we always want to use the ASR that would have been used if it
147 // didn't have fixed descendants, which is stored as the "container ASR" on
148 // the sticky item.
149 nsDisplayStickyPosition* sticky =
150 static_cast<nsDisplayStickyPosition*>(aItem);
151 asr = sticky->GetContainerASR();
152
153 // If the leafmost clip for the sticky item is just the displayport clip,
154 // then skip it. This allows sticky items to remain visible even if the
155 // rest of the content in the enclosing scrollframe is checkerboarding.
156 if (sticky->IsClippedToDisplayPort() && clip && clip->mASR == asr) {
157 clip = clip->mParent;
158 }
159 }
160
161 // In most cases we can combine the leaf of the clip chain with the clip rect
162 // of the display item. This reduces the number of clip items, which avoids
163 // some overhead further down the pipeline.
164 bool separateLeaf = false;
165 if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) {
166 // Container display items are not currently supported because the clip
167 // rect of a stacking context is not handled the same as normal display
168 // items.
169 separateLeaf = aItem->GetChildren() == nullptr;
170 }
171
172 ItemClips clips(asr, clip, separateLeaf);
173 MOZ_ASSERT(!mItemClipStack.empty());
174 if (clips.HasSameInputs(mItemClipStack.top())) {
175 // Early-exit because if the clips are the same as aItem's previous sibling,
176 // then we don't need to do do the work of popping the old stuff and then
177 // pushing it right back on for the new item. Note that if aItem doesn't
178 // have a previous sibling, that means BeginList would have been called
179 // just before this, which will have pushed a ItemClips(nullptr, nullptr)
180 // onto mItemClipStack, so the HasSameInputs check should return false.
181 CLIP_LOG("\tearly-exit for %p\n", aItem);
182 return mItemClipStack.top().GetSpaceAndClipChain();
183 }
184
185 // Pop aItem's previous sibling's stuff from mBuilder in preparation for
186 // pushing aItem's stuff.
187 mItemClipStack.pop();
188
189 // Zoom display items report their bounds etc using the parent document's
190 // APD because zoom items act as a conversion layer between the two different
191 // APDs.
192 int32_t auPerDevPixel;
193 if (type == DisplayItemType::TYPE_ZOOM) {
194 auPerDevPixel =
195 static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
196 } else {
197 auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
198 }
199
200 // If the leaf of the clip chain is going to be merged with the display item's
201 // clip rect, then we should create a clip chain id from the leaf's parent.
202 if (separateLeaf) {
203 CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n");
204 clip = clip->mParent;
205 }
206
207 // There are two ASR chains here that we need to be fully defined. One is the
208 // ASR chain pointed to by |asr|. The other is the
209 // ASR chain pointed to by clip->mASR. We pick the leafmost
210 // of these two chains because that one will include the other. Calling
211 // DefineScrollLayers with this leafmost ASR will recursively define all the
212 // ASRs that we care about for this item, but will not actually push
213 // anything onto the WR stack.
214 const ActiveScrolledRoot* leafmostASR = asr;
215 if (clip) {
216 leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR);
217 }
218 Maybe<wr::WrSpaceAndClip> leafmostId = DefineScrollLayers(leafmostASR, aItem);
219 Unused << leafmostId;
220
221 // Define all the clips in the item's clip chain, and obtain a clip chain id
222 // for it.
223 clips.mClipChainId = DefineClipChain(clip, auPerDevPixel);
224
225 Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(asr);
226 MOZ_ASSERT(spaceAndClip.isSome());
227 clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space);
228 CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->space.id,
229 (int)clips.mScrollId.id);
230
231 // Now that we have the scroll id and a clip id for the item, push it onto
232 // the WR stack.
233 clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel);
234 auto spaceAndClipChain = clips.GetSpaceAndClipChain();
235 mItemClipStack.push(clips);
236
237 CLIP_LOG("done setup for %p\n", aItem);
238 return spaceAndClipChain;
239 }
240
GetScrollLayer(const ActiveScrolledRoot * aASR)241 Maybe<wr::WrSpaceAndClip> ClipManager::GetScrollLayer(
242 const ActiveScrolledRoot* aASR) {
243 for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
244 Maybe<wr::WrSpaceAndClip> spaceAndClip =
245 mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId());
246 if (spaceAndClip) {
247 return spaceAndClip;
248 }
249
250 // If this ASR doesn't have a scroll ID, then we should check its ancestor.
251 // There may not be one defined because the ASR may not be scrollable or we
252 // failed to get the scroll metadata.
253 }
254
255 Maybe<wr::WrSpaceAndClip> spaceAndClip =
256 mBuilder->GetScrollIdForDefinedScrollLayer(
257 ScrollableLayerGuid::NULL_SCROLL_ID);
258 MOZ_ASSERT(spaceAndClip.isSome());
259 return spaceAndClip;
260 }
261
DefineScrollLayers(const ActiveScrolledRoot * aASR,nsDisplayItem * aItem)262 Maybe<wr::WrSpaceAndClip> ClipManager::DefineScrollLayers(
263 const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) {
264 if (!aASR) {
265 // Recursion base case
266 return Nothing();
267 }
268 ScrollableLayerGuid::ViewID viewId = aASR->GetViewId();
269 Maybe<wr::WrSpaceAndClip> spaceAndClip =
270 mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
271 if (spaceAndClip) {
272 // If we've already defined this scroll layer before, we can early-exit
273 return spaceAndClip;
274 }
275 // Recurse to define the ancestors
276 Maybe<wr::WrSpaceAndClip> ancestorSpaceAndClip =
277 DefineScrollLayers(aASR->mParent, aItem);
278
279 Maybe<ScrollMetadata> metadata =
280 aASR->mScrollableFrame->ComputeScrollMetadata(
281 mManager, aItem->ReferenceFrame(), Nothing(), nullptr);
282 if (!metadata) {
283 MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
284 return ancestorSpaceAndClip;
285 }
286
287 FrameMetrics& metrics = metadata->GetMetrics();
288 if (!metrics.IsScrollable()) {
289 // This item is a scrolling no-op, skip over it in the ASR chain.
290 return ancestorSpaceAndClip;
291 }
292
293 nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame;
294 nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
295 nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->ReferenceFrame());
296 float auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
297 nsRect scrollPort = scrollableFrame->GetScrollPortRect() + offset;
298 LayoutDeviceRect clipBounds =
299 LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel);
300
301 // The content rect that we hand to PushScrollLayer should be relative to
302 // the same origin as the clipBounds that we hand to PushScrollLayer -
303 // that is, both of them should be relative to the stacking context `aSc`.
304 // However, when we get the scrollable rect from the FrameMetrics, the
305 // origin has nothing to do with the position of the frame but instead
306 // represents the minimum allowed scroll offset of the scrollable content.
307 // While APZ uses this to clamp the scroll position, we don't need to send
308 // this to WebRender at all. Instead, we take the position from the
309 // composition bounds.
310 LayoutDeviceRect contentRect =
311 metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
312 contentRect.MoveTo(clipBounds.TopLeft());
313
314 Maybe<wr::WrSpaceAndClip> parent = ancestorSpaceAndClip;
315 if (parent) {
316 parent->space = SpatialIdAfterOverride(parent->space);
317 }
318 // The external scroll offset is accumulated into the local space positions of
319 // display items inside WR, so that the elements hash (intern) to the same
320 // content ID for quick comparisons. To avoid invalidations when the
321 // auPerDevPixel is not a round value, round here directly from app units.
322 // This guarantees we won't introduce any inaccuracy in the external scroll
323 // offset passed to WR.
324 LayoutDevicePoint scrollOffset = LayoutDevicePoint::FromAppUnitsRounded(
325 scrollableFrame->GetScrollPosition(), auPerDevPixel);
326
327 return Some(mBuilder->DefineScrollLayer(
328 viewId, parent, wr::ToLayoutRect(contentRect),
329 wr::ToLayoutRect(clipBounds), wr::ToLayoutPoint(scrollOffset)));
330 }
331
DefineClipChain(const DisplayItemClipChain * aChain,int32_t aAppUnitsPerDevPixel)332 Maybe<wr::WrClipChainId> ClipManager::DefineClipChain(
333 const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) {
334 AutoTArray<wr::WrClipId, 6> allClipIds;
335 // Iterate through the clips in the current item's clip chain, define them
336 // in WR, and put their IDs into |clipIds|.
337 for (const DisplayItemClipChain* chain = aChain; chain;
338 chain = chain->mParent) {
339 ClipIdMap& cache = mCacheStack.top();
340 auto it = cache.find(chain);
341 if (it != cache.end()) {
342 // Found it in the currently-active cache, so just use the id we have for
343 // it.
344 CLIP_LOG("cache[%p] => hit\n", chain);
345 allClipIds.AppendElements(it->second);
346 continue;
347 }
348 if (!chain->mClip.HasClip()) {
349 // This item in the chain is a no-op, skip over it
350 continue;
351 }
352
353 LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
354 chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
355 AutoTArray<wr::ComplexClipRegion, 6> wrRoundedRects;
356 chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects);
357
358 Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(chain->mASR);
359 // Before calling DefineClipChain we defined the ASRs by calling
360 // DefineScrollLayers, so we must have a scrollId here.
361 MOZ_ASSERT(spaceAndClip.isSome());
362
363 // Define the clip
364 spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space);
365
366 AutoTArray<wr::WrClipId, 4> chainClipIds;
367
368 auto rectClipId = mBuilder->DefineRectClip(Some(spaceAndClip->space),
369 wr::ToLayoutRect(clip));
370 CLIP_LOG("cache[%p] <= %zu\n", chain, rectClipId);
371 chainClipIds.AppendElement(rectClipId);
372
373 for (const auto& complexClip : wrRoundedRects) {
374 auto complexClipId = mBuilder->DefineRoundedRectClip(
375 Some(spaceAndClip->space), complexClip);
376 CLIP_LOG("cache[%p] <= %zu\n", chain, complexClipId);
377 chainClipIds.AppendElement(complexClipId);
378 }
379
380 cache[chain] = chainClipIds.Clone();
381 allClipIds.AppendElements(chainClipIds);
382 }
383
384 if (allClipIds.IsEmpty()) {
385 return Nothing();
386 }
387
388 return Some(mBuilder->DefineClipChain(allClipIds));
389 }
390
~ClipManager()391 ClipManager::~ClipManager() {
392 MOZ_ASSERT(!mBuilder);
393 MOZ_ASSERT(mCacheStack.empty());
394 MOZ_ASSERT(mItemClipStack.empty());
395 }
396
ItemClips(const ActiveScrolledRoot * aASR,const DisplayItemClipChain * aChain,bool aSeparateLeaf)397 ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
398 const DisplayItemClipChain* aChain,
399 bool aSeparateLeaf)
400 : mASR(aASR), mChain(aChain), mSeparateLeaf(aSeparateLeaf) {
401 mScrollId = wr::wr_root_scroll_node_id();
402 }
403
UpdateSeparateLeaf(wr::DisplayListBuilder & aBuilder,int32_t aAppUnitsPerDevPixel)404 void ClipManager::ItemClips::UpdateSeparateLeaf(
405 wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) {
406 Maybe<wr::LayoutRect> clipLeaf;
407 if (mSeparateLeaf) {
408 MOZ_ASSERT(mChain);
409 clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
410 mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
411 }
412
413 aBuilder.SetClipChainLeaf(clipLeaf);
414 }
415
HasSameInputs(const ItemClips & aOther)416 bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) {
417 return mASR == aOther.mASR && mChain == aOther.mChain &&
418 mSeparateLeaf == aOther.mSeparateLeaf;
419 }
420
CopyOutputsFrom(const ItemClips & aOther)421 void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) {
422 mScrollId = aOther.mScrollId;
423 mClipChainId = aOther.mClipChainId;
424 }
425
GetSpaceAndClipChain() const426 wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const {
427 auto spaceAndClipChain = wr::RootScrollNodeWithChain();
428 spaceAndClipChain.space = mScrollId;
429 if (mClipChainId) {
430 spaceAndClipChain.clip_chain = mClipChainId->id;
431 }
432 return spaceAndClipChain;
433 }
434
435 } // namespace layers
436 } // namespace mozilla
437