Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/gfx/layers/wr/ClipManager.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
#include "mozilla/layers/ClipManager.h"
8
9
#include "DisplayItemClipChain.h"
10
#include "FrameMetrics.h"
11
#include "LayersLogging.h"
12
#include "mozilla/layers/StackingContextHelper.h"
13
#include "mozilla/layers/WebRenderLayerManager.h"
14
#include "mozilla/webrender/WebRenderAPI.h"
15
#include "nsDisplayList.h"
16
#include "nsStyleStructInlines.h"
17
#include "UnitTransforms.h"
18
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
23
namespace mozilla {
24
namespace layers {
25
26
ClipManager::ClipManager()
27
  : mManager(nullptr)
28
  , mBuilder(nullptr)
29
0
{
30
0
}
31
32
void
33
ClipManager::BeginBuild(WebRenderLayerManager* aManager,
34
                        wr::DisplayListBuilder& aBuilder)
35
0
{
36
0
  MOZ_ASSERT(!mManager);
37
0
  mManager = aManager;
38
0
  MOZ_ASSERT(!mBuilder);
39
0
  mBuilder = &aBuilder;
40
0
  MOZ_ASSERT(mCacheStack.empty());
41
0
  mCacheStack.emplace();
42
0
  MOZ_ASSERT(mASROverride.empty());
43
0
  MOZ_ASSERT(mItemClipStack.empty());
44
0
}
45
46
void
47
ClipManager::EndBuild()
48
0
{
49
0
  mBuilder = nullptr;
50
0
  mManager = nullptr;
51
0
  mCacheStack.pop();
52
0
  MOZ_ASSERT(mCacheStack.empty());
53
0
  MOZ_ASSERT(mASROverride.empty());
54
0
  MOZ_ASSERT(mItemClipStack.empty());
55
0
}
56
57
void
58
ClipManager::BeginList(const StackingContextHelper& aStackingContext)
59
0
{
60
0
  if (aStackingContext.AffectsClipPositioning()) {
61
0
    PushOverrideForASR(
62
0
        mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR,
63
0
        aStackingContext.ReferenceFrameId());
64
0
  }
65
0
66
0
  ItemClips clips(nullptr, nullptr, false);
67
0
  if (!mItemClipStack.empty()) {
68
0
    clips.CopyOutputsFrom(mItemClipStack.top());
69
0
  }
70
0
  mItemClipStack.push(clips);
71
0
}
72
73
void
74
ClipManager::EndList(const StackingContextHelper& aStackingContext)
75
0
{
76
0
  MOZ_ASSERT(!mItemClipStack.empty());
77
0
  mItemClipStack.top().Unapply(mBuilder);
78
0
  mItemClipStack.pop();
79
0
80
0
  if (aStackingContext.AffectsClipPositioning()) {
81
0
    PopOverrideForASR(
82
0
        mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR);
83
0
  }
84
0
}
85
86
void
87
ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
88
                                const Maybe<wr::WrClipId>& aClipId)
89
0
{
90
0
  layers::FrameMetrics::ViewID viewId = aASR
91
0
      ? aASR->GetViewId() : layers::FrameMetrics::NULL_SCROLL_ID;
92
0
  Maybe<wr::WrClipId> scrollId = mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
93
0
  MOZ_ASSERT(scrollId.isSome());
94
0
95
0
  CLIP_LOG("Pushing override %zu -> %s\n", scrollId->id,
96
0
      aClipId ? Stringify(aClipId->id).c_str() : "(none)");
97
0
  auto it = mASROverride.insert({ *scrollId, std::stack<Maybe<wr::WrClipId>>() });
98
0
  it.first->second.push(aClipId);
99
0
100
0
  // Start a new cache
101
0
  mCacheStack.emplace();
102
0
}
103
104
void
105
ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR)
106
0
{
107
0
  MOZ_ASSERT(!mCacheStack.empty());
108
0
  mCacheStack.pop();
109
0
110
0
  layers::FrameMetrics::ViewID viewId = aASR
111
0
      ? aASR->GetViewId() : layers::FrameMetrics::NULL_SCROLL_ID;
112
0
  Maybe<wr::WrClipId> scrollId = mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
113
0
  MOZ_ASSERT(scrollId.isSome());
114
0
115
0
  auto it = mASROverride.find(*scrollId);
116
0
  MOZ_ASSERT(it != mASROverride.end());
117
0
  MOZ_ASSERT(!(it->second.empty()));
118
0
  CLIP_LOG("Popping override %zu -> %s\n", scrollId->id,
119
0
      it->second.top() ? Stringify(it->second.top()->id).c_str() : "(none)");
120
0
  it->second.pop();
121
0
  if (it->second.empty()) {
122
0
    mASROverride.erase(it);
123
0
  }
124
0
}
125
126
Maybe<wr::WrClipId>
127
ClipManager::ClipIdAfterOverride(const Maybe<wr::WrClipId>& aClipId)
128
0
{
129
0
  if (!aClipId) {
130
0
    return Nothing();
131
0
  }
132
0
  auto it = mASROverride.find(*aClipId);
133
0
  if (it == mASROverride.end()) {
134
0
    return aClipId;
135
0
  }
136
0
  MOZ_ASSERT(!it->second.empty());
137
0
  CLIP_LOG("Overriding %zu with %s\n", aClipId->id,
138
0
      it->second.top() ? Stringify(it->second.top()->id).c_str() : "(none)");
139
0
  return it->second.top();
140
0
}
141
142
void
143
ClipManager::BeginItem(nsDisplayItem* aItem,
144
                       const StackingContextHelper& aStackingContext)
