Bug 1531535 - Add visual smooth scroll support to nsIPresShell. r=kats
authorBotond Ballo <botond@mozilla.com>
Sat, 23 Mar 2019 20:23:35 +0000
changeset 465835 3d7514ac25e949e710a4cb597381207d7cc06c9c
parent 465834 9fde8244e2c45c29b4ccad390173539f3c890836
child 465836 82494837af145a36241ea46ea12dcd1c76945131
push id35749
push userdvarga@mozilla.com
push dateSun, 24 Mar 2019 09:47:08 +0000
treeherdermozilla-central@529782921812 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1531535
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1531535 - Add visual smooth scroll support to nsIPresShell. r=kats This patch renames nsIPresShell::SetPendingVisualScrollUpdate() to ScrollToVisual(), and adds an instant vs. smooth option. SetPendingVisualScrollUpdate() still exists, as a helper for the instant case. Differential Revision: https://phabricator.services.mozilla.com/D24553
docshell/base/nsDocShell.cpp
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/PresShell.cpp
layout/base/nsIPresShell.h
layout/base/nsLayoutUtils.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
toolkit/components/sessionstore/SessionStoreUtils.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5655,18 +5655,18 @@ nsresult nsDocShell::SetCurScrollPosEx(i
   // the visual viewport offset.
   if (!shell->IsVisualViewportSizeSet()) {
     return NS_OK;
   }
 
   // TODO: If scrollMode == SMOOTH_MSD, this will effectively override that
   // and jump to the target position instantly. A proper solution here would
   // involve giving nsIScrollableFrame a visual viewport smooth scrolling API.
-  shell->SetPendingVisualScrollUpdate(targetPos,
-                                      layers::FrameMetrics::eMainThread);
+  shell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread,
+                        nsIPresShell::ScrollMode::eInstant);
 
   return NS_OK;
 }
 
 //*****************************************************************************
 // nsDocShell::nsIScrollable
 //*****************************************************************************
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1416,18 +1416,19 @@ nsDOMWindowUtils::ScrollToVisual(float a
       break;
     case UPDATE_TYPE_MAIN_THREAD:
       updateType = FrameMetrics::eMainThread;
       break;
     default:
       return NS_ERROR_INVALID_ARG;
   }
 
