Bug 1247854 - Apply the correct scroll clips to the nsDisplayTransform and nsDisplayPerspective of a scrolled perspective item. r?mattwoodrow
I've decided to fix this in a very explicit way. The only "magic" part that's
left is how we decide that the AGR of the perspective item is outside the
scrolled frame (and I'm not sure myself how that works).
I didn't want to change what scroll clips we set on what items, because the
scroll clip really belongs on the perspective item, because that's the item
that needs to be clipped, and it should also be the item that should be
scrolled if it weren't for the fact that APZ wouldn't know that it should
apply the perspective transform before the APZ transform.
MozReview-Commit-ID: BBw8VPohQI4
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1055,16 +1055,17 @@ public:
nscolor aBackgroundColor,
const DisplayItemScrollClip* aContainerScrollClip) :
mBuilder(aBuilder), mManager(aManager),
mLayerBuilder(aLayerBuilder),
mContainerFrame(aContainerFrame),
mContainerLayer(aContainerLayer),
mContainerBounds(aContainerBounds),
mContainerScrollClip(aContainerScrollClip),
+ mScrollClipForPerspectiveChild(aParameters.mScrollClipForPerspectiveChild),
mParameters(aParameters),
mPaintedLayerDataTree(*this, aBackgroundColor),
mFlattenToSingleLayer(aFlattenToSingleLayer)
{
nsPresContext* presContext = aContainerFrame->PresContext();
mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
mContainerReferenceFrame =
const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
@@ -1360,16 +1361,17 @@ protected:
LayerManager* mManager;
FrameLayerBuilder* mLayerBuilder;
nsIFrame* mContainerFrame;
nsIFrame* mContainerReferenceFrame;
AnimatedGeometryRoot* mContainerAnimatedGeometryRoot;
ContainerLayer* mContainerLayer;
nsRect mContainerBounds;
const DisplayItemScrollClip* mContainerScrollClip;
+ const DisplayItemScrollClip* mScrollClipForPerspectiveChild;
DebugOnly<nsRect> mAccumulatedChildBounds;
ContainerLayerParameters mParameters;
/**
* The region of PaintedLayers that should be invalidated every time
* we recycle one.
*/
nsIntRegion mInvalidPaintedContent;
PaintedLayerDataTree mPaintedLayerDataTree;
@@ -3934,16 +3936,32 @@ ContainerState::ProcessDisplayItems(nsDi
// Note that items without their own layers can't be skipped this
// way, since their PaintedLayer may decide it wants to draw them
// into its buffer even if they're currently covered.
if (itemVisibleRect.IsEmpty() &&
!item->ShouldBuildLayerEvenIfInvisible(mBuilder)) {
continue;
}
+ if (mScrollClipForPerspectiveChild) {
+ // We are the single transform child item of an nsDisplayPerspective.
+ // Our parent forwarded a scroll clip to us. Pick it up.
+ // We do this after any clipping has been applied, because this
+ // forwarded scroll clip is only used for scrolling (in the form of
+ // APZ frame metrics), not for clipping - the clip still belongs on
+ // the perspective item.
+ MOZ_ASSERT(itemType == nsDisplayItem::TYPE_TRANSFORM);
+ MOZ_ASSERT(!itemScrollClip);
+ MOZ_ASSERT(!agrScrollClip);
+ MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(mContainerScrollClip,
+ mScrollClipForPerspectiveChild));
+ itemScrollClip = mScrollClipForPerspectiveChild;
+ agrScrollClip = mScrollClipForPerspectiveChild;
+ }
+
// 3D-transformed layers don't necessarily draw in the order in which
// they're added to their parent container layer.
bool mayDrawOutOfOrder = itemType == nsDisplayItem::TYPE_TRANSFORM &&
(item->Frame()->Combines3DTransformWithAncestors() ||
item->Frame()->Extend3DContext());
// Let mPaintedLayerDataTree know about this item, so that
// FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
@@ -3987,16 +4005,31 @@ ContainerState::ProcessDisplayItems(nsDi
// Time will tell whether this is good enough, or whether we need to do
// something more sophisticated here.
mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
&itemVisibleRect, uniformColorPtr);
}
mParameters.mBackgroundColor = uniformColor;
mParameters.mScrollClip = agrScrollClip;
+ mParameters.mScrollClipForPerspectiveChild = nullptr;
+
+ if (itemType == nsDisplayItem::TYPE_PERSPECTIVE) {
+ // Perspective items have a single child item, an nsDisplayTransform.
+ // If the perspective item is scrolled, but the perspective-inducing
+ // frame is outside the scroll frame (indicated by this items AGR
+ // being outside that scroll frame), we have to take special care to
+ // make APZ scrolling work properly. APZ needs us to put the scroll
+ // frame's FrameMetrics on our child transform ContainerLayer instead.
+ // Our agrScrollClip is the scroll clip that's applicable to our
+ // perspective frame, so it won't be the scroll clip for the scrolled
+ // frame in the case that we care about, and we'll forward that scroll
+ // clip to our child.
+ mParameters.mScrollClipForPerspectiveChild = itemScrollClip;
+ }
// Just use its layer.
// Set layerContentsVisibleRect.width/height to -1 to indicate we
// currently don't know. If BuildContainerLayerFor gets called by
// item->BuildLayer, this will be set to a proper rect.
nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
mParameters.mLayerContentsVisibleRect = &layerContentsVisibleRect;
RefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, mParameters);
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -51,43 +51,46 @@ public:
struct ContainerLayerParameters {
ContainerLayerParameters()
: mXScale(1)
, mYScale(1)
, mLayerContentsVisibleRect(nullptr)
, mBackgroundColor(NS_RGBA(0,0,0,0))
, mScrollClip(nullptr)
+ , mScrollClipForPerspectiveChild(nullptr)
, mInTransformedSubtree(false)
, mInActiveTransformedSubtree(false)
, mDisableSubpixelAntialiasingInDescendants(false)
, mInLowPrecisionDisplayPort(false)
, mForEventsOnly(false)
{}
ContainerLayerParameters(float aXScale, float aYScale)
: mXScale(aXScale)
, mYScale(aYScale)
, mLayerContentsVisibleRect(nullptr)
, mBackgroundColor(NS_RGBA(0,0,0,0))
, mScrollClip(nullptr)
+ , mScrollClipForPerspectiveChild(nullptr)
, mInTransformedSubtree(false)
, mInActiveTransformedSubtree(false)
, mDisableSubpixelAntialiasingInDescendants(false)
, mInLowPrecisionDisplayPort(false)
, mForEventsOnly(false)
{}
ContainerLayerParameters(float aXScale, float aYScale,
const nsIntPoint& aOffset,
const ContainerLayerParameters& aParent)
: mXScale(aXScale)
, mYScale(aYScale)
, mLayerContentsVisibleRect(nullptr)
, mOffset(aOffset)
, mBackgroundColor(aParent.mBackgroundColor)
, mScrollClip(aParent.mScrollClip)
+ , mScrollClipForPerspectiveChild(aParent.mScrollClipForPerspectiveChild)
, mInTransformedSubtree(aParent.mInTransformedSubtree)
, mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree)
, mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
, mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort)
, mForEventsOnly(aParent.mForEventsOnly)
{}
float mXScale, mYScale;
@@ -108,16 +111,20 @@ struct ContainerLayerParameters {
nsIntPoint mOffset;
LayerIntPoint Offset() const {
return LayerIntPoint::FromUnknownPoint(mOffset);
}
nscolor mBackgroundColor;
const DisplayItemScrollClip* mScrollClip;
+
+ // usually nullptr, except when building children of an nsDisplayPerspective
+ const DisplayItemScrollClip* mScrollClipForPerspectiveChild;
+
bool mInTransformedSubtree;
bool mInActiveTransformedSubtree;
bool mDisableSubpixelAntialiasingInDescendants;
bool mInLowPrecisionDisplayPort;
bool mForEventsOnly;
/**
* When this is false, PaintedLayer coordinates are drawn to with an integer
* translation and the scale in mXScale/mYScale.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-1-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Perspective scrolling</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ height: 100%;
+ perspective: 1px;
+ overflow: auto;
+ margin: 0;
+}
+
+div {
+ height: 4000px;
+ margin: 200px 100px;
+ border: 10px solid black;
+}
+
+</style>
+
+<div></div>
+
+<script>
+
+document.body.scrollTop = 100;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Perspective scrolling</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ height: 100%;
+ perspective: 1px;
+ overflow: auto;
+ margin: 0;
+}
+
+div {
+ transform-style: preserve-3d;
+ height: 4000px;
+ margin: 200px 100px;
+ border: 10px solid black;
+}
+
+</style>
+
+<body reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="100">
+
+<div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-2-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Perspective in unscrolled state</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ height: 100%;
+ perspective: 1px;
+ overflow: auto;
+ margin: 0;
+}
+
+div {
+ height: 4000px;
+ margin: 200px 100px 200px;
+ border: 10px solid black;
+}
+
+</style>
+
+<div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Perspective in unscrolled state</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ margin: 0;
+ height: 100%;
+ perspective: 1px;
+ perspective-origin: top left;
+ overflow: auto;
+}
+
+div {
+ transform: translateZ(-1px) scale(2);
+ transform-origin: -100px -200px;
+ margin: 200px 100px;
+ height: 4000px;
+ border: 10px solid black;
+}
+
+</style>
+
+<body reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="0"> <!-- no async scrolling -->
+
+<div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-3-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Perspective in scrolled state</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ height: 100%;
+ perspective: 1px;
+ overflow: auto;
+ margin: 0;
+}
+
+div {
+ height: 4000px;
+ margin: 300px 100px 100px;
+ border: 10px solid black;
+}
+
+</style>
+
+<div></div>
+
+<script>
+
+document.body.scrollTop = 200;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/perspective-scrolling-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Perspective in scrolled state</title>
+
+<style>
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ margin: 0;
+ height: 100%;
+ perspective: 1px;
+ perspective-origin: top left;
+ overflow: auto;
+}
+
+div {
+ transform: translateZ(-1px) scale(2);
+ transform-origin: -100px -200px;
+ margin: 200px 100px;
+ height: 4000px;
+ border: 10px solid black;
+}
+
+</style>
+
+<body reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="200">
+
+<div></div>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -31,16 +31,19 @@ skip-if(!asyncPan) == position-fixed-ifr
fuzzy-if(skiaContent,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
skip-if(!asyncPan) == position-sticky-transformed.html position-sticky-transformed-ref.html
skip-if(!asyncPan) == offscreen-prerendered-active-opacity.html offscreen-prerendered-active-opacity-ref.html
fuzzy-if(Android,6,4) skip-if(!asyncPan) == offscreen-clipped-blendmode-1.html offscreen-clipped-blendmode-ref.html
fuzzy-if(Android,6,4) skip-if(!asyncPan) == offscreen-clipped-blendmode-2.html offscreen-clipped-blendmode-ref.html
fuzzy-if(Android,6,4) skip == offscreen-clipped-blendmode-3.html offscreen-clipped-blendmode-ref.html # bug 8724034 - wrong AGR on mix-blend-mode item
fuzzy-if(Android,6,4) skip-if(!asyncPan) == offscreen-clipped-blendmode-4.html offscreen-clipped-blendmode-ref.html
+fuzzy-if(Android,7,4) skip-if(!asyncPan) == perspective-scrolling-1.html perspective-scrolling-1-ref.html
+fuzzy-if(Android,7,4) skip-if(!asyncPan) == perspective-scrolling-2.html perspective-scrolling-2-ref.html
+fuzzy-if(Android,7,4) skip-if(!asyncPan) == perspective-scrolling-3.html perspective-scrolling-3-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)
skip-if(!asyncPan) == checkerboard-1.html checkerboard-1-ref.html
skip-if(!asyncPan) == checkerboard-2.html checkerboard-2-ref.html