145
0
{
146
0
  CLIP_LOG("processing item %p\n", aItem);
147
0
148
0
  const DisplayItemClipChain* clip = aItem->GetClipChain();
149
0
  const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
150
0
  DisplayItemType type = aItem->GetType();
151
0
  if (type == DisplayItemType::TYPE_STICKY_POSITION) {
152
0
    // For sticky position items, the ASR is computed differently depending
153
0
    // on whether the item has a fixed descendant or not. But for WebRender
154
0
    // purposes we always want to use the ASR that would have been used if it
155
0
    // didn't have fixed descendants, which is stored as the "container ASR" on
156
0
    // the sticky item.
157
0
    asr = static_cast<nsDisplayStickyPosition*>(aItem)->GetContainerASR();
158
0
  }
159
0
160
0
  // In most cases we can combine the leaf of the clip chain with the clip rect
161
0
  // of the display item. This reduces the number of clip items, which avoids
162
0
  // some overhead further down the pipeline.
163
0
  bool separateLeaf = false;
164
0
  if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) {
165
0
    switch (type) {
166
0
      case DisplayItemType::TYPE_BLEND_CONTAINER:
167
0
      case DisplayItemType::TYPE_BLEND_MODE:
168
0
      case DisplayItemType::TYPE_FILTER:
169
0
      case DisplayItemType::TYPE_FIXED_POSITION:
170
0
      case DisplayItemType::TYPE_MASK:
171
0
      case DisplayItemType::TYPE_OPACITY:
172
0
      case DisplayItemType::TYPE_OWN_LAYER:
173
0
      case DisplayItemType::TYPE_PERSPECTIVE:
174
0
      case DisplayItemType::TYPE_RESOLUTION:
175
0
      case DisplayItemType::TYPE_SCROLL_INFO_LAYER:
176
0
      case DisplayItemType::TYPE_STICKY_POSITION:
177
0
      case DisplayItemType::TYPE_SUBDOCUMENT:
178
0
      case DisplayItemType::TYPE_SVG_WRAPPER:
179
0
      case DisplayItemType::TYPE_TABLE_BLEND_CONTAINER:
180
0
      case DisplayItemType::TYPE_TABLE_BLEND_MODE:
181
0
      case DisplayItemType::TYPE_TABLE_FIXED_POSITION:
182
0
      case DisplayItemType::TYPE_TRANSFORM:
183
0
      case DisplayItemType::TYPE_WRAP_LIST:
184
0
        // Container display items are not currently supported because the clip
185
0
        // rect of a stacking context is not handled the same as normal display
186
0
        // items.
187
0
        break;
188
0
      case DisplayItemType::TYPE_TEXT:
189
0
        // Text with shadows interprets the text display item clip rect and
190
0
        // clips from the clip chain differently.
191
0
        if (aItem->Frame()->StyleText()->HasTextShadow()) {
192
0
          break;
193
0
        }
194
0
        MOZ_FALLTHROUGH;
195
0
      default:
196
0
        separateLeaf = true;
197
0
        break;
198
0
    }
199
0
  }
200
0
201
0
  ItemClips clips(asr, clip, separateLeaf);
202
0
  MOZ_ASSERT(!mItemClipStack.empty());
