Source code

Revision control

Other Tools

/* -*- 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 */
#include "mozilla/layers/ClipManager.h"
#include "DisplayItemClipChain.h"
#include "FrameMetrics.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) {
mManager = aManager;
mBuilder = &aBuilder;
void ClipManager::EndBuild() {
mBuilder = nullptr;
mManager = nullptr;
void ClipManager::BeginList(const StackingContextHelper& aStackingContext) {
if (aStackingContext.AffectsClipPositioning()) {
if (aStackingContext.ReferenceFrameId()) {
mItemClipStack.empty() ? nullptr :,
} else {
// Start a new cache
ItemClips clips(nullptr, nullptr, false);
if (!mItemClipStack.empty()) {
if (aStackingContext.ReferenceFrameId()) {
clips.mScrollId = aStackingContext.ReferenceFrameId().ref();
void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
if (aStackingContext.AffectsClipPositioning()) {
if (aStackingContext.ReferenceFrameId()) {
PopOverrideForASR(mItemClipStack.empty() ? nullptr
} else {
void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
const wr::WrSpatialId& aSpatialId) {
Maybe<wr::WrSpatialId> space = GetScrollLayer(aASR);
CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, space->id,
auto it = mASROverride.insert({*space, std::stack<wr::WrSpatialId>()});
// Start a new cache
void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
Maybe<wr::WrSpatialId> space = GetScrollLayer(aASR);
auto it = mASROverride.find(*space);
CLIP_LOG("Popping %p override %zu -> %s\n", aASR, space->id,
if (it->second.empty()) {
wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
const wr::WrSpatialId& aSpatialId) {
auto it = mASROverride.find(aSpatialId);
if (it == mASROverride.end()) {
return aSpatialId;
CLIP_LOG("Overriding %zu with %s\n",,
return it->;
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 =
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);
if (clips.HasSameInputs( {
// 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);
// Pop aItem's previous sibling's stuff from mBuilder in preparation for
// pushing aItem's stuff.
// 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 =
} 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::WrSpatialId> 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::WrSpatialId> space = GetScrollLayer(asr);
clips.mScrollId = SpatialIdAfterOverride(*space);
CLIP_LOG("\tassigning %d -> %d\n", (int)space->id, (int);
// 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();
CLIP_LOG("done setup for %p\n", aItem);
return spaceAndClipChain;
Maybe<wr::WrSpatialId> ClipManager::GetScrollLayer(
const ActiveScrolledRoot* aASR) {
for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
Maybe<wr::WrSpatialId> space =
if (space) {
return space;
// 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::WrSpatialId> space = mBuilder->GetScrollIdForDefinedScrollLayer(
return space;
Maybe<wr::WrSpatialId> ClipManager::DefineScrollLayers(
const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) {
if (!aASR) {
// Recursion base case
return Nothing();
ScrollableLayerGuid::ViewID viewId = aASR->GetViewId();
Maybe<wr::WrSpatialId> space =
if (space) {
// If we've already defined this scroll layer before, we can early-exit
return space;
// Recurse to define the ancestors
Maybe<wr::WrSpatialId> ancestorSpace =
DefineScrollLayers(aASR->mParent, aItem);
Maybe<ScrollMetadata> metadata =
mManager, aItem->ReferenceFrame(), Nothing(), nullptr);
if (!metadata) {
MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
return ancestorSpace;
FrameMetrics& metrics = metadata->GetMetrics();
if (!metrics.IsScrollable()) {
// This item is a scrolling no-op, skip over it in the ASR chain.
return ancestorSpace;
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();
Maybe<wr::WrSpatialId> parent = ancestorSpace;
if (parent) {
*parent = SpatialIdAfterOverride(*parent);
// The external scroll offset is accumulated into the local space positions of
// display items inside WR, so that the elements hash (intern) to the same
// content ID for quick comparisons. To avoid invalidations when the
// auPerDevPixel is not a round value, round here directly from app units.
// This guarantees we won't introduce any inaccuracy in the external scroll
// offset passed to WR.
LayoutDevicePoint scrollOffset = LayoutDevicePoint::FromAppUnitsRounded(
scrollableFrame->GetScrollPosition(), auPerDevPixel);
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> allClipIds;
// 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 =;
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] => hit\n", chain);
if (!chain->mClip.HasClip()) {
// This item in the chain is a no-op, skip over it
LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
AutoTArray<wr::ComplexClipRegion, 6> wrRoundedRects;
chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects);
Maybe<wr::WrSpatialId> space = GetScrollLayer(chain->mASR);
// Before calling DefineClipChain we defined the ASRs by calling
// DefineScrollLayers, so we must have a scrollId here.
// Define the clip
*space = SpatialIdAfterOverride(*space);
AutoTArray<wr::WrClipId, 4> chainClipIds;
auto rectClipId = mBuilder->DefineRectClip(space, wr::ToLayoutRect(clip));
CLIP_LOG("cache[%p] <= %zu\n", chain, rectClipId);
for (const auto& complexClip : wrRoundedRects) {
auto complexClipId = mBuilder->DefineRoundedRectClip(space, complexClip);
CLIP_LOG("cache[%p] <= %zu\n", chain, complexClipId);
cache[chain] = chainClipIds.Clone();
if (allClipIds.IsEmpty()) {
return Nothing();
return Some(mBuilder->DefineClipChain(allClipIds));
ClipManager::~ClipManager() {
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) {
mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
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(); = mScrollId;
if (mClipChainId) {
spaceAndClipChain.clip_chain = mClipChainId->id;
return spaceAndClipChain;
} // namespace layers
} // namespace mozilla