The displayport clip that is applied to sticky items from the enclosing scrollframe can cause sticky items to checkerboard even when they might reasonably be left visible. This is because the displayport clip moves as the enclosing scrollframe is scrolled, but sticky items may remain fixed during such scrolling. The displayport clip can therefore clip out sticky items even if they are "stuck" and should be user-visible. This patch sets a flag to identify when a sticky item is being clipped by the displayport clip, and ensures that it doesn't actually get clipped. In the case where other clips are being applied to the sticky item, we leave the clips unaffected. This allows for other enclosing elements to clip the sticky item as before. Differential Revision: https://phabricator.services.mozilla.com/D68582
419 lines
15 KiB
C++
419 lines
15 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/layers/ClipManager.h"
|
|
|
|
#include "DisplayItemClipChain.h"
|
|
#include "FrameMetrics.h"
|
|
#include "LayersLogging.h"
|
|
#include "mozilla/layers/StackingContextHelper.h"
|
|
#include "mozilla/layers/WebRenderLayerManager.h"
|
|
#include "mozilla/webrender/WebRenderAPI.h"
|
|
#include "nsDisplayList.h"
|
|
#include "nsStyleStructInlines.h"
|
|
#include "UnitTransforms.h"
|
|
|
|
// clang-format off
|
|
#define CLIP_LOG(...)
|
|
//#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__)
|
|
//#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__)
|
|
// clang-format on
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {}
|
|
|
|
void ClipManager::BeginBuild(WebRenderLayerManager* aManager,
|
|
wr::DisplayListBuilder& aBuilder) {
|
|
MOZ_ASSERT(!mManager);
|
|
mManager = aManager;
|
|
MOZ_ASSERT(!mBuilder);
|
|
mBuilder = &aBuilder;
|
|
MOZ_ASSERT(mCacheStack.empty());
|
|
mCacheStack.emplace();
|
|
MOZ_ASSERT(mASROverride.empty());
|
|
MOZ_ASSERT(mItemClipStack.empty());
|
|
}
|
|
|
|
void ClipManager::EndBuild() {
|
|
mBuilder = nullptr;
|
|
mManager = nullptr;
|
|
mCacheStack.pop();
|
|
MOZ_ASSERT(mCacheStack.empty());
|
|
MOZ_ASSERT(mASROverride.empty());
|
|
MOZ_ASSERT(mItemClipStack.empty());
|
|
}
|
|
|
|
void ClipManager::BeginList(const StackingContextHelper& aStackingContext) {
|
|
if (aStackingContext.AffectsClipPositioning()) {
|
|
if (aStackingContext.ReferenceFrameId()) {
|
|
PushOverrideForASR(
|
|
mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR,
|
|
aStackingContext.ReferenceFrameId().ref());
|
|
} else {
|
|
// Start a new cache
|
|
mCacheStack.emplace();
|
|
}
|
|
}
|
|
|
|
ItemClips clips(nullptr, nullptr, false);
|
|
if (!mItemClipStack.empty()) {
|
|
clips.CopyOutputsFrom(mItemClipStack.top());
|
|
}
|
|
|
|
if (aStackingContext.ReferenceFrameId()) {
|
|
clips.mScrollId = aStackingContext.ReferenceFrameId().ref();
|
|
}
|
|
|
|
mItemClipStack.push(clips);
|
|
}
|
|
|
|
void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
|
|
MOZ_ASSERT(!mItemClipStack.empty());
|
|
mBuilder->SetClipChainLeaf(Nothing());
|
|
mItemClipStack.pop();
|
|
|
|
if (aStackingContext.AffectsClipPositioning()) {
|
|
if (aStackingContext.ReferenceFrameId()) {
|
|
PopOverrideForASR(mItemClipStack.empty() ? nullptr
|
|
: mItemClipStack.top().mASR);
|
|
} else {
|
|
MOZ_ASSERT(!mCacheStack.empty());
|
|
mCacheStack.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
|
|
const wr::WrSpatialId& aSpatialId) {
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
|
|
MOZ_ASSERT(spaceAndClip.isSome());
|
|
|
|
CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
|
|
Stringify(aSpatialId.id).c_str());
|
|
|
|
auto it =
|
|
mASROverride.insert({spaceAndClip->space, std::stack<wr::WrSpatialId>()});
|
|
it.first->second.push(aSpatialId);
|
|
|
|
// Start a new cache
|
|
mCacheStack.emplace();
|
|
}
|
|
|
|
void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
|
|
MOZ_ASSERT(!mCacheStack.empty());
|
|
mCacheStack.pop();
|
|
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
|
|
MOZ_ASSERT(spaceAndClip.isSome());
|
|
|
|
auto it = mASROverride.find(spaceAndClip->space);
|
|
CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
|
|
Stringify(it->second.top().id).c_str());
|
|
|
|
it->second.pop();
|
|
if (it->second.empty()) {
|
|
mASROverride.erase(it);
|
|
}
|
|
}
|
|
|
|
wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
|
|
const wr::WrSpatialId& aSpatialId) {
|
|
auto it = mASROverride.find(aSpatialId);
|
|
if (it == mASROverride.end()) {
|
|
return aSpatialId;
|
|
}
|
|
MOZ_ASSERT(!it->second.empty());
|
|
CLIP_LOG("Overriding %zu with %s\n", aSpatialId.id,
|
|
Stringify(it->second.top().id).c_str());
|
|
|
|
return it->second.top();
|
|
}
|
|
|
|
wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayItem* aItem) {
|
|
const DisplayItemClipChain* clip = aItem->GetClipChain();
|
|
const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
|
|
CLIP_LOG("processing item %p (%s) asr %p\n", aItem,
|
|
DisplayItemTypeName(aItem->GetType()), asr);
|
|
|
|
DisplayItemType type = aItem->GetType();
|
|
if (type == DisplayItemType::TYPE_STICKY_POSITION) {
|
|
// For sticky position items, the ASR is computed differently depending
|
|
// on whether the item has a fixed descendant or not. But for WebRender
|
|
// purposes we always want to use the ASR that would have been used if it
|
|
// didn't have fixed descendants, which is stored as the "container ASR" on
|
|
// the sticky item.
|
|
nsDisplayStickyPosition* sticky =
|
|
static_cast<nsDisplayStickyPosition*>(aItem);
|
|
asr = sticky->GetContainerASR();
|
|
|
|
// If the leafmost clip for the sticky item is just the displayport clip,
|
|
// then skip it. This allows sticky items to remain visible even if the
|
|
// rest of the content in the enclosing scrollframe is checkerboarding.
|
|
if (sticky->IsClippedToDisplayPort() && clip && clip->mASR == asr) {
|
|
clip = clip->mParent;
|
|
}
|
|
}
|
|
|
|
// In most cases we can combine the leaf of the clip chain with the clip rect
|
|
// of the display item. This reduces the number of clip items, which avoids
|
|
// some overhead further down the pipeline.
|
|
bool separateLeaf = false;
|
|
if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) {
|
|
// Container display items are not currently supported because the clip
|
|
// rect of a stacking context is not handled the same as normal display
|
|
// items.
|
|
separateLeaf = aItem->GetChildren() == nullptr;
|
|
}
|
|
|
|
ItemClips clips(asr, clip, separateLeaf);
|
|
MOZ_ASSERT(!mItemClipStack.empty());
|
|
if (clips.HasSameInputs(mItemClipStack.top())) {
|
|
// Early-exit because if the clips are the same as aItem's previous sibling,
|
|
// then we don't need to do do the work of popping the old stuff and then
|
|
// pushing it right back on for the new item. Note that if aItem doesn't
|
|
// have a previous sibling, that means BeginList would have been called
|
|
// just before this, which will have pushed a ItemClips(nullptr, nullptr)
|
|
// onto mItemClipStack, so the HasSameInputs check should return false.
|
|
CLIP_LOG("\tearly-exit for %p\n", aItem);
|
|
return mItemClipStack.top().GetSpaceAndClipChain();
|
|
}
|
|
|
|
// Pop aItem's previous sibling's stuff from mBuilder in preparation for
|
|
// pushing aItem's stuff.
|
|
mItemClipStack.pop();
|
|
|
|
// Zoom display items report their bounds etc using the parent document's
|
|
// APD because zoom items act as a conversion layer between the two different
|
|
// APDs.
|
|
int32_t auPerDevPixel;
|
|
if (type == DisplayItemType::TYPE_ZOOM) {
|
|
auPerDevPixel =
|
|
static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
|
|
} else {
|
|
auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
|
|
}
|
|
|
|
// If the leaf of the clip chain is going to be merged with the display item's
|
|
// clip rect, then we should create a clip chain id from the leaf's parent.
|
|
if (separateLeaf) {
|
|
CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n");
|
|
clip = clip->mParent;
|
|
}
|
|
|
|
// There are two ASR chains here that we need to be fully defined. One is the
|
|
// ASR chain pointed to by |asr|. The other is the
|
|
// ASR chain pointed to by clip->mASR. We pick the leafmost
|
|
// of these two chains because that one will include the other. Calling
|
|
// DefineScrollLayers with this leafmost ASR will recursively define all the
|
|
// ASRs that we care about for this item, but will not actually push
|
|
// anything onto the WR stack.
|
|
const ActiveScrolledRoot* leafmostASR = asr;
|
|
if (clip) {
|
|
leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR);
|
|
}
|
|
Maybe<wr::WrSpaceAndClip> leafmostId = DefineScrollLayers(leafmostASR, aItem);
|
|
Unused << leafmostId;
|
|
|
|
// Define all the clips in the item's clip chain, and obtain a clip chain id
|
|
// for it.
|
|
clips.mClipChainId = DefineClipChain(clip, auPerDevPixel);
|
|
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(asr);
|
|
MOZ_ASSERT(spaceAndClip.isSome());
|
|
clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space);
|
|
CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->space.id,
|
|
(int)clips.mScrollId.id);
|
|
|
|
// Now that we have the scroll id and a clip id for the item, push it onto
|
|
// the WR stack.
|
|
clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel);
|
|
auto spaceAndClipChain = clips.GetSpaceAndClipChain();
|
|
mItemClipStack.push(clips);
|
|
|
|
CLIP_LOG("done setup for %p\n", aItem);
|
|
return spaceAndClipChain;
|
|
}
|
|
|
|
Maybe<wr::WrSpaceAndClip> ClipManager::GetScrollLayer(
|
|
const ActiveScrolledRoot* aASR) {
|
|
for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip =
|
|
mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId());
|
|
if (spaceAndClip) {
|
|
return spaceAndClip;
|
|
}
|
|
|
|
// If this ASR doesn't have a scroll ID, then we should check its ancestor.
|
|
// There may not be one defined because the ASR may not be scrollable or we
|
|
// failed to get the scroll metadata.
|
|
}
|
|
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip =
|
|
mBuilder->GetScrollIdForDefinedScrollLayer(
|
|
ScrollableLayerGuid::NULL_SCROLL_ID);
|
|
MOZ_ASSERT(spaceAndClip.isSome());
|
|
return spaceAndClip;
|
|
}
|
|
|
|
Maybe<wr::WrSpaceAndClip> ClipManager::DefineScrollLayers(
|
|
const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) {
|
|
if (!aASR) {
|
|
// Recursion base case
|
|
return Nothing();
|
|
}
|
|
ScrollableLayerGuid::ViewID viewId = aASR->GetViewId();
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip =
|
|
mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
|
|
if (spaceAndClip) {
|
|
// If we've already defined this scroll layer before, we can early-exit
|
|
return spaceAndClip;
|
|
}
|
|
// Recurse to define the ancestors
|
|
Maybe<wr::WrSpaceAndClip> ancestorSpaceAndClip =
|
|
DefineScrollLayers(aASR->mParent, aItem);
|
|
|
|
Maybe<ScrollMetadata> metadata =
|
|
aASR->mScrollableFrame->ComputeScrollMetadata(
|
|
mManager, aItem->ReferenceFrame(), Nothing(), nullptr);
|
|
if (!metadata) {
|
|
MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
|
|
return ancestorSpaceAndClip;
|
|
}
|
|
|
|
FrameMetrics& metrics = metadata->GetMetrics();
|
|
if (!metrics.IsScrollable()) {
|
|
// This item is a scrolling no-op, skip over it in the ASR chain.
|
|
return ancestorSpaceAndClip;
|
|
}
|
|
|
|
nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame;
|
|
nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
|
|
nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->ReferenceFrame());
|
|
float auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
|
|
nsRect scrollPort = scrollableFrame->GetScrollPortRect() + offset;
|
|
LayoutDeviceRect clipBounds =
|
|
LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel);
|
|
|
|
// The content rect that we hand to PushScrollLayer should be relative to
|
|
// the same origin as the clipBounds that we hand to PushScrollLayer -
|
|
// that is, both of them should be relative to the stacking context `aSc`.
|
|
// However, when we get the scrollable rect from the FrameMetrics, the
|
|
// origin has nothing to do with the position of the frame but instead
|
|
// represents the minimum allowed scroll offset of the scrollable content.
|
|
// While APZ uses this to clamp the scroll position, we don't need to send
|
|
// this to WebRender at all. Instead, we take the position from the
|
|
// composition bounds.
|
|
LayoutDeviceRect contentRect =
|
|
metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
|
|
contentRect.MoveTo(clipBounds.TopLeft());
|
|
|
|
Maybe<wr::WrSpaceAndClip> parent = ancestorSpaceAndClip;
|
|
if (parent) {
|
|
parent->space = SpatialIdAfterOverride(parent->space);
|
|
}
|
|
LayoutDevicePoint scrollOffset =
|
|
metrics.GetScrollOffset() * metrics.GetDevPixelsPerCSSPixel();
|
|
return Some(mBuilder->DefineScrollLayer(
|
|
viewId, parent, wr::ToLayoutRect(contentRect),
|
|
wr::ToLayoutRect(clipBounds), wr::ToLayoutPoint(scrollOffset)));
|
|
}
|
|
|
|
Maybe<wr::WrClipChainId> ClipManager::DefineClipChain(
|
|
const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) {
|
|
AutoTArray<wr::WrClipId, 6> clipIds;
|
|
// Iterate through the clips in the current item's clip chain, define them
|
|
// in WR, and put their IDs into |clipIds|.
|
|
for (const DisplayItemClipChain* chain = aChain; chain;
|
|
chain = chain->mParent) {
|
|
ClipIdMap& cache = mCacheStack.top();
|
|
auto it = cache.find(chain);
|
|
if (it != cache.end()) {
|
|
// Found it in the currently-active cache, so just use the id we have for
|
|
// it.
|
|
CLIP_LOG("cache[%p] => %zu\n", chain, it->second.id);
|
|
clipIds.AppendElement(it->second);
|
|
continue;
|
|
}
|
|
if (!chain->mClip.HasClip()) {
|
|
// This item in the chain is a no-op, skip over it
|
|
continue;
|
|
}
|
|
|
|
LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
|
|
chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
|
|
nsTArray<wr::ComplexClipRegion> wrRoundedRects;
|
|
chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects);
|
|
|
|
Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(chain->mASR);
|
|
// Before calling DefineClipChain we defined the ASRs by calling
|
|
// DefineScrollLayers, so we must have a scrollId here.
|
|
MOZ_ASSERT(spaceAndClip.isSome());
|
|
|
|
// Define the clip
|
|
spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space);
|
|
wr::WrClipId clipId = mBuilder->DefineClip(
|
|
spaceAndClip, wr::ToLayoutRect(clip), &wrRoundedRects);
|
|
clipIds.AppendElement(clipId);
|
|
cache[chain] = clipId;
|
|
CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id);
|
|
}
|
|
|
|
if (clipIds.IsEmpty()) {
|
|
return Nothing();
|
|
}
|
|
|
|
return Some(mBuilder->DefineClipChain(clipIds));
|
|
}
|
|
|
|
ClipManager::~ClipManager() {
|
|
MOZ_ASSERT(!mBuilder);
|
|
MOZ_ASSERT(mCacheStack.empty());
|
|
MOZ_ASSERT(mItemClipStack.empty());
|
|
}
|
|
|
|
ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
|
|
const DisplayItemClipChain* aChain,
|
|
bool aSeparateLeaf)
|
|
: mASR(aASR), mChain(aChain), mSeparateLeaf(aSeparateLeaf) {
|
|
mScrollId = wr::wr_root_scroll_node_id();
|
|
}
|
|
|
|
void ClipManager::ItemClips::UpdateSeparateLeaf(
|
|
wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) {
|
|
Maybe<wr::LayoutRect> clipLeaf;
|
|
if (mSeparateLeaf) {
|
|
MOZ_ASSERT(mChain);
|
|
clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
|
|
mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
|
|
}
|
|
|
|
aBuilder.SetClipChainLeaf(clipLeaf);
|
|
}
|
|
|
|
bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) {
|
|
return mASR == aOther.mASR && mChain == aOther.mChain &&
|
|
mSeparateLeaf == aOther.mSeparateLeaf;
|
|
}
|
|
|
|
void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) {
|
|
mScrollId = aOther.mScrollId;
|
|
mClipChainId = aOther.mClipChainId;
|
|
}
|
|
|
|
wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const {
|
|
auto spaceAndClipChain = wr::RootScrollNodeWithChain();
|
|
spaceAndClipChain.space = mScrollId;
|
|
if (mClipChainId) {
|
|
spaceAndClipChain.clip_chain = mClipChainId->id;
|
|
}
|
|
return spaceAndClipChain;
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|