203
0
  if (clips.HasSameInputs(mItemClipStack.top())) {
204
0
    // Early-exit because if the clips are the same as aItem's previous sibling,
205
0
    // then we don't need to do do the work of popping the old stuff and then
206
0
    // pushing it right back on for the new item. Note that if aItem doesn't
207
0
    // have a previous sibling, that means BeginList would have been called
208
0
    // just before this, which will have pushed a ItemClips(nullptr, nullptr)
209
0
    // onto mItemClipStack, so the HasSameInputs check should return false.
210
0
    CLIP_LOG("early-exit for %p\n", aItem);
211
0
    return;
212
0
  }
213
0
214
0
  // Pop aItem's previous sibling's stuff from mBuilder in preparation for
215
0
  // pushing aItem's stuff.
216
0
  mItemClipStack.top().Unapply(mBuilder);
217
0
  mItemClipStack.pop();
218
0
219
0
  // Zoom display items report their bounds etc using the parent document's
220
0
  // APD because zoom items act as a conversion layer between the two different
221
0
  // APDs.
222
0
  int32_t auPerDevPixel;
223
0
  if (type == DisplayItemType::TYPE_ZOOM) {
224
0
    auPerDevPixel = static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
225
0
  } else {
226
0
    auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
227
0
  }
228
0
229
0
  // If the leaf of the clip chain is going to be merged with the display item's
230
0
  // clip rect, then we should create a clip chain id from the leaf's parent.
231
0
  if (separateLeaf) {
232
0
    clip = clip->mParent;
233
0
  }
234
0
235
0
  // There are two ASR chains here that we need to be fully defined. One is the
236
0
  // ASR chain pointed to by |asr|. The other is the
237
0
  // ASR chain pointed to by clip->mASR. We pick the leafmost
238
0
  // of these two chains because that one will include the other. Calling
239
0
  // DefineScrollLayers with this leafmost ASR will recursively define all the
240
0
  // ASRs that we care about for this item, but will not actually push
241
0
  // anything onto the WR stack.
242
0
  const ActiveScrolledRoot* leafmostASR = asr;
243
0
  if (clip) {
244
0
    leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR);
245
0
  }
246
0
  Maybe<wr::WrClipId> leafmostId = DefineScrollLayers(leafmostASR, aItem, aStackingContext);
247
0
248
0
  // Define all the clips in the item's clip chain, and obtain a clip chain id
249
0
  // for it.
250
0
  clips.mClipChainId = DefineClipChain(clip, auPerDevPixel, aStackingContext);
251
0
252
0
  if (clip && clip->mASR == asr) {
253
0
    // If the clip's ASR is the same as the item's ASR, then we want to use
254
0
    // the clip as the "scrollframe" for the item, as WR will do the right thing
255
0
    // when building the ClipScrollTree and ensure the item scrolls with the
256
0
    // ASR. Note in particular that we don't want to use scroll id of |asr| here
257
0
    // because we might have a situation where there is a stacking context
258
0
    // between |asr| and |aItem|, and if we used |asr|'s scroll id, then WR
259
0
    // would effectively hoist the item out of the stacking context and attach
260
0
    // it directly to |asr|. This can produce incorrect results. Using the clip
261
0
    // instead of the ASR is strictly better because the clip is usually defined
262
0
    // inside the stacking context, and so the item also stays "inside" the
263
0
    // stacking context rather than geting hoisted out. Note that there might
264
0
    // be cases where the clip is also "outside" the stacking context and in
265
0
    // theory that situation might not be handled correctly, but I haven't seen
266
0
    // it in practice so far.
267
0
    const ClipIdMap& cache = mCacheStack.top();
268
0
    auto it = cache.find(clip);
269
0
    MOZ_ASSERT(it != cache.end());
270
0
    clips.mScrollId = Some(it->second);
271
0
  } else if (clip) {
272
0
    // If the clip's ASR is different, then we need to set the scroll id
273
0
    // explicitly to match the desired ASR.
274
0
    FrameMetrics::ViewID viewId = asr
275
0
        ? asr->GetViewId()
276
0
        : FrameMetrics::NULL_SCROLL_ID;
277
0
    Maybe<wr::WrClipId> scrollId =
278
0
        mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
279
0
    MOZ_ASSERT(scrollId.isSome());
280
0
    clips.mScrollId = ClipIdAfterOverride(scrollId);
281
0
  } else {
282
0
    // If we don't have a clip at all, then we don't want to explicitly push
283
0
    // the ASR either, because as with the first clause of this if condition,
284
0
    // the item might get hoisted out of a stacking context that was pushed
285
0
    // between the |asr| and this |aItem|. Instead we just leave clips.mScrollId
286
0
    // empty and things seem to work out.
287
0
    // XXX: there might be cases where things don't just "work out", in which
288
0
    // case we might need to do something smarter here.
289
0
  }
