Bug 1247854 - Apply the correct scroll clips to the nsDisplayTransform and nsDisplayPerspective of a scrolled perspective item. r=mattwoodrow a=ritu
authorMarkus Stange <mstange@themasta.com>
Fri, 26 Feb 2016 01:29:41 +0100
changeset 323488 c690a0802e0007bb7e58bafeee672c816faa3cf5
parent 323487 dab205ab83eb560ab7d43e2270d8355bcd2cea4b
child 323489 0651d94c89dfee98a8dc66f5ddc8ee1902a00f6e
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, ritu
bugs1247854
milestone47.0a2
Bug 1247854 - Apply the correct scroll clips to the nsDisplayTransform and nsDisplayPerspective of a scrolled perspective item. r=mattwoodrow a=ritu 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 @@ fuzzy-if(skiaContent,1,32000) fuzzy-if(b
 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
 fuzzy-if(Android,6,4) == offscreen-clipped-blendmode-1.html offscreen-clipped-blendmode-ref.html
 fuzzy-if(Android,6,4) == 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 1251588 - wrong AGR on mix-blend-mode item
 fuzzy-if(Android,6,4) == offscreen-clipped-blendmode-4.html offscreen-clipped-blendmode-ref.html
+fuzzy-if(Android,7,4) == perspective-scrolling-1.html perspective-scrolling-1-ref.html
+fuzzy-if(Android,7,4) == perspective-scrolling-2.html perspective-scrolling-2-ref.html
+fuzzy-if(Android,7,4) == 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)
 == checkerboard-1.html checkerboard-1-ref.html
 == checkerboard-2.html checkerboard-2-ref.html