Bug 1351783 part 15 - Hook up APZC for scrolling based on a KeyboardScrollAction. r?kats,botond draft
authorRyan Hunt <rhunt@eqrion.net>
Tue, 06 Jun 2017 04:47:10 -0500
changeset 599279 b02717218b46daf255d35e04091c7982165040ed
parent 599278 895d7351af223410f03642e8c42b2033cd09f398
child 599280 426e9a2b3d5eb9143f74e3881cc1a182ded5c1a6
push id65466
push userbmo:rhunt@eqrion.net
push dateThu, 22 Jun 2017 22:16:51 +0000
reviewerskats, botond
bugs1351783
milestone56.0a1
Bug 1351783 part 15 - Hook up APZC for scrolling based on a KeyboardScrollAction. r?kats,botond This commit adds code for keyboard scroll animations and computing the delta needed for a keyboard scroll action. Keyboard scrolling behavior is more complex with scroll snapping, so we don't support async keyboard scrolling when we have scroll snap points. MozReview-Commit-ID: 97CpprCBp2A
gfx/layers/apz/src/AsyncPanZoomAnimation.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/GenericScrollAnimation.cpp
gfx/layers/apz/src/GenericScrollAnimation.h
gfx/layers/apz/src/Keyboard.cpp
gfx/layers/apz/src/Keyboard.h
gfx/layers/apz/src/KeyboardScrollAction.cpp
gfx/layers/apz/src/KeyboardScrollAction.h
gfx/layers/apz/src/KeyboardScrollAnimation.cpp
gfx/layers/apz/src/KeyboardScrollAnimation.h
gfx/layers/moz.build
gfx/thebes/gfxPrefs.h
widget/InputData.h
--- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h
+++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h
@@ -13,16 +13,17 @@
 #include "FrameMetrics.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace layers {
 
 class WheelScrollAnimation;
+class KeyboardScrollAnimation;
 class SmoothScrollAnimation;
 
 class AsyncPanZoomAnimation {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
 
 public:
   explicit AsyncPanZoomAnimation()
   { }
@@ -45,16 +46,19 @@ public:
   /**
    * Get the deferred tasks in |mDeferredTasks| and place them in |aTasks|. See
    * |mDeferredTasks| for more information.  Clears |mDeferredTasks|.
    */
   nsTArray<RefPtr<Runnable>> TakeDeferredTasks() {
     return Move(mDeferredTasks);
   }
 
+  virtual KeyboardScrollAnimation* AsKeyboardScrollAnimation() {
+    return nullptr;
+  }
   virtual WheelScrollAnimation* AsWheelScrollAnimation() {
     return nullptr;
   }
   virtual SmoothScrollAnimation* AsSmoothScrollAnimation() {
     return nullptr;
   }
 
   virtual bool WantsRepaints() {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -72,16 +72,17 @@
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsViewportInfo.h"             // for kViewportMinScale, kViewportMaxScale
 #include "prsystem.h"                   // for PR_GetPhysicalMemorySize
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
 #include "ScrollSnap.h"                 // for ScrollSnapUtils
 #include "WheelScrollAnimation.h"
+#include "KeyboardScrollAnimation.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #include "AndroidAPZ.h"
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
@@ -999,16 +1000,21 @@ nsEventStatus AsyncPanZoomController::Ha
     TapGestureInput tapInput = aEvent.AsTapGestureInput();
     if (!tapInput.TransformToLocal(aTransformToApzc)) {
       return rv;
     }
 
     rv = HandleGestureEvent(tapInput);
     break;
   }
+  case KEYBOARD_INPUT: {
+    KeyboardInput keyInput = aEvent.AsKeyboardInput();
+    rv = OnKeyboard(keyInput);
+    break;
+  }
   case SENTINEL_INPUT: {
     MOZ_ASSERT_UNREACHABLE("Invalid value");
     break;
   }
   }
 
   return rv;
 }