290
0
291
0
  // Now that we have the scroll id and a clip id for the item, push it onto
292
0
  // the WR stack.
293
0
  clips.Apply(mBuilder, auPerDevPixel);
294
0
  mItemClipStack.push(clips);
295
0
296
0
  CLIP_LOG("done setup for %p\n", aItem);
297
0
}
298
299
Maybe<wr::WrClipId>
300
ClipManager::DefineScrollLayers(const ActiveScrolledRoot* aASR,
301
                                nsDisplayItem* aItem,
302
                                const StackingContextHelper& aSc)
303
0
{
304
0
  if (!aASR) {
305
0
    // Recursion base case
306
0
    return Nothing();
307
0
  }
308
0
  FrameMetrics::ViewID viewId = aASR->GetViewId();
309
0
  Maybe<wr::WrClipId> scrollId = mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
310
0
  if (scrollId) {
311
0
    // If we've already defined this scroll layer before, we can early-exit
312
0
    return scrollId;
313
0
  }
314
0
  // Recurse to define the ancestors
315
0
  Maybe<wr::WrClipId> ancestorScrollId = DefineScrollLayers(aASR->mParent, aItem, aSc);
316
0
317
0
  Maybe<ScrollMetadata> metadata = aASR->mScrollableFrame->ComputeScrollMetadata(
318
0
      mManager, aItem->ReferenceFrame(), ContainerLayerParameters(), nullptr);
319
0
  MOZ_ASSERT(metadata);
320
0
  FrameMetrics& metrics = metadata->GetMetrics();
321
0
322
0
  if (!metrics.IsScrollable()) {
323
0
    // This item is a scrolling no-op, skip over it in the ASR chain.
324
0
    return ancestorScrollId;
325
0
  }
326
0
327
0
  LayoutDeviceRect contentRect =
328
0
      metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
329
0
  LayoutDeviceRect clipBounds =
330
0
      LayoutDeviceRect::FromUnknownRect(metrics.GetCompositionBounds().ToUnknownRect());
331
0
  // The content rect that we hand to PushScrollLayer should be relative to
332
0
  // the same origin as the clipBounds that we hand to PushScrollLayer - that
333
0
  // is, both of them should be relative to the stacking context `aSc`.
334
0
  // However, when we get the scrollable rect from the FrameMetrics, the origin
335
0
  // has nothing to do with the position of the frame but instead represents
336
0
  // the minimum allowed scroll offset of the scrollable content. While APZ
337
0
  // uses this to clamp the scroll position, we don't need to send this to
338
0
  // WebRender at all. Instead, we take the position from the composition
339
0
  // bounds.
340
0
  contentRect.MoveTo(clipBounds.TopLeft());
341
0
342
0
  Maybe<wr::WrClipId> parent = ClipIdAfterOverride(ancestorScrollId);
343
0
  scrollId = Some(mBuilder->DefineScrollLayer(viewId, parent,
344
0
      wr::ToRoundedLayoutRect(contentRect),
345
0
      wr::ToRoundedLayoutRect(clipBounds)));
346
0
347
0
  return scrollId;
348
0
}
349
350
Maybe<wr::WrClipChainId>
351
ClipManager::DefineClipChain(const DisplayItemClipChain* aChain,
352
                             int32_t aAppUnitsPerDevPixel,
353
                             const StackingContextHelper& aSc)
