Bug 1238564 - Anticipate async scrolling when computing the scroll clipped bounds of a display list. r?roc
This makes sure that for example the bounds of an opacity item are not empty if
the opacity item contains a scroll frame whose contents are currently scrolled
offscreen but still inside that scroll frame's display port.
On its own, this changeset causes test failures due to missed optimizations
because the bounds of many opacity items are now too large. That's because of
the way we're setting scroll clips on opacity items at the moment: Even if the
opacity is inside a scroll frame, we're currently only setting that scroll
frame's scroll clip on the opacity item's contents, not on the opacity item
itself, because the opacity item might also contain other items that are not
scrolled by this scroll frame.
The next patch in this bug will make us only do that when necessary.
MozReview-Commit-ID: 9TtcJ7eQE7U
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -3885,21 +3885,27 @@ ContainerState::ProcessDisplayItems(nsDi
bool dummy;
if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
bounds = item->GetBounds(mBuilder, &dummy);
if (itemClip.HasClip()) {
bounds.IntersectRect(bounds, itemClip.GetClipRect());
}
}
bounds = fixedToViewportClip.ApplyNonRoundedIntersection(bounds);
- for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
- scrollClip && scrollClip != mContainerScrollClip;
- scrollClip = scrollClip->mParent) {
- if (scrollClip->mClip) {
- bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds);
+ if (!bounds.IsEmpty()) {
+ for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
+ scrollClip && scrollClip != mContainerScrollClip;
+ scrollClip = scrollClip->mParent) {
+ if (scrollClip->mClip) {
+ if (scrollClip->mIsAsyncScrollable) {
+ bounds = scrollClip->mClip->GetClipRect();
+ } else {
+ bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds);
+ }
+ }
}
}
((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, bounds);
#endif
nsIntRect itemVisibleRect = itemDrawRect;
if (!shouldFixToViewport) {
// We haven't computed visibility at this point, so item->GetVisibleRect()
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1466,17 +1466,31 @@ nsDisplayList::GetBounds(nsDisplayListBu
return bounds;
}
nsRect
nsDisplayList::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const {
nsRect bounds;
for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
- bounds.UnionRect(bounds, i->GetScrollClippedBoundsUpTo(aBuilder, aIncludeScrollClipsUpTo));
+ nsRect r = i->GetClippedBounds(aBuilder);
+ if (r.IsEmpty()) {
+ continue;
+ }
+ for (auto* sc = i->ScrollClip(); sc && sc != aIncludeScrollClipsUpTo; sc = sc->mParent) {
+ if (sc->mClip && sc->mClip->HasClip()) {
+ if (sc->mIsAsyncScrollable) {
+ // Assume the item can move anywhere in the scroll clip's clip rect.
+ r = sc->mClip->GetClipRect();
+ } else {
+ r = sc->mClip->ApplyNonRoundedIntersection(r);
+ }
+ }
+ }
+ bounds.UnionRect(bounds, r);
}
return bounds;
}
nsRect
nsDisplayList::GetVisibleRect() const {
nsRect result;
for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
@@ -2255,31 +2269,16 @@ nsRect
nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder)
{
bool snap;
nsRect r = GetBounds(aBuilder, &snap);
return GetClip().ApplyNonRoundedIntersection(r);
}
nsRect
-nsDisplayItem::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
- const DisplayItemScrollClip* aIncludeScrollClipsUpTo)
-{
- nsRect r = GetClippedBounds(aBuilder);
- for (const DisplayItemScrollClip* sc = mScrollClip;
- sc && sc != aIncludeScrollClipsUpTo;
- sc = sc->mParent) {
- if (sc->mClip) {
- r = sc->mClip->ApplyNonRoundedIntersection(r);
- }
- }
- return r;
-}
-
-nsRect
nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
{
*aSnap = true;
return mBounds;
}
void
nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1423,27 +1423,16 @@ public:
return false;
}
/**
* Returns the result of GetBounds intersected with the item's clip.
* The intersection is approximate since rounded corners are not taking into
* account.
*/
nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder);
- /**
- * Returns the result of GetClippedBounds, intersected with the item's
- * scroll clips. The item walks up its chain of scroll clips, *not* crossing
- * stacking contexts, applying each scroll clip until aIncludeScrollClipsUpTo
- * is reached. aIncludeScrollClipsUpTo is *not* applied.
- * The intersection is approximate since rounded corners are not taking into
- * account.
- */
- nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
- const DisplayItemScrollClip* aIncludeScrollClipsUpTo);
-
nsRect GetBorderRect() {
return nsRect(ToReferenceFrame(), Frame()->GetSize());
}
nsRect GetPaddingRect() {
return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame();
}
nsRect GetContentRect() {
return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
@@ -2150,17 +2139,24 @@ public:
nsRenderingContext* aCtx,
uint32_t aFlags);
/**
* Get the bounds. Takes the union of the bounds of all children.
* The result is not cached.
*/
nsRect GetBounds(nsDisplayListBuilder* aBuilder) const;
/**
- * Return the union of the scroll clipped bounds of all children.
+ * Return the union of the scroll clipped bounds of all children. To get the
+ * scroll clipped bounds of a child item, we start with the item's clipped
+ * bounds and walk its scroll clip chain up to (but not including)
+ * aIncludeScrollClipsUpTo, and take each scroll clip into account. For
+ * scroll clips from async scrollable frames we assume that the item can move
+ * anywhere inside that scroll frame.
+ * In other words, the return value from this method includes all pixels that
+ * could potentially be covered by items in this list under async scrolling.
*/
nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const;
/**
* Find the topmost display item that returns a non-null frame, and return
* the frame.
*/
void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -49,16 +49,17 @@
#include "ImageLayers.h"
#include "nsPluginInstanceOwner.h"
#ifdef XP_WIN
#include "gfxWindowsNativeDrawing.h"
#include "gfxWindowsSurface.h"
#endif
+#include "DisplayItemScrollClip.h"
#include "Layers.h"
#include "ReadbackLayer.h"
#include "ImageContainer.h"
// accessibility support
#ifdef ACCESSIBILITY
#include "nsAccessibilityService.h"
#endif
@@ -997,16 +998,29 @@ void
nsDisplayPlugin::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx)
{
nsPluginFrame* f = static_cast<nsPluginFrame*>(mFrame);
bool snap;
f->PaintPlugin(aBuilder, *aCtx, mVisibleRect, GetBounds(aBuilder, &snap));
}
+static nsRect
+GetClippedBoundsIncludingAllScrollClips(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder)
+{
+ nsRect r = aItem->GetClippedBounds(aBuilder);
+ for (auto* sc = aItem->ScrollClip(); sc; sc = sc->mParent) {
+ if (sc->mClip) {
+ r = sc->mClip->ApplyNonRoundedIntersection(r);
+ }
+ }
+ return r;
+}
+
bool
nsDisplayPlugin::ComputeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion)
{
if (aBuilder->IsForPluginGeometry()) {
nsPluginFrame* f = static_cast<nsPluginFrame*>(mFrame);
if (!aBuilder->IsInTransform() || f->IsPaintedByGecko()) {
// Since transforms induce reference frames, we don't need to worry
@@ -1017,17 +1031,17 @@ nsDisplayPlugin::ComputeVisibility(nsDis
ReferenceFrame()->PresContext()->AppUnitsPerDevPixel();
f->mNextConfigurationBounds = LayoutDeviceIntRect::FromUnknownRect(
rAncestor.ToNearestPixels(appUnitsPerDevPixel));
nsRegion visibleRegion;
// Apply all scroll clips when computing the clipped bounds of this item.
// We hide windowed plugins during APZ scrolling, so there never is an
// async transform that we need to take into account when clipping.
- visibleRegion.And(*aVisibleRegion, GetScrollClippedBoundsUpTo(aBuilder, nullptr));
+ visibleRegion.And(*aVisibleRegion, GetClippedBoundsIncludingAllScrollClips(this, aBuilder));
// Make visibleRegion relative to f
visibleRegion.MoveBy(-ToReferenceFrame());
f->mNextConfigurationClipRegion.Clear();
for (auto iter = visibleRegion.RectIter(); !iter.Done(); iter.Next()) {
nsRect rAncestor =
nsLayoutUtils::TransformFrameRectToAncestor(f, iter.Get(), ReferenceFrame());
LayoutDeviceIntRect rPixels =
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>Reference: Active opacity should be rendered if it's inside the display port, even if it's currently offscreen</title>
+
+<style>
+
+#scrollbox {
+ border: 1px solid black;
+ width: 200px;
+ height: 200px;
+ overflow: hidden;
+}
+
+#scrolledContent {
+ height: 1000px;
+}
+
+#opacity {
+ will-change: opacity;
+ opacity: 0.5;
+ margin-top: 250px;
+ width: 100px;
+ height: 100px;
+ box-sizing: border-box;
+ border: 1px solid blue;
+}
+
+
+</style>
+
+<div id="scrollbox">
+ <div id="scrolledContent">
+ <div id="opacity"></div>
+ </div>
+</div>
+
+<script>
+
+document.getElementById("scrollbox").scrollTop = 150;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html reftest-async-scroll>
+<meta charset="utf-8">
+<title>Active opacity should be rendered if it's inside the display port, even if it's currently offscreen</title>
+
+<style>
+
+.scrollbox {
+ border: 1px solid black;
+ width: 200px;
+ height: 200px;
+ overflow: hidden;
+}
+
+.scrolledContent {
+ height: 1000px;
+}
+
+.opacity {
+ will-change: opacity;
+ opacity: 0.5;
+ margin-top: 250px;
+ width: 100px;
+ height: 100px;
+ box-sizing: border-box;
+ border: 1px solid blue;
+}
+
+
+</style>
+
+<div class="scrollbox"
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="200" reftest-displayport-h="1000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="150">
+ <div class="scrolledContent">
+ <div class="opacity"></div>
+ </div>
+</div>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -1,15 +1,15 @@
-== bg-fixed-1.html bg-fixed-1-ref.html
-== bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
-== bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
-== bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
-== bg-fixed-child.html bg-fixed-child-ref.html
-== bg-fixed-child-clip-1.html bg-fixed-child-clip-ref.html
-== bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
+skip-if(!asyncPan) == bg-fixed-1.html bg-fixed-1-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
+skip-if(!asyncPan) == bg-fixed-child.html bg-fixed-child-ref.html
+skip-if(!asyncPan) == bg-fixed-child-clip-1.html bg-fixed-child-clip-ref.html
+skip-if(!asyncPan) == bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
fuzzy(1,246) fuzzy-if(skiaContent,2,160) skip-if(!asyncPan) == bg-fixed-child-mask.html bg-fixed-child-mask-ref.html
== bg-fixed-in-opacity.html bg-fixed-in-opacity-ref.html
== bg-fixed-child-no-culling.html bg-fixed-child-no-culling-ref.html
fuzzy-if(B2G,2,5366) fuzzy-if(Android,2,4000) fuzzy-if(browserIsRemote&&cocoaWidget,2,179524) fuzzy-if(browserIsRemote&&winWidget,1,74590) skip-if(!asyncPan) == bg-fixed-transformed-image.html bg-fixed-transformed-image-ref.html
== element-1.html element-1-ref.html
pref(layers.force-active,true) == iframe-1.html iframe-1-ref.html
== nested-1.html nested-1-ref.html
== nested-2.html nested-2-ref.html
@@ -26,16 +26,17 @@ fuzzy-if(skiaContent,1,32000) fuzzy-if(b
== sticky-pos-scrollable-1.html sticky-pos-scrollable-1-ref.html
== fixed-pos-scrollable-1.html fixed-pos-scrollable-1-ref.html
== culling-1.html culling-1-ref.html
== position-fixed-iframe-1.html position-fixed-iframe-1-ref.html
== position-fixed-iframe-2.html position-fixed-iframe-2-ref.html
fuzzy-if(skiaContent||(browserIsRemote&&cocoaWidget),1,10000) skip-if(!asyncPan) == position-fixed-in-scroll-container.html position-fixed-in-scroll-container-ref.html
fuzzy(1,60000) skip-if(!asyncPan) == group-opacity-surface-size-1.html group-opacity-surface-size-1-ref.html
== position-sticky-transformed.html position-sticky-transformed-ref.html
+== offscreen-prerendered-active-opacity.html offscreen-prerendered-active-opacity-ref.html
# for the following tests, we want to disable the low-precision buffer
# as it will expand the displayport beyond what the test specifies in
# its reftest-displayport attributes, and interfere with where we expect
# checkerboarding to occur
default-preferences pref(layers.low-precision-buffer,false)
== checkerboard-1.html checkerboard-1-ref.html
== checkerboard-2.html checkerboard-2-ref.html