@@ -1061,16 +1067,17 @@ nsEventStatus AsyncPanZoomController::On
   ParentLayerPoint point = GetFirstTouchPoint(aEvent);
 
   switch (mState) {
     case FLING:
     case ANIMATING_ZOOM:
     case SMOOTH_SCROLL:
     case OVERSCROLL_ANIMATION:
     case WHEEL_SCROLL:
+    case KEYBOARD_SCROLL:
     case PAN_MOMENTUM:
       MOZ_ASSERT(GetCurrentTouchBlock());
       GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll);
       MOZ_FALLTHROUGH;
     case NOTHING: {
       mX.StartTouch(point.x, aEvent.mTime);
       mY.StartTouch(point.y, aEvent.mTime);
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
@@ -1136,16 +1143,17 @@ nsEventStatus AsyncPanZoomController::On
       return nsEventStatus_eConsumeNoDefault;
 
     case PINCHING:
       // The scale gesture listener should have handled this.
       NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
       return nsEventStatus_eIgnore;
 
     case WHEEL_SCROLL:
+    case KEYBOARD_SCROLL:
     case OVERSCROLL_ANIMATION:
       // Should not receive a touch-move in the OVERSCROLL_ANIMATION state
       // as touch blocks that begin in an overscrolled state cancel the
       // animation. The same is true for wheel scroll animations.
       NS_WARNING("Received impossible touch in OnTouchMove");
       break;
   }
 
@@ -1216,16 +1224,17 @@ nsEventStatus AsyncPanZoomController::On
   }
   case PINCHING:
     SetState(NOTHING);
     // Scale gesture listener should have handled this.
     NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
     return nsEventStatus_eIgnore;
 
   case WHEEL_SCROLL:
+  case KEYBOARD_SCROLL:
   case OVERSCROLL_ANIMATION:
     // Should not receive a touch-end in the OVERSCROLL_ANIMATION state
     // as touch blocks that begin in an overscrolled state cancel the
     // animation. The same is true for WHEEL_SCROLL.
     NS_WARNING("Received impossible touch in OnTouchEnd");
     break;
   }
 
@@ -1647,16 +1656,127 @@ AsyncPanZoomController::GetScrollWheelDe
     delta.y = (delta.y >= 0)
               ? pageScrollSize.height
               : -pageScrollSize.height;
   }
 
   return delta;
 }
 