354
0
{
355
0
  nsTArray<wr::WrClipId> clipIds;
356
0
  // Iterate through the clips in the current item's clip chain, define them
357
0
  // in WR, and put their IDs into |clipIds|.
358
0
  for (const DisplayItemClipChain* chain = aChain; chain; chain = chain->mParent) {
359
0
    ClipIdMap& cache = mCacheStack.top();
360
0
    auto it = cache.find(chain);
361
0
    if (it != cache.end()) {
362
0
      // Found it in the currently-active cache, so just use the id we have for
363
0
      // it.
364
0
      CLIP_LOG("cache[%p] => %zu\n", chain, it->second.id);
365
0
      clipIds.AppendElement(it->second);
366
0
      continue;
367
0
    }
368
0
    if (!chain->mClip.HasClip()) {
369
0
      // This item in the chain is a no-op, skip over it
370
0
      continue;
371
0
    }
372
0
373
0
    LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
374
0
        chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
375
0
    nsTArray<wr::ComplexClipRegion> wrRoundedRects;
376
0
    chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, aSc, wrRoundedRects);
377
0
378
0
    FrameMetrics::ViewID viewId = chain->mASR
379
0
        ? chain->mASR->GetViewId()
380
0
        : FrameMetrics::NULL_SCROLL_ID;
381
0
    Maybe<wr::WrClipId> scrollId =
382
0
      mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
383
0
    // Before calling DefineClipChain we defined the ASRs by calling
384
0
    // DefineScrollLayers, so we must have a scrollId here.
385
0
    MOZ_ASSERT(scrollId.isSome());
386
0
387
0
    // Define the clip
388
0
    Maybe<wr::WrClipId> parent = ClipIdAfterOverride(scrollId);
389
0
    wr::WrClipId clipId = mBuilder->DefineClip(
390
0
        parent,
391
0
        wr::ToRoundedLayoutRect(clip), &wrRoundedRects);
392
0
    clipIds.AppendElement(clipId);
393
0
    cache[chain] = clipId;
394
0
    CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id);
395
0
  }
396
0
397
0
  // Now find the parent display item's clipchain id
398
0
  Maybe<wr::WrClipChainId> parentChainId;
399
0
  if (!mItemClipStack.empty()) {
400
0
    parentChainId = mItemClipStack.top().mClipChainId;
401
0
  }
402
0
403
0
  // And define the current display item's clipchain using the clips and the
404
0
  // parent. If the current item has no clips of its own, just use the parent
405
0
  // item's clipchain.
406
0
  Maybe<wr::WrClipChainId> chainId;
407
0
  if (clipIds.Length() > 0) {
408
0
    chainId = Some(mBuilder->DefineClipChain(parentChainId, clipIds));
409
0
  } else {
410
0
    chainId = parentChainId;
411
0
  }
412
0
  return chainId;
413
0
}
414
415
ClipManager::~ClipManager()
416
0
{
417
0
  MOZ_ASSERT(!mBuilder);
418
0
  MOZ_ASSERT(mCacheStack.empty());
419
0
  MOZ_ASSERT(mItemClipStack.empty());
420
0
}
421
422
ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
423
                                  const DisplayItemClipChain* aChain,
424
                                  bool aSeparateLeaf)
425
  : mASR(aASR)
426
  , mChain(aChain)
427
  , mSeparateLeaf(aSeparateLeaf)
428
  , mApplied(false)
429
0
{
430
0
}
431
432
void
433
ClipManager::ItemClips::Apply(wr::DisplayListBuilder* aBuilder,
434
                              int32_t aAppUnitsPerDevPixel)
435
0
{
436
0
  MOZ_ASSERT(!mApplied);
437
0
  mApplied = true;
438
0
439
0
  Maybe<wr::LayoutRect> clipLeaf;
440
0
  if (mSeparateLeaf) {
441
0
    MOZ_ASSERT(mChain);
442
0
    clipLeaf.emplace(wr::ToRoundedLayoutRect(LayoutDeviceRect::FromAppUnits(
443
0
      mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
444
0
  }
445
0
446
0
  aBuilder->PushClipAndScrollInfo(mScrollId.ptrOr(nullptr),
447
0
                                  mClipChainId.ptrOr(nullptr),
448
0
                                  clipLeaf);
449
0
}
450
451
void
452
ClipManager::ItemClips::Unapply(wr::DisplayListBuilder* aBuilder)
453
0
{
454
0
  if (mApplied) {
455
0
    mApplied = false;
456
0
    aBuilder->PopClipAndScrollInfo(mScrollId.ptrOr(nullptr));
457
0
  }
458
0
}
459
460
bool
461
ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther)
462
0
{
463
0
  return mASR == aOther.mASR &&
464
0
         mChain == aOther.mChain &&
465
0
         mSeparateLeaf == aOther.mSeparateLeaf;
466
0
}
467
468
void
469
ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther)
470
0
{
471
0
  mScrollId = aOther.mScrollId;
472
0
  mClipChainId = aOther.mClipChainId;
473
0
  mSeparateLeaf = aOther.mSeparateLeaf;
474
0
}
475
476
} // namespace layers
477
} // namespace mozilla