Bug 1247854 - Apply the correct scroll clips to the nsDisplayTransform and nsDisplayPerspective of a scrolled perspective item. r?mattwoodrow draft
authorMarkus Stange <mstange@themasta.com>
Fri, 26 Feb 2016 01:29:41 +0100
changeset 334895 c3495eb380d225de43a49676bbe84acb09e0d5e5
parent 334894 99c1b5e08db75bd2ebbe9583176579002fe61ae5
child 515019 d4047ad53bdaf592a3bdb7229ec4ad7a36b75a60
push id11663
push usermstange@themasta.com
push dateFri, 26 Feb 2016 13:40:10 +0000
reviewersmattwoodrow
bugs1247854
milestone47.0a1
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
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
layout/reftests/async-scrolling/perspective-scrolling-1-ref.html
layout/reftests/async-scrolling/perspective-scrolling-1.html
layout/reftests/async-scrolling/perspective-scrolling-2-ref.html
layout/reftests/async-scrolling/perspective-scrolling-2.html
layout/reftests/async-scrolling/perspective-scrolling-3-ref.html
layout/reftests/async-scrolling/perspective-scrolling-3.html
layout/reftests/async-scrolling/reftest.list
--- 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