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