-  presContext->PresShell()->SetPendingVisualScrollUpdate(
-      CSSPoint::ToAppUnits(CSSPoint(aOffsetX, aOffsetY)), updateType);
+  presContext->PresShell()->ScrollToVisual(
+      CSSPoint::ToAppUnits(CSSPoint(aOffsetX, aOffsetY)), updateType,
+      nsIPresShell::ScrollMode::eInstant);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetVisualViewportOffsetRelativeToLayoutViewport(
     float* aOffsetX, float* aOffsetY) {
   *aOffsetX = 0;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -832,19 +832,18 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * Scroll the visual viewport to the given coordinates, relative to the
    * document origin.
    * Only applicable to the window associated with the root content document.
    * Note: this does not take effect right away. Rather, the visual scroll
    *       request is sent to APZ with the next transaction, and will be
    *       reflected in the main thread with the subsequent APZ repaint request.
-   * Please see the caveats mentioned at nsIPresShell::
-   * SetPendingVisualViewportOffset(), and request APZ review if adding a new
-   * call to this.
+   * Please see the caveats mentioned at nsIPresShell::ScrollToVisual(), and
+   * request APZ review if adding a new call to this.
    */
   const long UPDATE_TYPE_RESTORE = 0;
   const long UPDATE_TYPE_MAIN_THREAD = 1;
   void scrollToVisual(in float aOffsetX, in float aOffsetY, in long aUpdateType);
 
   /**
    * Returns the offset of the window's visual viewport relative to the
    * layout viewport.
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -10694,16 +10694,32 @@ bool nsIPresShell::SetVisualViewportOffs
             GetRootScrollFrameAsScrollable()) {
       ScrollAnchorContainer* container = rootScrollFrame->Anchor();
       container->UserScrolled();
     }
   }
   return didChange;
 }
 
+void nsIPresShell::ScrollToVisual(
+    const nsPoint& aVisualViewportOffset,
+    FrameMetrics::ScrollOffsetUpdateType aUpdateType, ScrollMode aMode) {
+  if (aMode == ScrollMode::eSmooth) {
+    if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
+      if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) {
+        return;
+      }
+    }
+  }
+
+  // If the caller asked for instant scroll, or if we failed
+  // to do a smooth scroll, do an instant scroll.
+  SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
+}
+
 void nsIPresShell::SetPendingVisualScrollUpdate(
     const nsPoint& aVisualViewportOffset,
     FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
   mPendingVisualScrollUpdate =
       mozilla::Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType});
 
   // The pending update is picked up during the next paint.
   // Schedule a paint to make sure one will happen.
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1684,29 +1684,35 @@ class nsIPresShell : public nsStubDocume
   // Represents an update to the visual scroll offset that will be sent to APZ.
   // The update type is used to determine priority compared to other scroll
   // updates.
   struct VisualScrollUpdate {
     nsPoint mVisualScrollOffset;
     FrameMetrics::ScrollOffsetUpdateType mUpdateType;
   };
 
+  // Scroll mode enum for ScrollToVisual(). We'd like to reuse
+  // nsIScrollableFrame::ScrollMode but that would require including
+  // nsIScrollableFrame.h from this header, which quickly sinks everything
+  // into a circular dependency quagmire.
+  enum class ScrollMode { eInstant, eSmooth };
+
   // Ask APZ in the next transaction to scroll to the given visual viewport
   // offset (relative to the document).
   // Use this sparingly, as it will clobber JS-driven scrolling that happens
   // in the same frame. This is mostly intended to be used in special
   // situations like "first paint" or session restore.
   // If scrolling "far away", i.e. not just within the existing layout
   // viewport, it's recommended to use both nsIScrollableFrame.ScrollTo*()
   // (via window.scrollTo if calling from JS) *and* this function; otherwise,
   // temporary checkerboarding may result.
   // Please request APZ review if adding a new call site.
-  void SetPendingVisualScrollUpdate(
-      const nsPoint& aVisualViewportOffset,
-      FrameMetrics::ScrollOffsetUpdateType aUpdateType);
+  void ScrollToVisual(const nsPoint& aVisualViewportOffset,
+                      FrameMetrics::ScrollOffsetUpdateType aUpdateType,
+                      ScrollMode aMode);
   void ClearPendingVisualScrollUpdate() {
     mPendingVisualScrollUpdate = mozilla::Nothing();
   }
   const mozilla::Maybe<VisualScrollUpdate>& GetPendingVisualScrollUpdate()
       const {
     return mPendingVisualScrollUpdate;
   }
 
@@ -1762,16 +1768,20 @@ class nsIPresShell : public nsStubDocume
   friend class ::nsAutoCauseReflowNotifier;
 
   void WillCauseReflow();
   void DidCauseReflow();
 
   void CancelPostedReflowCallbacks();
   void FlushPendingScrollAnchorAdjustments();
 
+  void SetPendingVisualScrollUpdate(
+      const nsPoint& aVisualViewportOffset,
+      FrameMetrics::ScrollOffsetUpdateType aUpdateType);
+
 #ifdef DEBUG
   mozilla::UniquePtr<mozilla::ServoStyleSet> CloneStyleSet(
       mozilla::ServoStyleSet*);
   bool VerifyIncrementalReflow();
   void DoVerifyReflow();
   void VerifyHasDirtyRootAncestor(nsIFrame* aFrame);
   void ShowEventTargetDebug();
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8805,18 +8805,19 @@ ScrollMetadata nsLayoutUtils::ComputeScr
     metrics.SetScrollOffset(scrollPosition);
     metrics.SetBaseScrollOffset(apzScrollPosition);
 
     if (aIsRootContent) {
       if (aLayerManager->GetIsFirstPaint() &&
           presShell->IsVisualViewportOffsetSet()) {
         // Restore the visual viewport offset to the copy stored on the
         // main thread.
-        presShell->SetPendingVisualScrollUpdate(
-            presShell->GetVisualViewportOffset(), FrameMetrics::eRestore);
+        presShell->ScrollToVisual(presShell->GetVisualViewportOffset(),
+                                  FrameMetrics::eRestore,
+                                  nsIPresShell::ScrollMode::eInstant);
       }
 
       if (const Maybe<nsIPresShell::VisualScrollUpdate>& visualUpdate =
               presShell->GetPendingVisualScrollUpdate()) {
         metrics.SetVisualViewportOffset(
             CSSPoint::FromAppUnits(visualUpdate->mVisualScrollOffset));
         metrics.SetVisualScrollUpdateType(visualUpdate->mUpdateType);
         presShell->ClearPendingVisualScrollUpdate();
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4461,18 +4461,19 @@ void ScrollFrameHelper::ScrollToRestored
       // It's very important to pass nsGkAtoms::restore here, so
       // ScrollToWithOrigin won't clear out mRestorePos.
       ScrollToWithOrigin(layoutScrollToPos, nsIScrollableFrame::INSTANT,
                          nsGkAtoms::restore, nullptr);
       if (!weakFrame.IsAlive()) {
         return;
       }
       if (mIsRoot && mOuter->PresContext()->IsRootContentDocument()) {
-        mOuter->PresShell()->SetPendingVisualScrollUpdate(
-            visualScrollToPos, FrameMetrics::eRestore);
+        mOuter->PresShell()->ScrollToVisual(visualScrollToPos,
+                                            FrameMetrics::eRestore,
+                                            nsIPresShell::ScrollMode::eInstant);
       }
       if (state == LoadingState::Loading || NS_SUBTREE_DIRTY(mOuter)) {
         // If we're trying to do a history scroll restore, then we want to
         // keep trying this until we succeed, because the page can be loading
         // incrementally. So re-get the scroll position for the next iteration,
         // it might not be exactly equal to mRestorePos due to rounding and
         // clamping.
         mLastPos = GetLogicalVisualViewportOffset();
@@ -6667,8 +6668,33 @@ void ScrollFrameHelper::ApzSmoothScrollT
     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
         frame, nsLayoutUtils::RepaintMode::DoNotRepaint);
   }
 
   // Schedule a paint to ensure that the frame metrics get updated on
   // the compositor thread.
   mOuter->SchedulePaint();
 }
+
+bool ScrollFrameHelper::SmoothScrollVisual(
+    const nsPoint& aVisualViewportOffset,
+    FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
+  bool canDoApzSmoothScroll = gfxPrefs::ScrollBehaviorEnabled() &&
+                              nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
+                              WantAsyncScroll();
+  if (!canDoApzSmoothScroll) {
+    return false;
+  }
+
+  // Clamp the destination to the visual scroll range.
+  nsPoint destination =
+      GetScrollRangeForClamping().ClampPoint(aVisualViewportOffset);
+
+  // We also want to set mDestination to that subsequent ScrollBy()s work
+  // correctly, but mDestination needs to be clamped to the layout scroll range.
+  mDestination = GetScrollRange().ClampPoint(destination);
+
+  // Perform the scroll.
+  ApzSmoothScrollTo(destination, aUpdateType == FrameMetrics::eRestore
+                                     ? nsGkAtoms::restore
+                                     : nsGkAtoms::other);
+  return true;
+}
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -539,16 +539,20 @@ class ScrollFrameHelper : public nsIRefl
   bool DragScroll(WidgetEvent* aEvent);
 
   void AsyncScrollbarDragInitiated(uint64_t aDragBlockId,
                                    mozilla::layers::ScrollDirection aDirection);
   void AsyncScrollbarDragRejected();
 
   bool IsRootScrollFrameOfDocument() const { return mIsRoot; }
 
+  bool SmoothScrollVisual(
+      const nsPoint& aVisualViewportOffset,
+      mozilla::layers::FrameMetrics::ScrollOffsetUpdateType aUpdateType);
+
   // Update minimum-scale size.  The minimum-scale size will be set/used only
   // if there is overflow-x:hidden region.
   void UpdateMinimumScaleSize(const nsRect& aScrollableOverflow,
                               const nsSize& aICBSize);
 
   // owning references to the nsIAnonymousContentCreator-built content
   nsCOMPtr<mozilla::dom::Element> mHScrollbarContent;
   nsCOMPtr<mozilla::dom::Element> mVScrollbarContent;
@@ -1200,16 +1204,22 @@ class nsHTMLScrollFrame : public nsConta
 
   virtual ScrollAnchorContainer* Anchor() override { return &mHelper.mAnchor; }
 
   // Return the scrolled frame.
   void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override {
     aResult.AppendElement(OwnedAnonBox(mHelper.GetScrolledFrame()));
   }
 
+  bool SmoothScrollVisual(const nsPoint& aVisualViewportOffset,
+                          mozilla::layers::FrameMetrics::ScrollOffsetUpdateType
+                              aUpdateType) override {
+    return mHelper.SmoothScrollVisual(aVisualViewportOffset, aUpdateType);
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
@@ -1678,16 +1688,22 @@ class nsXULScrollFrame final : public ns
 
   virtual ScrollAnchorContainer* Anchor() override { return &mHelper.mAnchor; }
 
   // Return the scrolled frame.
   void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override {
     aResult.AppendElement(OwnedAnonBox(mHelper.GetScrolledFrame()));
   }
 
+  bool SmoothScrollVisual(const nsPoint& aVisualViewportOffset,
+                          mozilla::layers::FrameMetrics::ScrollOffsetUpdateType
+                              aUpdateType) override {
+    return mHelper.SmoothScrollVisual(aVisualViewportOffset, aUpdateType);
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
  protected:
   nsXULScrollFrame(ComputedStyle*, nsPresContext*, bool aIsRoot,
                    bool aClipAllDescendants);
 
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -568,11 +568,15 @@ class nsIScrollableFrame : public nsIScr
   virtual bool IsRootScrollFrameOfDocument() const = 0;
 
   /**
    * Returns the scroll anchor associated with this scrollable frame. This is
    * never null.
    */
   virtual const ScrollAnchorContainer* Anchor() const = 0;
   virtual ScrollAnchorContainer* Anchor() = 0;
+
+  virtual bool SmoothScrollVisual(
+      const nsPoint& aVisualViewportOffset,
+      mozilla::layers::FrameMetrics::ScrollOffsetUpdateType aUpdateType) = 0;
 };
 
 #endif
--- a/toolkit/components/sessionstore/SessionStoreUtils.cpp
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -289,19 +289,20 @@ void SessionStoreUtils::RestoreScrollPos
   int pos_Y = atoi(token.get());
   aWindow.ScrollTo(pos_X, pos_Y);
 
   if (nsCOMPtr<Document> doc = aWindow.GetExtantDoc()) {
     if (nsPresContext* presContext = doc->GetPresContext()) {
       if (presContext->IsRootContentDocument()) {
         // Use eMainThread so this takes precedence over session history
         // (ScrollFrameHelper::ScrollToRestoredPosition()).
-        presContext->PresShell()->SetPendingVisualScrollUpdate(
+        presContext->PresShell()->ScrollToVisual(
             CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)),
-            layers::FrameMetrics::eMainThread);
+            layers::FrameMetrics::eMainThread,
+            nsIPresShell::ScrollMode::eInstant);
       }
     }
   }
 }
 
 // Implements the Luhn checksum algorithm as described at
 // http://wikipedia.org/wiki/Luhn_algorithm
 // Number digit lengths vary with network, but should fall within 12-19 range.