+nsEventStatus
+AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent)
+{
+  // Calculate the destination for this keyboard scroll action
+  nsPoint destination = CSSPoint::ToAppUnits(GetKeyboardDestination(aEvent.mAction));
+
+  // The lock must be held across the entire update operation, so the
+  // compositor doesn't end the animation before we get a chance to
+  // update it.
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
+  // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
+  if (mState != KEYBOARD_SCROLL) {
+    CancelAnimation();
+    SetState(KEYBOARD_SCROLL);
+
+    nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+    StartAnimation(new KeyboardScrollAnimation(*this, initialPosition, aEvent.mAction.mType));
+  }
+
+  // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
+  // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+  // conversion so that the scroll duration isn't affected by zoom
+  nsPoint velocity =
+    CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
+
+  KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
+  MOZ_ASSERT(animation);
+
+  animation->UpdateDestination(aEvent.mTimeStamp, destination, nsSize(velocity.x, velocity.y));
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+CSSPoint
+AsyncPanZoomController::GetKeyboardDestination(const KeyboardScrollAction& aAction) const
+{
+  CSSSize lineScrollSize;
+  CSSSize pageScrollSize;
+  CSSPoint scrollOffset;
+  CSSRect scrollRect;
+
+  {
+    // Grab the lock to access the frame metrics.
+    ReentrantMonitorAutoEnter lock(mMonitor);
+
+    lineScrollSize = mScrollMetadata.GetLineScrollAmount() /
+      mFrameMetrics.GetDevPixelsPerCSSPixel();
+    pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
+      mFrameMetrics.GetDevPixelsPerCSSPixel();
+
+    if (mState == WHEEL_SCROLL) {
+      scrollOffset = mAnimation->AsWheelScrollAnimation()->GetDestination();
+    } else if (mState == SMOOTH_SCROLL) {
+      scrollOffset = mAnimation->AsSmoothScrollAnimation()->GetDestination();
+    } else if (mState == KEYBOARD_SCROLL) {
+      scrollOffset = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
+    } else {
+      scrollOffset = mFrameMetrics.GetScrollOffset();
+    }
+
+    scrollRect = mFrameMetrics.GetScrollableRect();
+  }
+
+  // Calculate the scroll destination based off of the scroll type and direction
+  CSSPoint scrollDestination = scrollOffset;
+
+  switch (aAction.mType) {
+    case KeyboardScrollAction::eScrollCharacter: {
+      int32_t scrollDistance = gfxPrefs::ToolkitHorizontalScrollDistance();
+
+      if (aAction.mForward) {
+        scrollDestination.x += scrollDistance * lineScrollSize.width;
+      } else {
+        scrollDestination.x -= scrollDistance * lineScrollSize.width;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollLine: {
+      int32_t scrollDistance = gfxPrefs::ToolkitVerticalScrollDistance();
+
+      if (aAction.mForward) {
+        scrollDestination.y += scrollDistance * lineScrollSize.height;
+      } else {
+        scrollDestination.y -= scrollDistance * lineScrollSize.height;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollPage: {
+      if (aAction.mForward) {
+        scrollDestination.y += pageScrollSize.height;
+      } else {
+        scrollDestination.y -= pageScrollSize.height;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollComplete: {
+      if (aAction.mForward) {
+        scrollDestination.y = scrollRect.YMost();
+      } else {
+        scrollDestination.y = scrollRect.y;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eSentinel:
+      MOZ_ASSERT_UNREACHABLE("unexpected keyboard delta type");
+  }
+
+  return scrollDestination;
+}
+
 // Return whether or not the underlying layer can be scrolled on either axis.
 bool
 AsyncPanZoomController::CanScroll(const InputData& aEvent) const
 {
   ParentLayerPoint delta;
   if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
     delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
   } else if (aEvent.mInputType == PANGESTURE_INPUT) {
@@ -1813,16 +1933,18 @@ nsEventStatus AsyncPanZoomController::On
       CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
       // If we're already in a wheel scroll or smooth scroll animation,
       // the delta is applied to its destination, not to the current
       // scroll position. Take this into account when finding a snap point.
       if (mState == WHEEL_SCROLL) {
         startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
       } else if (mState == SMOOTH_SCROLL) {
         startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
+      } else if (mState == KEYBOARD_SCROLL) {
+        startPosition = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
       }
       if (MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition)) {
         // If we're scroll snapping, use a smooth scroll animation to get
         // the desired physics. Note that SmoothScrollTo() will re-use an
         // existing smooth scroll animation if there is one.
         APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str());
         SmoothScrollTo(startPosition);
         break;
@@ -1836,17 +1958,18 @@ nsEventStatus AsyncPanZoomController::On
         nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
         StartAnimation(new WheelScrollAnimation(
           *this, initialPosition, aEvent.mDeltaType));
       }
 
       nsPoint deltaInAppUnits =
         CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
       // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-      // appunits/second
+      // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+      // conversion so that the scroll duration isn't affected by zoom
       nsPoint velocity =
         CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
 
       WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
       animation->UpdateDelta(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
       break;
     }
 
@@ -2593,17 +2716,18 @@ void AsyncPanZoomController::SmoothScrol
     RefPtr<SmoothScrollAnimation> animation(
       static_cast<SmoothScrollAnimation*>(mAnimation.get()));
     animation->SetDestination(CSSPoint::ToAppUnits(aDestination));
   } else {
     CancelAnimation();
     SetState(SMOOTH_SCROLL);
     nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
     // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-    // appunits/second
+    // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+    // conversion so that the scroll duration isn't affected by zoom
     nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(),
                                                             mY.GetVelocity())) * 1000.0f;
     nsPoint destination = CSSPoint::ToAppUnits(aDestination);
 
     StartAnimation(new SmoothScrollAnimation(*this,
                                              initialPosition, initialVelocity,
                                              destination,
                                              gfxPrefs::ScrollBehaviorSpringConstant(),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -56,16 +56,17 @@ class TouchBlockState;
 class PanGestureBlockState;
 class OverscrollHandoffChain;
 class StateChangeNotificationBlocker;
 class CheckerboardEvent;
 class OverscrollEffectBase;
 class WidgetOverscrollEffect;
 class GenericOverscrollEffect;
 class AndroidSpecificState;
+struct KeyboardScrollAction;
 
 // Base class for grouping platform-specific APZC state variables.
 class PlatformSpecificStateBase {
 public:
   virtual ~PlatformSpecificStateBase() {}
   virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; }
 };
 
@@ -478,16 +479,23 @@ protected:
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
   ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const;
 
   /**
+   * Helper methods for handling keyboard events.
+   */
+  nsEventStatus OnKeyboard(const KeyboardInput& aEvent);
+
+  CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const;
+
+  /**
    * Helper methods for long press gestures.
    */
   nsEventStatus OnLongPress(const TapGestureInput& aEvent);
   nsEventStatus OnLongPressUp(const TapGestureInput& aEvent);
 
   /**
    * Helper method for single tap gestures.
    */
@@ -834,17 +842,18 @@ protected:
     PAN_MOMENTUM,             /* like PANNING, but controlled by momentum PanGestureInput events */
 
     PINCHING,                 /* nth touch-start, where n > 1. this mode allows pan and zoom */
     ANIMATING_ZOOM,           /* animated zoom to a new rect */
     OVERSCROLL_ANIMATION,     /* Spring-based animation used to relieve overscroll once
                                  the finger is lifted. */
     SMOOTH_SCROLL,            /* Smooth scrolling to destination. Used by
                                  CSSOM-View smooth scroll-behavior */
-    WHEEL_SCROLL              /* Smooth scrolling to a destination for a wheel event. */
+    WHEEL_SCROLL,             /* Smooth scrolling to a destination for a wheel event. */
+    KEYBOARD_SCROLL           /* Smooth scrolling to a destination for a keyboard event. */
   };
 
   // This is in theory protected by |mMonitor|; that is, it should be held whenever
   // this is updated. In practice though... see bug 897017.
   PanZoomState mState;
 
 private:
   friend class StateChangeNotificationBlocker;
@@ -933,16 +942,17 @@ public:
 
 private:
   friend class AndroidFlingAnimation;
   friend class GenericFlingAnimation;
   friend class OverscrollAnimation;
   friend class SmoothScrollAnimation;
   friend class GenericScrollAnimation;
   friend class WheelScrollAnimation;
+  friend class KeyboardScrollAnimation;
 
   friend class GenericOverscrollEffect;
   friend class WidgetOverscrollEffect;
 
   // The initial velocity of the most recent fling.
   ParentLayerPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
--- a/gfx/layers/apz/src/GenericScrollAnimation.cpp
+++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp
@@ -20,22 +20,36 @@ GenericScrollAnimation::GenericScrollAni
   , mFinalDestination(aInitialPosition)
   , mForceVerticalOverscroll(false)
 {
 }
 
 void
 GenericScrollAnimation::UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity)
 {
+  mFinalDestination += aDelta;
+
+  Update(aTime, aCurrentVelocity);
+}
+
+void
+GenericScrollAnimation::UpdateDestination(TimeStamp aTime, nsPoint aDestination, const nsSize& aCurrentVelocity)
+{
+  mFinalDestination = aDestination;
+
+  Update(aTime, aCurrentVelocity);
+}
+
+void
+GenericScrollAnimation::Update(TimeStamp aTime, const nsSize& aCurrentVelocity)
+{
   if (mIsFirstIteration) {
     InitializeHistory(aTime);
   }
 
-  mFinalDestination += aDelta;
-
   // Clamp the final destination to the scrollable area.
   CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination);
   clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x);
   clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y);
   mFinalDestination = CSSPoint::ToAppUnits(clamped);
 
   AsyncScrollBase::Update(aTime, mFinalDestination, aCurrentVelocity);
 }
--- a/gfx/layers/apz/src/GenericScrollAnimation.h
+++ b/gfx/layers/apz/src/GenericScrollAnimation.h
@@ -19,22 +19,27 @@ class GenericScrollAnimation
   : public AsyncPanZoomAnimation,
     public AsyncScrollBase
 {
 public:
   GenericScrollAnimation(AsyncPanZoomController& aApzc,
                          const nsPoint& aInitialPosition);
 
   bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
+
   void UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity);
+  void UpdateDestination(TimeStamp aTime, nsPoint aDestination, const nsSize& aCurrentVelocity);
 
   CSSPoint GetDestination() const {
     return CSSPoint::FromAppUnits(mFinalDestination);
   }
 
+private:
+  void Update(TimeStamp aTime, const nsSize& aCurrentVelocity);
+
 protected:
   AsyncPanZoomController& mApzc;
   nsPoint mFinalDestination;
   bool mForceVerticalOverscroll;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/Keyboard.cpp
+++ b/gfx/layers/apz/src/Keyboard.cpp
@@ -5,46 +5,16 @@
 
 #include "mozilla/layers/Keyboard.h"
 
 #include "mozilla/TextEvents.h" // for IgnoreModifierState, ShortcutKeyCandidate
 
 namespace mozilla {
 namespace layers {
 
-/* static */ nsIScrollableFrame::ScrollUnit
-KeyboardScrollAction::GetScrollUnit(KeyboardScrollAction::KeyboardScrollActionType aDeltaType)
-{
-  switch (aDeltaType) {
-    case KeyboardScrollAction::eScrollCharacter:
-      return nsIScrollableFrame::LINES;
-    case KeyboardScrollAction::eScrollLine:
-      return nsIScrollableFrame::LINES;
-    case KeyboardScrollAction::eScrollPage:
-      return nsIScrollableFrame::PAGES;
-    case KeyboardScrollAction::eScrollComplete:
-      return nsIScrollableFrame::WHOLE;
-    case KeyboardScrollAction::eSentinel:
-      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType.");
-      return nsIScrollableFrame::WHOLE;
-  }
-}
-
-KeyboardScrollAction::KeyboardScrollAction()
-  : mType(KeyboardScrollAction::eScrollCharacter)
-  , mForward(false)
-{
-}
-
-KeyboardScrollAction::KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward)
-  : mType(aType)
-  , mForward(aForward)
-{
-}
-
 KeyboardShortcut::KeyboardShortcut()
 {
 }
 
 KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
                                    uint32_t aKeyCode,
                                    uint32_t aCharCode,
                                    Modifiers aModifiers,
--- a/gfx/layers/apz/src/Keyboard.h
+++ b/gfx/layers/apz/src/Keyboard.h
@@ -7,55 +7,27 @@
 #define mozilla_layers_Keyboard_h
 
 #include <stdint.h> // for uint32_t
 
 #include "InputData.h"          // for KeyboardInput
 #include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit
 #include "nsTArray.h"           // for nsTArray
 #include "mozilla/Maybe.h"      // for mozilla::Maybe
+#include "KeyboardScrollAction.h" // for KeyboardScrollAction
 
 namespace mozilla {
 
 struct IgnoreModifierState;
 
 namespace layers {
 
 class KeyboardMap;
 
 /**
- * This class represents a scrolling action to be performed on a scrollable layer.
- */
-struct KeyboardScrollAction final
-{
-public:
-  enum KeyboardScrollActionType : uint8_t
-  {
-    eScrollCharacter,
-    eScrollLine,
-    eScrollPage,
-    eScrollComplete,
-
-    // Used as an upper bound for ContiguousEnumSerializer
-    eSentinel,
-  };
-
-  static nsIScrollableFrame::ScrollUnit
-  GetScrollUnit(KeyboardScrollActionType aDeltaType);
-
-  KeyboardScrollAction();
-  KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward);
-
-  // The type of scroll to perform for this action
-  KeyboardScrollActionType mType;
-  // Whether to scroll forward or backward along the axis of this action type
-  bool mForward;
-};
-
-/**
  * This class is an off main-thread <xul:handler> for scrolling commands.
  */
 class KeyboardShortcut final
 {
 public:
   KeyboardShortcut();
 
   /**
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAction.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/KeyboardScrollAction.h"
+
+namespace mozilla {
+namespace layers {
+
+/* static */ nsIScrollableFrame::ScrollUnit
+KeyboardScrollAction::GetScrollUnit(KeyboardScrollAction::KeyboardScrollActionType aDeltaType)
+{
+  switch (aDeltaType) {
+    case KeyboardScrollAction::eScrollCharacter:
+      return nsIScrollableFrame::LINES;
+    case KeyboardScrollAction::eScrollLine:
+      return nsIScrollableFrame::LINES;
+    case KeyboardScrollAction::eScrollPage:
+      return nsIScrollableFrame::PAGES;
+    case KeyboardScrollAction::eScrollComplete:
+      return nsIScrollableFrame::WHOLE;
+    case KeyboardScrollAction::eSentinel:
+      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType.");
+      return nsIScrollableFrame::WHOLE;
+  }
+}
+
+KeyboardScrollAction::KeyboardScrollAction()
+  : mType(KeyboardScrollAction::eScrollCharacter)
+  , mForward(false)
+{
+}
+
+KeyboardScrollAction::KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward)
+  : mType(aType)
+  , mForward(aForward)
+{
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAction.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_KeyboardScrollAction_h
+#define mozilla_layers_KeyboardScrollAction_h
+
+#include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class represents a scrolling action to be performed on a scrollable layer.
+ */
+struct KeyboardScrollAction final
+{
+public:
+  enum KeyboardScrollActionType : uint8_t
+  {
+    eScrollCharacter,
+    eScrollLine,
+    eScrollPage,
+    eScrollComplete,
+
+    // Used as an upper bound for ContiguousEnumSerializer
+    eSentinel,
+  };
+
+  static nsIScrollableFrame::ScrollUnit
+  GetScrollUnit(KeyboardScrollActionType aDeltaType);
+
+  KeyboardScrollAction();
+  KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward);
+
+  // The type of scroll to perform for this action
+  KeyboardScrollActionType mType;
+  // Whether to scroll forward or backward along the axis of this action type
+  bool mForward;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_KeyboardScrollAction_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAnimation.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "KeyboardScrollAnimation.h"
+
+#include "gfxPrefs.h"
+
+namespace mozilla {
+namespace layers {
+
+KeyboardScrollAnimation::KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
+                                                 const nsPoint& aInitialPosition,
+                                                 KeyboardScrollAction::KeyboardScrollActionType aType)
+  : GenericScrollAnimation(aApzc, aInitialPosition)
+{
+  switch (aType) {
+    case KeyboardScrollAction::eScrollCharacter:
+    case KeyboardScrollAction::eScrollLine: {
+      mOriginMaxMS = clamped(gfxPrefs::LineSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::LineSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eScrollPage: {
+      mOriginMaxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eScrollComplete: {
+      mOriginMaxMS = clamped(gfxPrefs::OtherSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::OtherSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eSentinel: {
+      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType.");
+    }
+  }
+
+  // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
+  mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
+  mIntervalRatio = std::max(1.0, mIntervalRatio);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAnimation.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_KeyboardScrollAnimation_h_
+#define mozilla_layers_KeyboardScrollAnimation_h_
+
+#include "GenericScrollAnimation.h"
+#include "mozilla/layers/Keyboard.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+class KeyboardScrollAnimation
+  : public GenericScrollAnimation
+{
+public:
+  KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
+                          const nsPoint& aInitialPosition,
+                          KeyboardScrollAction::KeyboardScrollActionType aType);
+
+  KeyboardScrollAnimation* AsKeyboardScrollAnimation() override {
+    return this;
+  }
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_KeyboardScrollAnimation_h_
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -98,16 +98,17 @@ EXPORTS.mozilla.layers += [
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
     'apz/src/AsyncDragMetrics.h',
     'apz/src/AsyncPanZoomAnimation.h',
     'apz/src/FocusState.h',
     'apz/src/FocusTarget.h',
     'apz/src/Keyboard.h',
+    'apz/src/KeyboardScrollAction.h',
     'apz/src/TouchCounter.h',
     'apz/testutil/APZTestData.h',
     'apz/util/ActiveElementManager.h',
     'apz/util/APZCCallbackHelper.h',
     'apz/util/APZEventState.h',
     'apz/util/APZThreadUtils.h',
     'apz/util/ChromeProcessController.h',
     'apz/util/ContentProcessController.h',
@@ -283,16 +284,18 @@ UNIFIED_SOURCES += [
     'apz/src/FocusState.cpp',
     'apz/src/FocusTarget.cpp',
     'apz/src/GenericScrollAnimation.cpp',
     'apz/src/GestureEventListener.cpp',
     'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
     'apz/src/Keyboard.cpp',
+    'apz/src/KeyboardScrollAction.cpp',
+    'apz/src/KeyboardScrollAnimation.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/PotentialCheckerboardDurationTracker.cpp',
     'apz/src/QueuedInput.cpp',
     'apz/src/TouchCounter.cpp',
     'apz/src/WheelScrollAnimation.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -361,21 +361,30 @@ private:
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines",            LineSmoothScrollEnabled, bool, true);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines.durationMaxMS",
+                LineSmoothScrollMaxDurationMs, int32_t, 150);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines.durationMinMS",
+                LineSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel",       WheelSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMaxMS",
                 WheelSmoothScrollMaxDurationMs, int32_t, 400);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMinMS",
                 WheelSmoothScrollMinDurationMs, int32_t, 200);
+  DECL_GFX_PREF(Live, "general.smoothScroll.other.durationMaxMS",
+                OtherSmoothScrollMaxDurationMs, int32_t, 150);
+  DECL_GFX_PREF(Live, "general.smoothScroll.other.durationMinMS",
+                OtherSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages",            PageSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMaxMS",
                 PageSmoothScrollMaxDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMinMS",
                 PageSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels",           PixelSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMaxMS",
                 PixelSmoothScrollMaxDurationMs, int32_t, 150);
@@ -646,16 +655,19 @@ private:
 
   DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
 
   DECL_GFX_PREF(Once, "slider.snapMultiplier",                 SliderSnapMultiplier, int32_t, 0);
 
   DECL_GFX_PREF(Live, "test.events.async.enabled",             TestEventsAsyncEnabled, bool, false);
   DECL_GFX_PREF(Live, "test.mousescroll",                      MouseScrollTestingEnabled, bool, false);
 
+  DECL_GFX_PREF(Live, "toolkit.scrollbox.horizontalScrollDistance", ToolkitHorizontalScrollDistance, int32_t, 5);
+  DECL_GFX_PREF(Live, "toolkit.scrollbox.verticalScrollDistance",   ToolkitVerticalScrollDistance, int32_t, 3);
+
   DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay",     UiClickHoldContextMenusDelay, int32_t, 500);
 
   // WebGL (for pref access from Worker threads)
   DECL_GFX_PREF(Live, "webgl.all-angle-options",               WebGLAllANGLEOptions, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-d3d11",               WebGLANGLEForceD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.try-d3d11",                 WebGLANGLETryD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-warp",                WebGLANGLEForceWARP, bool, false);
   DECL_GFX_PREF(Live, "webgl.bypass-shader-validation",        WebGLBypassShaderValidator, bool, true);
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -10,16 +10,17 @@
 #include "nsIDOMWheelEvent.h"
 #include "nsIScrollableFrame.h"
 #include "nsPoint.h"
 #include "nsTArray.h"
 #include "Units.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/layers/KeyboardScrollAction.h"
 
 template<class E> struct already_AddRefed;
 class nsIWidget;
 
 namespace mozilla {
 
 namespace layers {
 class PAPZCTreeManagerParent;
@@ -618,16 +619,18 @@ public:
   bool mMayHaveMomentum;
   bool mIsMomentum;
   bool mAllowToOverrideSystemScrollSpeed;
 };
 
 class KeyboardInput : public InputData
 {
 public:
+  typedef mozilla::layers::KeyboardScrollAction KeyboardScrollAction;
+
   enum KeyboardEventType
   {
     KEY_DOWN,
     KEY_PRESS,
     KEY_UP,
     // Any other key event such as eKeyDownOnPlugin
     KEY_OTHER,
 
@@ -642,16 +645,20 @@ public:
 
   KeyboardEventType mType;
   uint32_t mKeyCode;
   uint32_t mCharCode;
   nsTArray<ShortcutKeyCandidate> mShortcutCandidates;
 
   bool mHandledByAPZ;
 
+  // The scroll action to perform on a layer for this keyboard input. This is
+  // only used in APZ and is NOT serialized over IPC.
+  KeyboardScrollAction mAction;
+
 protected:
   friend mozilla::layers::PAPZCTreeManagerParent;
   friend mozilla::layers::APZCTreeManagerChild;
 
   KeyboardInput();
 };
 
 } // namespace mozilla