Bug 1422057 - Extend the clipchain of a display item to the ancestor's clipchain if it is a strict superset. r?mstange
See the documentation in the added function for details.
MozReview-Commit-ID: 4R0WaSy0tm2
--- a/gfx/layers/wr/ScrollingLayersHelper.cpp
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -74,16 +74,18 @@ ScrollingLayersHelper::EndList(const Sta
void
ScrollingLayersHelper::BeginItem(nsDisplayItem* aItem,
const StackingContextHelper& aStackingContext)
{
SLH_LOG("processing item %p\n", aItem);
const DisplayItemClipChain* clip = aItem->GetClipChain();
+ clip = ExtendChain(clip);
+
ItemClips clips(aItem->GetActiveScrolledRoot(), clip);
MOZ_ASSERT(!mItemClipStack.empty());
if (clips.HasSameInputs(mItemClipStack.back())) {
// Early-exit because if the clips are the same 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.
SLH_LOG("early-exit for %p\n", aItem);
return;
@@ -471,16 +473,81 @@ ScrollingLayersHelper::RecurseAndDefineA
mBuilder->DefineScrollLayer(scrollId, ancestorIds.first, ancestorIds.second,
aSc.ToRelativeLayoutRect(contentRect),
aSc.ToRelativeLayoutRect(clipBounds));
ids.first = Some(scrollId);
return ids;
}
+const DisplayItemClipChain*
+ScrollingLayersHelper::ExtendChain(const DisplayItemClipChain* aClip)
+{
+ // The intent of this function is to handle Gecko display list scenarios
+ // like so:
+ // nsDisplayFixedPosition with clip chain A -> B -> nullptr
+ // nsDisplayBackgroundColor with clip chain B -> nullptr
+ //
+ // The specific types are not relevant, but the important part is that there
+ // is a display item whose clip chain is a subchain of the enclosing display
+ // item.
+ //
+ // The semantics of the gecko display items means that the two clip chains
+ // should be intersected for the child display item; because one clip chain
+ // is a subset of the other the intersection comes out to be clip chain from
+ // the parent.
+ // However, WebRender doesn't let us (yet) intersect clip chains, so one of
+ // the jobs of ScrollingLayersHelper is to generate as-good-as-possible clip
+ // chains by merging the necessary clips into a new clip chain. In the example
+ // above, we really want the nsDisplayBackgroundColor to use the clip chain
+ // from A rather than from B in order to get the right clips, and this
+ // function "extends" an input of |B| and returns |A|.
+
+ if (!aClip) {
+ return aClip;
+ }
+ // mItemClipStack has the clips that we pushed for ancestor display items.
+ size_t clipDepth = mItemClipStack.size();
+ MOZ_ASSERT(clipDepth > 0);
+ while (--clipDepth > 0) {
+ const DisplayItemClipChain* enclosingClip = mItemClipStack[clipDepth - 1].mChain;
+ if (!enclosingClip) {
+ // This is a special case; if an item has a nullptr clipchain it basically
+ // inherits the clipchain from its ancestor, so let's skip to that.
+ continue;
+ }
+ if (aClip == enclosingClip) {
+ // The ancestor clip chain is the same as our item's clip chain, so
+ // we're done. Note that because this function will have run on the
+ // ancestor as well, we can be assured via induction that there is no
+ // ancestor beyond this one that has a longer superset-clipchain.
+ return aClip;
+ }
+ const ClipIdMap& cache = mCacheStack.back();
+ if (cache.find(enclosingClip) == cache.end()) {
+ // The ancestor clip chain isn't in our clip cache, which means there
+ // must be a reference frame between the ancestor item and this item.
+ // Therefore we cannot use the enclosing clip, so let's abort
+ return aClip;
+ }
+ for (const DisplayItemClipChain* i = enclosingClip->mParent; i; i = i->mParent) {
+ if (i == aClip) {
+ // aClip is contained inside the enclosingClip clipchain. Since the
+ // enclosingClip also applies to the item we're currently processing,
+ // we should use that as it is a better approximation to the real clip
+ // set that applies to the item.
+ SLH_LOG("extending clip %p to %p\n", aClip, enclosingClip);
+ return enclosingClip;
+ }
+ }
+ break;
+ }
+ return aClip;
+}
+
Maybe<ScrollingLayersHelper::ClipAndScroll>
ScrollingLayersHelper::EnclosingClipAndScroll() const
{
for (auto it = mItemClipStack.rbegin(); it != mItemClipStack.rend(); it++) {
if (it->mClipAndScroll) {
return it->mClipAndScroll;
}
// If an entry in the stack pushed a single clip or scroll without pushing
--- a/gfx/layers/wr/ScrollingLayersHelper.h
+++ b/gfx/layers/wr/ScrollingLayersHelper.h
@@ -65,16 +65,17 @@ private:
std::pair<Maybe<FrameMetrics::ViewID>, Maybe<wr::WrClipId>>
RecurseAndDefineAsr(nsDisplayItem* aItem,
const ActiveScrolledRoot* aAsr,
const DisplayItemClipChain* aChain,
int32_t aAppUnitsPerDevPixel,
const StackingContextHelper& aSc);
+ const DisplayItemClipChain* ExtendChain(const DisplayItemClipChain* aClip);
Maybe<ClipAndScroll> EnclosingClipAndScroll() const;
// Note: two DisplayItemClipChain* A and B might actually be "equal" (as per
// DisplayItemClipChain::Equal(A, B)) even though they are not the same pointer
// (A != B). In this hopefully-rare case, they will get separate entries
// in this map when in fact we could collapse them. However, to collapse
// them involves writing a custom hash function for the pointer type such that
// A and B hash to the same things whenever DisplayItemClipChain::Equal(A, B)