Bug 1423011 - Part 1: Allow APZ to async-scroll the layout viewport. r?botond draft
authorKashav Madan <kmadan@mozilla.com>
Wed, 13 Jun 2018 22:10:18 -0700
changeset 807304 d99f673c5d5a3959c44e23839e1841a2d7eb53bc
parent 805967 4cb14f8ae2688a31e8485b3e03030090b0c7030d
push id113079
push userbmo:kmadan@mozilla.com
push dateThu, 14 Jun 2018 05:10:29 +0000
reviewersbotond
bugs1423011
milestone62.0a1
Bug 1423011 - Part 1: Allow APZ to async-scroll the layout viewport. r?botond MozReview-Commit-ID: 7AD8wvthh2m
gfx/layers/FrameMetrics.cpp
gfx/layers/FrameMetrics.h
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/util/APZCCallbackHelper.cpp
layout/base/nsLayoutUtils.cpp
--- a/gfx/layers/FrameMetrics.cpp
+++ b/gfx/layers/FrameMetrics.cpp
@@ -11,29 +11,34 @@
 namespace mozilla {
 namespace layers {
 
 const FrameMetrics::ViewID FrameMetrics::NULL_SCROLL_ID = 0;
 
 void
 FrameMetrics::RecalculateViewportOffset()
 {
-  CSSRect cb = CalculateCompositedRectInCssPixels();
-  if (!mIsRootContent || mViewport.Contains(cb + mScrollOffset)) {
+  CSSRect visualViewport;
+  if (!mIsRootContent ||
+      mViewport.Contains(visualViewport = GetVisualViewport())) {
     return;
   }
-  if (mScrollOffset.X() < mViewport.X()) {
-    mViewport.MoveToX(mScrollOffset.X());
-  } else if (mScrollOffset.X() + cb.Width() > mViewport.XMost()) {
-    mViewport.MoveByX(mScrollOffset.X() + cb.Width() - mViewport.XMost());
+  MOZ_ASSERT(visualViewport.Height() <= mViewport.Height(),
+             "Visual viewport taller than layout viewport");
+  MOZ_ASSERT(visualViewport.Width() <= mViewport.Width(),
+             "Visual viewport wider than layout viewport");
+  if (visualViewport.X() < mViewport.X()) {
+    mViewport.MoveToX(visualViewport.X());
+  } else if (visualViewport.XMost() > mViewport.XMost()) {
+    mViewport.MoveByX(visualViewport.XMost() - mViewport.XMost());
   }
-  if (mScrollOffset.Y() < mViewport.Y()) {
-    mViewport.MoveToY(mScrollOffset.Y());
-  } else if (mScrollOffset.Y() + cb.Height() > mViewport.YMost()) {
-    mViewport.MoveByY(mScrollOffset.Y() + cb.Height() - mViewport.YMost());
+  if (visualViewport.Y() < mViewport.Y()) {
+    mViewport.MoveToY(visualViewport.Y());
+  } else if (visualViewport.YMost() > mViewport.YMost()) {
+    mViewport.MoveByY(visualViewport.YMost() - mViewport.YMost());
   }
 }
 
 void
 ScrollMetadata::SetUsesContainerScrolling(bool aValue) {
   MOZ_ASSERT_IF(aValue, gfxPrefs::LayoutUseContainersForRootFrames());
   mUsesContainerScrolling = aValue;
 }
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -463,16 +463,21 @@ public:
     mViewport = aViewport;
   }
 
   const CSSRect& GetViewport() const
   {
     return mViewport;
   }
 
+  const CSSRect GetVisualViewport() const
+  {
+    return CSSRect(mScrollOffset, CalculateCompositedSizeInCssPixels());
+  }
+
   void SetExtraResolution(const ScreenToLayerScale2D& aExtraResolution)
   {
     mExtraResolution = aExtraResolution;
   }
 
   const ScreenToLayerScale2D& GetExtraResolution() const
   {
     return mExtraResolution;
@@ -511,16 +516,18 @@ public:
   }
   bool IsScrollInfoLayer() const {
     return mIsScrollInfoLayer;
   }
 
   // Determine if the visual viewport is outside of the layout viewport and
   // adjust the x,y-offset in mViewport accordingly. This is necessary to
   // allow APZ to async-scroll the layout viewport.
+  //
+  // This is a no-op if mIsRootContent is false.
   void RecalculateViewportOffset();
 
 private:
   // A unique ID assigned to each scrollable frame.
   ViewID mScrollId;
 
   // The pres-shell resolution that has been induced on the document containing
   // this scroll frame as a result of zooming this scroll frame (whether via
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -661,43 +661,47 @@ protected:
    * Helper method to cancel any gesture currently going to Gecko. Used
    * primarily when a user taps the screen over some clickable content but then
    * pans down instead of letting go (i.e. to cancel a previous touch so that a
    * new one can properly take effect.
    */
   nsEventStatus OnCancelTap(const TapGestureInput& aEvent);
 
   /**
-   * Scroll the scroll frame to an X,Y offset and recalculate the viewport
-   * offset.
+   * The following five methods modify the scroll offset. For the APZC
+   * representing the RCD-RSF, they also recalculate the offset of the layout
+   * viewport.
+   */
+
+  /**
+   * Scroll the scroll frame to an X,Y offset.
    */
   void SetScrollOffset(const CSSPoint& aOffset);
 
   /**
    * Scroll the scroll frame to an X,Y offset, clamping the resulting scroll
-   * offset to the scrollable rect and recalculate the viewport offset.
+   * offset to the scroll range.
    */
   void ClampAndSetScrollOffset(const CSSPoint& aOffset);
 
   /**
    * Scroll the scroll frame by an X,Y offset.
    * The resulting scroll offset is not clamped to the scrollable rect;
    * the caller must ensure it stays within range.
    */
   void ScrollBy(const CSSPoint& aOffset);
 
   /**
    * Scroll the scroll frame by an X,Y offset, clamping the resulting
-   * scroll offset to the scrollable rect.
+   * scroll offset to the scroll range.
    */
   void ScrollByAndClamp(const CSSPoint& aOffset);
 
   /**
-   * Copy the scroll offset and scroll generation from |aFrameMetrics| and
-   * recalculate the viewport offset.
+   * Copy the scroll offset and scroll generation from |aFrameMetrics|.
    */
   void CopyScrollInfoFrom(const FrameMetrics& aFrameMetrics);
 
   /**
    * Scales the viewport by an amount (note that it multiplies this scale in to
    * the current scale, it doesn't set it to |aScale|). Also considers a focus
    * point so that the page zooms inward/outward from that point.
    */
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -90,16 +90,32 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
 
   // If the repaint request was triggered due to a previous main-thread scroll
   // offset update sent to the APZ, then we don't need to do another scroll here
   // and we can just return.
   if (!aMetrics.GetScrollOffsetUpdated()) {
     return geckoScrollPosition;
   }
 
+  // If this is the root content with overflow:hidden, then APZ should not
+  // allow scrolling in such a way that moves the layout viewport.
+  //
+  // If this is overflow:hidden, but not the root content, then
+  // nsLayoutUtils::CalculateScrollableRectForFrame should have sized the
+  // scrollable rect in a way that prevents APZ from scrolling it at all.
+  //
+  // In either case, targetScrollPosition should be the same as
+  // geckoScrollPosition here.
+  if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
+    MOZ_ASSERT(targetScrollPosition.y == geckoScrollPosition.y);
+  }
+  if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
+    MOZ_ASSERT(targetScrollPosition.x == geckoScrollPosition.x);
+  }
+
   // If the scrollable frame is currently in the middle of an async or smooth
   // scroll then we don't want to interrupt it (see bug 961280).
   // Also if the scrollable frame got a scroll request from a higher priority origin
   // since the last layers update, then we don't want to push our scroll request
   // because we'll clobber that one, which is bad.
   bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
   if (!scrollInProgress) {
     aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
@@ -145,17 +161,18 @@ ScrollFrame(nsIContent* aContent,
       if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
         frame->SchedulePaint();
       }
     } else {
       // Correct the display port due to the difference between mScrollOffset and the
       // actual scroll offset.
       APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
     }
-  } else if (aMetrics.IsRootContent()) {
+  } else if (aMetrics.IsRootContent() &&
+             aMetrics.GetScrollOffset() != aMetrics.GetViewport().TopLeft()) {
     // APZ uses the visual viewport's offset to calculate where to place the
     // display port, so the display port is misplaced when a pinch zoom occurs.
     //
     // We need to force a display port adjustment in the following paint to
     // account for a difference between mScrollOffset and the actual scroll
     // offset in repaints requested by AsyncPanZoomController::NotifyLayersUpdated.
     APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
   } else {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9113,21 +9113,21 @@ nsLayoutUtils::ComputeScrollMetadata(nsI
   nsIScrollableFrame* scrollableFrame = nullptr;
   if (aScrollFrame)
     scrollableFrame = aScrollFrame->GetScrollTargetFrame();
 
   metrics.SetScrollableRect(CSSRect::FromAppUnits(
     nsLayoutUtils::CalculateScrollableRectForFrame(scrollableFrame, aForFrame)));
 
   if (scrollableFrame) {
-    nsPoint scrollPosition = scrollableFrame->GetScrollPosition();
-    metrics.SetScrollOffset(CSSPoint::FromAppUnits(scrollPosition));
+    CSSPoint scrollPosition = CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
+    metrics.SetScrollOffset(scrollPosition);
 
     CSSRect viewport = metrics.GetViewport();
-    viewport.MoveTo(CSSPoint::FromAppUnits(scrollPosition));
+    viewport.MoveTo(scrollPosition);
     metrics.SetViewport(viewport);
 
     nsPoint smoothScrollPosition = scrollableFrame->LastScrollDestination();
     metrics.SetSmoothScrollOffset(CSSPoint::FromAppUnits(smoothScrollPosition));
 
     // If the frame was scrolled since the last layers update, and by something
     // that is higher priority than APZ, we want to tell the APZ to update
     // its scroll offset. We want to distinguish the case where the scroll offset