Bug 1358017 - Part 4: Implements the auto-dir scrolling feature(without the "honour root" functionality) in APZ r?kats draft
authorZhang Junzhi <zjz@zjz.name>
Fri, 16 Mar 2018 19:23:53 +0800
changeset 774636 97479cdba49c443b3c66c901b8515582e11b2f57
parent 774635 1553f54e54f60c2cfc327341bbb346ac89147792
child 774637 e94ae64d66d5f11d5259da5c18101d73665ee5fb
push id104460
push userbmo:zjz@zjz.name
push dateThu, 29 Mar 2018 08:46:31 +0000
reviewerskats
bugs1358017
milestone61.0a1
Bug 1358017 - Part 4: Implements the auto-dir scrolling feature(without the "honour root" functionality) in APZ r?kats This commit implements the auto-dir scrolling functionality in APZ, based on part 1 to part 3. However, the functionality of mousewheel.autodir.honourroot will be implemented in a future. MozReview-Commit-ID: 9xai99x71gh
dom/events/WheelHandlingHelper.h
gfx/layers/FrameMetrics.h
gfx/layers/apz/src/APZInputBridge.cpp
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h
gfx/layers/apz/src/Axis.cpp
gfx/layers/apz/src/Axis.h
gfx/layers/apz/test/gtest/InputUtils.h
gfx/layers/apz/test/gtest/TestHitTesting.cpp
gfx/layers/apz/test/gtest/TestTreeManager.cpp
widget/InputData.cpp
widget/InputData.h
widget/android/nsWindow.cpp
widget/cocoa/nsChildView.mm
widget/nsGUIEventIPC.h
--- a/dom/events/WheelHandlingHelper.h
+++ b/dom/events/WheelHandlingHelper.h
@@ -253,16 +253,19 @@ enum class WheelDeltaAdjustmentStrategy 
   // element, but the <body> element is typically considered as a root element.
   // If there is no <body> element, then consider the <html> element instead.
   // And also note that like |eHorizontalize|, delta values are *ONLY* going to
   // be adjusted during the process of its default action handling; in views of
   // any programmes other than the default action handler, such as a DOM event
   // listener or a plugin, delta values are never going to be adjusted.
   eAutoDir,
   eAutoDirWithRootHonour,
+  // Not an actual strategy. This is just used as an upper bound for
+  // ContiguousEnumSerializer.
+  eSentinel,
 };
 
 /**
  * When a *pure* vertical wheel event should be treated as if it was a
  * horizontal scroll because the user wants to horizontalize the wheel scroll,
  * an instance of this class will adjust the delta values upon calling
  * Horizontalize(). And the horizontalized delta values will be restored
  * automatically when the instance of this class is being destructed. Or you can
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -482,16 +482,27 @@ public:
     return mScrollableRect;
   }
 
   void SetScrollableRect(const CSSRect& aScrollableRect)
   {
     mScrollableRect = aScrollableRect;
   }
 
+  // If the frame is in vertical-RTL writing mode(E.g. "writing-mode:
+  // vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode(E.g.
+  // "writing-mode: horizontal-tb; direction: rtl;" in CSS), then this function
+  // returns true. From the representation perspective, frames whose horizontal
+  // contents start at rightside also cause their horizontal scrollbars, if any,
+  // initially start at rightside. So we can also learn about the initial side
+  // of the horizontal scrollbar for the frame by calling this function.
+  bool IsHorizontalContentRightToLeft() {
+    return mScrollableRect.x < 0;
+  }
+
   void SetPaintRequestTime(const TimeStamp& aTime) {
     mPaintRequestTime = aTime;
   }
   const TimeStamp& GetPaintRequestTime() const {
     return mPaintRequestTime;
   }
 
   void SetIsScrollInfoLayer(bool aIsScrollInfoLayer) {
--- a/gfx/layers/apz/src/APZInputBridge.cpp
+++ b/gfx/layers/apz/src/APZInputBridge.cpp
@@ -134,17 +134,18 @@ APZInputBridge::ReceiveInputEvent(
         if (wheelEvent.mDeltaX || wheelEvent.mDeltaY) {
           ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y);
           ScrollWheelInput input(wheelEvent.mTime, wheelEvent.mTimeStamp, 0,
                                  scrollMode,
                                  ScrollWheelInput::DeltaTypeForDeltaMode(
                                                      wheelEvent.mDeltaMode),
                                  origin,
                                  wheelEvent.mDeltaX, wheelEvent.mDeltaY,
-                                 wheelEvent.mAllowToOverrideSystemScrollSpeed);
+                                 wheelEvent.mAllowToOverrideSystemScrollSpeed,
+                                 strategy);
 
           // We add the user multiplier as a separate field, rather than premultiplying
           // it, because if the input is converted back to a WidgetWheelEvent, then
           // EventStateManager would apply the delta a second time. We could in theory
           // work around this by asking ESM to customize the event much sooner, and
           // then save the "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for
           // now, this seems easier.
           EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent,
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -8,16 +8,17 @@
 
 #include <math.h>                       // for fabsf, fabs, atan2
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include <sys/types.h>                  // for int32_t
 #include <algorithm>                    // for max, min
 
 #include "APZCTreeManager.h"            // for APZCTreeManager
 #include "AsyncPanZoomAnimation.h"      // for AsyncPanZoomAnimation
+#include "AutoDirWheelDeltaAdjuster.h"  // for APZAutoDirWheelDeltaAdjuster
 #include "AutoscrollAnimation.h"        // for AutoscrollAnimation
 #include "Axis.h"                       // for AxisX, AxisY, Axis, etc
 #include "CheckerboardEvent.h"          // for CheckerboardEvent
 #include "Compositor.h"                 // for Compositor
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "GenericFlingAnimation.h"      // for GenericFlingAnimation
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
@@ -1722,16 +1723,29 @@ AllowsScrollingMoreThanOnePage(double aM
   const int32_t kMinAllowPageScroll =
     EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
   return Abs(aMultiplier) >= kMinAllowPageScroll;
 }
 
 ParentLayerPoint
 AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent) const
 {
+  return GetScrollWheelDelta(aEvent,
+                             aEvent.mDeltaX, aEvent.mDeltaY,
+                             aEvent.mUserDeltaMultiplierX,
+                             aEvent.mUserDeltaMultiplierY);
+}
+
+ParentLayerPoint
+AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent,
+                                            double aDeltaX,
+                                            double aDeltaY,
+                                            double aMultiplierX,
+                                            double aMultiplierY) const
+{
   ParentLayerSize scrollAmount;
   ParentLayerSize pageScrollSize;
 
   {
     // Grab the lock to access the frame metrics.
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount();
     LayoutDeviceIntSize pageScrollSizeLD = mScrollMetadata.GetPageScrollAmount();
@@ -1739,34 +1753,35 @@ AsyncPanZoomController::GetScrollWheelDe
       mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
     pageScrollSize = pageScrollSizeLD /
       mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
   }
 
   ParentLayerPoint delta;
   switch (aEvent.mDeltaType) {
     case ScrollWheelInput::SCROLLDELTA_LINE: {
-      delta.x = aEvent.mDeltaX * scrollAmount.width;
-      delta.y = aEvent.mDeltaY * scrollAmount.height;
+      delta.x = aDeltaX * scrollAmount.width;
+      delta.y = aDeltaY * scrollAmount.height;
       break;
     }
     case ScrollWheelInput::SCROLLDELTA_PAGE: {
-      delta.x = aEvent.mDeltaX * pageScrollSize.width;
-      delta.y = aEvent.mDeltaY * pageScrollSize.height;
+      delta.x = aDeltaX * pageScrollSize.width;
+      delta.y = aDeltaY * pageScrollSize.height;
       break;
     }
     case ScrollWheelInput::SCROLLDELTA_PIXEL: {
-      delta = ToParentLayerCoordinates(ScreenPoint(aEvent.mDeltaX, aEvent.mDeltaY), aEvent.mOrigin);
+      delta = ToParentLayerCoordinates(ScreenPoint(aDeltaX, aDeltaY),
+                                       aEvent.mOrigin);
       break;
     }
   }
 
   // Apply user-set multipliers.
-  delta.x *= aEvent.mUserDeltaMultiplierX;
-  delta.y *= aEvent.mUserDeltaMultiplierY;
+  delta.x *= aMultiplierX;
+  delta.y *= aMultiplierY;
 
   // For the conditions under which we allow system scroll overrides, see
   // EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction
   // and WheelTransaction::OverrideSystemScrollSpeed. Note that we do *not*
   // restrict this to the root content, see bug 1217715 for discussion on this.
   if (gfxPrefs::MouseWheelHasRootScrollDeltaOverride() &&
       !aEvent.IsCustomizedByUserPrefs() &&
       aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE &&
@@ -1787,23 +1802,23 @@ AsyncPanZoomController::GetScrollWheelDe
         delta.x = ComputeAcceleratedWheelDelta(delta.x, aEvent.mScrollSeriesNumber, factor);
         delta.y = ComputeAcceleratedWheelDelta(delta.y, aEvent.mScrollSeriesNumber, factor);
       }
     }
   }
 
   // We shouldn't scroll more than one page at once except when the
   // user preference is large.
-  if (!AllowsScrollingMoreThanOnePage(aEvent.mUserDeltaMultiplierX) &&
+  if (!AllowsScrollingMoreThanOnePage(aMultiplierX) &&
       Abs(delta.x) > pageScrollSize.width) {
     delta.x = (delta.x >= 0)
               ? pageScrollSize.width
               : -pageScrollSize.width;
   }
-  if (!AllowsScrollingMoreThanOnePage(aEvent.mUserDeltaMultiplierY) &&
+  if (!AllowsScrollingMoreThanOnePage(aMultiplierY) &&
       Abs(delta.y) > pageScrollSize.height) {
     delta.y = (delta.y >= 0)
               ? pageScrollSize.height
               : -pageScrollSize.height;
   }
 
   return delta;
 }
@@ -1976,16 +1991,36 @@ bool
 AsyncPanZoomController::CanScroll(const InputData& aEvent) const
 {
   ParentLayerPoint delta = GetDeltaForEvent(aEvent);
   if (!delta.x && !delta.y) {
     return false;
   }
 
   if (SCROLLWHEEL_INPUT == aEvent.mInputType) {
+    const ScrollWheelInput& scrollWheelInput = aEvent.AsScrollWheelInput();
+    // If it's a wheel scroll, we first check if it is an auto-dir scroll.
+    // 1. For an auto-dir scroll, check if it's delta should be adjusted, if it
+    //    is, then we can conclude it must be scrollable; otherwise, fall back
+    //    to checking if it is scrollable without adjusting its delta.
+    // 2. For a non-auto-dir scroll, simply check if it is scrollable without
+    //    adjusting its delta.
+    if (scrollWheelInput.IsAutoDir()) {
+      RecursiveMutexAutoLock lock(mRecursiveMutex);
+      auto deltaX = scrollWheelInput.mDeltaX;
+      auto deltaY = scrollWheelInput.mDeltaY;
+      bool isRTL = IsContentOfHonouredTargetRightToLeft(
+                     scrollWheelInput.HonoursRoot());
+      APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
+      if (adjuster.ShouldBeAdjusted()) {
+        // If we detect that the delta values should be adjusted for an auto-dir
+        // wheel scroll, then it is impossible to be an unscrollable scroll.
+        return true;
+      }
+    }
     return CanScrollWithWheel(delta);
   }
   return CanScroll(delta);
 }
 
 ScrollDirections
 AsyncPanZoomController::GetAllowedHandoffDirections() const
 {
@@ -2035,16 +2070,26 @@ AsyncPanZoomController::CanScroll(Scroll
   case ScrollDirection::eHorizontal: return mX.CanScroll();
   case ScrollDirection::eVertical:   return mY.CanScroll();
   }
   MOZ_ASSERT_UNREACHABLE("Invalid value");
   return false;
 }
 
 bool
+AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft(
+                          bool aHonoursRoot) const
+{
+  // TODO The current implementation only honours the scrolling target, the
+  // functionality of honouring root is going to be added in the next commit.
+  RecursiveMutexAutoLock lock(mRecursiveMutex);
+  return mFrameMetrics.IsHorizontalContentRightToLeft();
+}
+
+bool
 AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const
 {
   bool result = mInputQueue->AllowScrollHandoff();
   if (!gfxPrefs::APZAllowImmediateHandoff()) {
     if (InputBlockState* currentBlock = GetCurrentInputBlock()) {
       // Do not allow handoff beyond the first APZC to scroll.
       if (currentBlock->GetScrolledApzc() == this) {
         result = false;
@@ -2073,20 +2118,62 @@ AdjustDeltaForAllowedScrollDirections(
   }
   if (!aAllowedScrollDirections.contains(ScrollDirection::eVertical)) {
     aDelta.y = 0;
   }
 }
 
 nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
 {
-  ParentLayerPoint delta = GetScrollWheelDelta(aEvent);
-  APZC_LOG("%p got a scroll-wheel with delta %s\n", this, Stringify(delta).c_str());
-
-  if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
+  // Get the scroll wheel's delta values in parent-layer pixels. But before
+  // getting the values, we need to check if it is an auto-dir scroll and if it
+  // should be adjusted, if both answers are yes, let's adjust X and Y values
+  // first, and then get the delta values in parent-layer pixels based on the
+  // adjusted values.
+  bool adjustedByAutoDir = false;
+  ParentLayerPoint delta;
+  if (aEvent.IsAutoDir()) {
+    // It's an auto-dir scroll, so check if its delta should be adjusted, if so,
+    // adjust it.
+    RecursiveMutexAutoLock lock(mRecursiveMutex);
+    auto deltaX = aEvent.mDeltaX;
+    auto deltaY = aEvent.mDeltaY;
+    bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot());
+    APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
+    if (adjuster.ShouldBeAdjusted()) {
+      adjuster.Adjust();
+      // If the original delta values have been adjusted, we pass them to
+      // replace the original delta values in |aEvent| so that the delta values
+      // in parent-layer pixels are caculated based on the adjusted values, not
+      // the original ones.
+      // Pay special attention to the last two parameters. They are in a swaped
+      // order so that they still correspond to their delta after adjustment.
+      delta = GetScrollWheelDelta(aEvent,
+                                  deltaX, deltaY,
+                                  aEvent.mUserDeltaMultiplierY,
+                                  aEvent.mUserDeltaMultiplierX);
+      adjustedByAutoDir = true;
+    }
+  }
+  if (!adjustedByAutoDir) {
+    // If the original delta values haven't been adjusted by auto-dir, just pass
+    // the |aEvent| and caculate the delta values in parent-layer pixels based
+    // on the original delta values from |aEvent|.
+    delta = GetScrollWheelDelta(aEvent);
+  }
+
+  APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n",
+           this, Stringify(delta).c_str());
+
+  if (adjustedByAutoDir) {
+    MOZ_ASSERT(delta.x || delta.y,
+               "Adjusted auto-dir delta values can never be all-zero.");
+    APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n",
+             this);
+  } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
     // We can't scroll this apz anymore, so we simply drop the event.
     if (mInputQueue->GetActiveWheelTransaction() &&
         gfxPrefs::MouseScrollTestingEnabled()) {
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
         controller->NotifyMozMouseScrollEvent(
           mFrameMetrics.GetScrollId(),
           NS_LITERAL_STRING("MozMouseScrollFailed"));
       }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -12,17 +12,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RecursiveMutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Atomics.h"
 #include "InputData.h"
-#include "Axis.h"
+#include "Axis.h"                       // for Axis, Side, etc.
 #include "InputQueue.h"
 #include "APZUtils.h"
 #include "Layers.h"                     // for Layer::ScrollDirection
 #include "LayersTypes.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsIScrollableFrame.h"
 #include "nsRegion.h"
 #include "nsTArray.h"
@@ -500,16 +500,23 @@ public:
    */
   CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
                                  const ScrollThumbData& aThumbData) const;
 
   void NotifyMozMouseScrollEvent(const nsString& aString) const;
 
   bool OverscrollBehaviorAllowsSwipe() const;
 
+private:
+  // Get whether the horizontal content of the honoured target of auto-dir
+  // scrolling starts from right to left. If you don't know of auto-dir
+  // scrolling or what a honoured target means,
+  // @see mozilla::WheelDeltaAdjustmentStrategy
+  bool IsContentOfHonouredTargetRightToLeft(bool aHonoursRoot) const;
+
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~AsyncPanZoomController();
 
   // Returns the cached current frame time.
   TimeStamp GetFrameTime() const;
 
   /**
@@ -566,19 +573,51 @@ protected:
   nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent);
   nsEventStatus HandleEndOfPan();
 
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
+  /**
+   * Gets the scroll wheel delta's values in parent-layer pixels from the
+   * original delta's values of a wheel input.
+   */
   ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const;
 
   /**
+   * This function is like GetScrollWheelDelta(aEvent).
+   * The difference is the four added parameters provide values as alternatives
+   * to the original wheel input's delta values, so |aEvent|'s delta values are
+   * ignored in this function, we only use some other member variables and
+   * functions of |aEvent|.
+   */
+  ParentLayerPoint
+  GetScrollWheelDelta(const ScrollWheelInput& aEvent,
+                      double aDeltaX,
+                      double aDeltaY,
+                      double aMultiplierX,
+                      double aMultiplierY) const;
+
+  /**
+   * This deleted function is used for:
+   * 1. avoiding accidental implicit value type conversions of input delta
+   *    values when callers intend to call the above function;
+   * 2. decoupling the manual relationship between the delta value type and the
+   *    above function. If by any chance the defined delta value type in
+   *    ScrollWheelInput has changed, this will automatically result in build
+   *    time failure, so we can learn of it the first time and accordingly
+   *    redefine those parameters' value types in the above function.
+   */
+  template <typename T>
+  ParentLayerPoint
+  GetScrollWheelDelta(ScrollWheelInput&, T, T, T, T) = delete;
+
+  /**
    * Helper methods for handling keyboard events.
    */
   nsEventStatus OnKeyboard(const KeyboardInput& aEvent);
 
   CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const;
 
   /**
    * Helper methods for long press gestures.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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_AutoDirWheelDeltaAdjuster_h__
+#define __mozilla_layers_AutoDirWheelDeltaAdjuster_h__
+
+#include "Axis.h"                        // for AxisX, AxisY, Side
+#include "mozilla/WheelHandlingHelper.h" // for AutoDirWheelDeltaAdjuster
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * About AutoDirWheelDeltaAdjuster:
+ * For an AutoDir wheel scroll, there's some situations where we should adjust a
+ * wheel event's delta values. AutoDirWheelDeltaAdjuster converts delta values
+ * for AutoDir scrolling. An AutoDir wheel scroll lets the user scroll a frame
+ * with only one scrollbar, using either a vertical or a horzizontal wheel.
+ * For more detail about the concept of AutoDir scrolling, see the comments in
+ * AutoDirWheelDeltaAdjuster.
+ *
+ * This is the APZ implementation of AutoDirWheelDeltaAdjuster.
+ */
+class MOZ_STACK_CLASS APZAutoDirWheelDeltaAdjuster final
+                        : public AutoDirWheelDeltaAdjuster
+{
+public:
+  /**
+   * @param aDeltaX            DeltaX for a wheel event whose delta values will
+   *                           be adjusted upon calling adjust() when
+   *                           ShouldBeAdjusted() returns true.
+   * @param aDeltaY            DeltaY for a wheel event, like DeltaX.
+   * @param aAxisX             The X axis information provider for the current
+   *                           frame, such as whether the frame can be scrolled
+   *                           horizontally, leftwards or rightwards.
+   * @param aAxisY             The Y axis information provider for the current
+   *                           frame, such as whether the frame can be scrolled
+   *                           vertically, upwards or downwards.
+   * @param aIsHorizontalContentRightToLeft
+   *                           Indicates whether the horizontal content starts
+   *                           at rightside. This value will decide which edge
+   *                           the adjusted scroll goes towards, in other words,
+   *                           it will decide the sign of the adjusted delta
+   *                           values). For detailed information, see
+   *                           IsHorizontalContentRightToLeft() in
+   *                           the base class AutoDirWheelDeltaAdjuster.
+   */
+  explicit
+  APZAutoDirWheelDeltaAdjuster(double& aDeltaX,
+                               double& aDeltaY,
+                               const AxisX& aAxisX,
+                               const AxisY& aAxisY,
+                               bool aIsHorizontalContentRightToLeft)
+    : AutoDirWheelDeltaAdjuster(aDeltaX, aDeltaY)
+    , mAxisX(aAxisX)
+    , mAxisY(aAxisY)
+    , mIsHorizontalContentRightToLeft(aIsHorizontalContentRightToLeft)
+  {
+  }
+
+private:
+  virtual bool CanScrollAlongXAxis() const override
+  {
+    return mAxisX.CanScroll();
+  }
+  virtual bool CanScrollAlongYAxis() const override
+  {
+    return mAxisY.CanScroll();
+  }
+  virtual bool CanScrollUpwards() const override
+  {
+    return mAxisY.CanScrollTo(eSideTop);
+  }
+  virtual bool CanScrollDownwards() const override
+  {
+    return mAxisY.CanScrollTo(eSideBottom);
+  }
+  virtual bool CanScrollLeftwards() const override
+  {
+    return mAxisX.CanScrollTo(eSideLeft);
+  }
+  virtual bool CanScrollRightwards() const override
+  {
+    return mAxisX.CanScrollTo(eSideRight);
+  }
+  virtual bool IsHorizontalContentRightToLeft() const override
+  {
+    return mIsHorizontalContentRightToLeft;
+  }
+
+  const AxisX& mAxisX;
+  const AxisY& mAxisY;
+  bool mIsHorizontalContentRightToLeft;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // __mozilla_layers_AutoDirWheelDeltaAdjuster_h__
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -540,16 +540,29 @@ ScreenPoint AxisX::MakePoint(ScreenCoord
   return ScreenPoint(aCoord, 0);
 }
 
 const char* AxisX::Name() const
 {
   return "X";
 }
 
+bool AxisX::CanScrollTo(Side aSide) const
+{
+  switch (aSide) {
+    case eSideLeft:
+      return CanScroll(-COORDINATE_EPSILON * 2);
+    case eSideRight:
+      return CanScroll(COORDINATE_EPSILON * 2);
+    default:
+      MOZ_ASSERT_UNREACHABLE("aSide is out of valid values");
+      return false;
+  }
+}
+
 OverscrollBehavior AxisX::GetOverscrollBehavior() const
 {
   return GetScrollMetadata().GetOverscrollBehavior().mBehaviorX;
 }
 
 AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
@@ -581,15 +594,28 @@ ScreenPoint AxisY::MakePoint(ScreenCoord
   return ScreenPoint(0, aCoord);
 }
 
 const char* AxisY::Name() const
 {
   return "Y";
 }
 
+bool AxisY::CanScrollTo(Side aSide) const
+{
+  switch (aSide) {
+    case eSideTop:
+      return CanScroll(-COORDINATE_EPSILON * 2);
+    case eSideBottom:
+      return CanScroll(COORDINATE_EPSILON * 2);
+    default:
+      MOZ_ASSERT_UNREACHABLE("aSide is out of valid values");
+      return false;
+  }
+}
+
 OverscrollBehavior AxisY::GetOverscrollBehavior() const
 {
   return GetScrollMetadata().GetOverscrollBehavior().mBehaviorY;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -3,21 +3,23 @@
 /* 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_Axis_h
 #define mozilla_layers_Axis_h
 
 #include <sys/types.h>                  // for int32_t
+
 #include "APZUtils.h"
 #include "AxisPhysicsMSDModel.h"
-#include "Units.h"
+#include "mozilla/gfx/Types.h"          // for Side
 #include "mozilla/TimeStamp.h"          // for TimeDuration
 #include "nsTArray.h"                   // for nsTArray
+#include "Units.h"
 
 namespace mozilla {
 namespace layers {
 
 const float EPSILON = 0.0001f;
 
 /**
  * Compare two coordinates for equality, accounting for rounding error.
@@ -308,29 +310,31 @@ class AxisX : public Axis {
 public:
   explicit AxisX(AsyncPanZoomController* mAsyncPanZoomController);
   virtual ParentLayerCoord GetPointOffset(const ParentLayerPoint& aPoint) const override;
   virtual ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override;
   virtual ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override;
   virtual CSSToParentLayerScale GetScaleForAxis(const CSSToParentLayerScale2D& aScale) const override;
   virtual ScreenPoint MakePoint(ScreenCoord aCoord) const override;
   virtual const char* Name() const override;
+  bool CanScrollTo(Side aSide) const;
 private:
   virtual OverscrollBehavior GetOverscrollBehavior() const override;
 };
 
 class AxisY : public Axis {
 public:
   explicit AxisY(AsyncPanZoomController* mAsyncPanZoomController);
   virtual ParentLayerCoord GetPointOffset(const ParentLayerPoint& aPoint) const override;
   virtual ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override;
   virtual ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override;
   virtual CSSToParentLayerScale GetScaleForAxis(const CSSToParentLayerScale2D& aScale) const override;
   virtual ScreenPoint MakePoint(ScreenCoord aCoord) const override;
   virtual const char* Name() const override;
+  bool CanScrollTo(Side aSide) const;
 private:
   virtual OverscrollBehavior GetOverscrollBehavior() const override;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/apz/test/gtest/InputUtils.h
+++ b/gfx/layers/apz/test/gtest/InputUtils.h
@@ -244,28 +244,28 @@ PinchWithTouchInputAndCheckStatus(const 
 
 template<class InputReceiver>
 nsEventStatus
 Wheel(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
       const ScreenPoint& aDelta, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
 {
   ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0,
       ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-      aPoint, aDelta.x, aDelta.y, false);
+      aPoint, aDelta.x, aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone);
   return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
 }
 
 template<class InputReceiver>
 nsEventStatus
 SmoothWheel(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
             const ScreenPoint& aDelta, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
 {
   ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0,
       ScrollWheelInput::SCROLLMODE_SMOOTH, ScrollWheelInput::SCROLLDELTA_LINE,
-      aPoint, aDelta.x, aDelta.y, false);
+      aPoint, aDelta.x, aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone);
   return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
 }
 
 template<class InputReceiver>
 nsEventStatus
 MouseDown(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
           TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
 {
--- a/gfx/layers/apz/test/gtest/TestHitTesting.cpp
+++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp
@@ -470,17 +470,17 @@ TEST_F(APZHitTestingTester, TestRepaintF
   manager->UpdateHitTestingTree(0, root, false, 0, 0);
   TestAsyncPanZoomController* apzcroot = ApzcOf(root);
 
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3));
   ScreenPoint origin(100, 50);
   for (int i = 0; i < 3; i++) {
     ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
       ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-      origin, 0, 10, false);
+      origin, 0, 10, false, WheelDeltaAdjustmentStrategy::eNone);
     EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
     EXPECT_EQ(origin, swi.mOrigin);
 
     AsyncTransform viewTransform;
     ParentLayerPoint point;
     apzcroot->SampleContentTransformForFrame(&viewTransform, point);
     EXPECT_EQ(0, point.x);
     EXPECT_EQ((i + 1) * 10, point.y);
@@ -496,17 +496,17 @@ TEST_F(APZHitTestingTester, TestForceDis
   DisableApzOn(root);
   ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
   manager->UpdateHitTestingTree(0, root, false, 0, 0);
   TestAsyncPanZoomController* apzcroot = ApzcOf(root);
 
   ScreenPoint origin(100, 50);
   ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
     ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-    origin, 0, 10, false);
+    origin, 0, 10, false, WheelDeltaAdjustmentStrategy::eNone);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
   EXPECT_EQ(origin, swi.mOrigin);
 
   AsyncTransform viewTransform;
   ParentLayerPoint point;
   apzcroot->SampleContentTransformForFrame(&viewTransform, point);
   // Since APZ is force-disabled, we expect to see the async transform via
   // the NORMAL AsyncMode, but not via the RESPECT_FORCE_DISABLE AsyncMode.
@@ -523,17 +523,17 @@ TEST_F(APZHitTestingTester, TestForceDis
 
   mcc->AdvanceByMillis(10);
 
   // With untransforming events we should get normal behaviour (in this case,
   // no noticeable untransform, because the repaint request already got
   // flushed).
   swi = ScrollWheelInput(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
     ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-    origin, 0, 0, false);
+    origin, 0, 0, false, WheelDeltaAdjustmentStrategy::eNone);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
   EXPECT_EQ(origin, swi.mOrigin);
 }
 
 TEST_F(APZHitTestingTester, Bug1148350) {
   CreateBug1148350LayerTree();
   ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
   manager->UpdateHitTestingTree(0, root, false, 0, 0);
--- a/gfx/layers/apz/test/gtest/TestTreeManager.cpp
+++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp
@@ -99,14 +99,14 @@ TEST_F(APZCTreeManagerTester, Bug1198900
   // crash.
   CreateSimpleDTCScrollingLayer();
   ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
   manager->UpdateHitTestingTree(0, root, false, 0, 0);
 
   ScreenPoint origin(100, 50);
   ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
     ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-    origin, 0, 10, false);
+    origin, 0, 10, false, WheelDeltaAdjustmentStrategy::eNone);
   uint64_t blockId;
   manager->ReceiveInputEvent(swi, nullptr, &blockId);
   manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true);
 }
 
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -668,32 +668,35 @@ ScrollWheelInput::ScrollWheelInput()
 {
 }
 
 ScrollWheelInput::ScrollWheelInput(uint32_t aTime, TimeStamp aTimeStamp,
                                    Modifiers aModifiers, ScrollMode aScrollMode,
                                    ScrollDeltaType aDeltaType,
                                    const ScreenPoint& aOrigin, double aDeltaX,
                                    double aDeltaY,
-                                   bool aAllowToOverrideSystemScrollSpeed)
+                                   bool aAllowToOverrideSystemScrollSpeed,
+                                   WheelDeltaAdjustmentStrategy
+                                     aWheelDeltaAdjustmentStrategy)
   : InputData(SCROLLWHEEL_INPUT, aTime, aTimeStamp, aModifiers)
   , mDeltaType(aDeltaType)
   , mScrollMode(aScrollMode)
   , mOrigin(aOrigin)
   , mHandledByAPZ(false)
   , mDeltaX(aDeltaX)
   , mDeltaY(aDeltaY)
   , mLineOrPageDeltaX(0)
   , mLineOrPageDeltaY(0)
   , mScrollSeriesNumber(0)
   , mUserDeltaMultiplierX(1.0)
   , mUserDeltaMultiplierY(1.0)
   , mMayHaveMomentum(false)
   , mIsMomentum(false)
   , mAllowToOverrideSystemScrollSpeed(aAllowToOverrideSystemScrollSpeed)
+  , mWheelDeltaAdjustmentStrategy(aWheelDeltaAdjustmentStrategy)
 {
 }
 
 ScrollWheelInput::ScrollWheelInput(const WidgetWheelEvent& aWheelEvent)
   : InputData(SCROLLWHEEL_INPUT, aWheelEvent.mTime, aWheelEvent.mTimeStamp,
               aWheelEvent.mModifiers)
   , mDeltaType(DeltaTypeForDeltaMode(aWheelEvent.mDeltaMode))
   , mScrollMode(SCROLLMODE_INSTANT)
@@ -704,16 +707,17 @@ ScrollWheelInput::ScrollWheelInput(const
   , mLineOrPageDeltaY(aWheelEvent.mLineOrPageDeltaY)
   , mScrollSeriesNumber(0)
   , mUserDeltaMultiplierX(1.0)
   , mUserDeltaMultiplierY(1.0)
   , mMayHaveMomentum(aWheelEvent.mMayHaveMomentum)
   , mIsMomentum(aWheelEvent.mIsMomentum)
   , mAllowToOverrideSystemScrollSpeed(
       aWheelEvent.mAllowToOverrideSystemScrollSpeed)
+  , mWheelDeltaAdjustmentStrategy(WheelDeltaAdjustmentStrategy::eNone)
 {
   mOrigin =
     ScreenPoint(ViewAs<ScreenPixel>(aWheelEvent.mRefPoint,
       PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
 }
 
 ScrollWheelInput::ScrollDeltaType
 ScrollWheelInput::DeltaTypeForDeltaMode(uint32_t aDeltaMode)
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -9,16 +9,17 @@
 #include "nsDebug.h"
 #include "nsIScrollableFrame.h"
 #include "nsPoint.h"
 #include "nsTArray.h"
 #include "Units.h"
 #include "mozilla/DefineEnum.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/WheelHandlingHelper.h"   // for WheelDeltaAdjustmentStrategy
 #include "mozilla/gfx/MatrixFwd.h"
 #include "mozilla/layers/KeyboardScrollAction.h"
 
 template<class E> struct already_AddRefed;
 class nsIWidget;
 
 namespace mozilla {
 
@@ -527,28 +528,54 @@ public:
       SCROLLMODE_INSTANT,
       SCROLLMODE_SMOOTH
     )
   );
 
   ScrollWheelInput(uint32_t aTime, TimeStamp aTimeStamp, Modifiers aModifiers,
                    ScrollMode aScrollMode, ScrollDeltaType aDeltaType,
                    const ScreenPoint& aOrigin, double aDeltaX, double aDeltaY,
-                   bool aAllowToOverrideSystemScrollSpeed);
+                   bool aAllowToOverrideSystemScrollSpeed,
+                   WheelDeltaAdjustmentStrategy aWheelDeltaAdjustmentStrategy);
   explicit ScrollWheelInput(const WidgetWheelEvent& aEvent);
 
   static ScrollDeltaType DeltaTypeForDeltaMode(uint32_t aDeltaMode);
   static uint32_t DeltaModeForDeltaType(ScrollDeltaType aDeltaType);
   static nsIScrollableFrame::ScrollUnit ScrollUnitForDeltaType(ScrollDeltaType aDeltaType);
 
   WidgetWheelEvent ToWidgetWheelEvent(nsIWidget* aWidget) const;
   bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
 
   bool IsCustomizedByUserPrefs() const;
 
+  // The following two functions are for auto-dir scrolling. For detailed
+  // information on auto-dir, @see mozilla::WheelDeltaAdjustmentStrategy
+  bool IsAutoDir() const
+  {
+    switch (mWheelDeltaAdjustmentStrategy) {
+      case WheelDeltaAdjustmentStrategy::eAutoDir:
+      case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
+        return true;
+    }
+    return false;
+  }
+  // Indicates which element this scroll honours if it's an auto-dir scroll.
+  // If true, honour the root element; otherwise, honour the currently scrolling
+  // target.
+  // Note that if IsAutoDir() returns false, then this function also returns
+  // false, but false in this case is meaningless as IsAutoDir() indicates it's
+  // not an auto-dir scroll.
+  // For detailed information on auto-dir,
+  // @see mozilla::WheelDeltaAdjustmentStrategy
+  bool HonoursRoot() const
+  {
+    return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour ==
+             mWheelDeltaAdjustmentStrategy;
+  }
+
   // Warning, this class is serialized and sent over IPC. Any change to its
   // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
   ScrollDeltaType mDeltaType;
   ScrollMode mScrollMode;
   ScreenPoint mOrigin;
 
   bool mHandledByAPZ;
 
@@ -577,16 +604,20 @@ public:
 
   // User-set delta multipliers.
   double mUserDeltaMultiplierX;
   double mUserDeltaMultiplierY;
 
   bool mMayHaveMomentum;
   bool mIsMomentum;
   bool mAllowToOverrideSystemScrollSpeed;
+
+  // Sometimes a wheel event input's wheel delta should be adjusted. This member
+  // specifies how to adjust the wheel delta.
+  WheelDeltaAdjustmentStrategy mWheelDeltaAdjustmentStrategy;
 };
 
 class KeyboardInput : public InputData
 {
 public:
   typedef mozilla::layers::KeyboardScrollAction KeyboardScrollAction;
 
   // Note that if you change the first member in this enum(I.e. KEY_DOWN) to one
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -10,16 +10,17 @@
 #include <math.h>
 #include <unistd.h>
 
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/WeakPtr.h"
+#include "mozilla/WheelHandlingHelper.h"    // for WheelDeltaAdjustmentStrategy
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/layers/RenderTrace.h"
 #include <algorithm>
@@ -483,17 +484,23 @@ public:
             aVScroll = -aVScroll;
         }
 
         ScrollWheelInput input(aTime, GetEventTimeStamp(aTime), GetModifiers(aMetaState),
                                ScrollWheelInput::SCROLLMODE_SMOOTH,
                                ScrollWheelInput::SCROLLDELTA_PIXEL,
                                origin,
                                aHScroll, aVScroll,
-                               false);
+                               false,
+                               // XXX Do we need to support auto-dir scrolling
+                               // for Android widgets with a wheel device?
+                               // Currently, I just leave it unimplemented. If
+                               // we need to implement it, what's the extra work
+                               // to do?
+                               false, false);
 
         ScrollableLayerGuid guid;
         uint64_t blockId;
         nsEventStatus status = controller->InputBridge()->ReceiveInputEvent(input, &guid, &blockId);
 
         if (status == nsEventStatus_eConsumeNoDefault) {
             return true;
         }
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -12,16 +12,17 @@
 
 #include "nsChildView.h"
 #include "nsCocoaWindow.h"
 
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
+#include "mozilla/WheelHandlingHelper.h"    // for WheelDeltaAdjustmentStrategy
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/SimpleGestureEventBinding.h"
 #include "mozilla/dom/WheelEventBinding.h"
 
 #include "nsArrayUtils.h"
 #include "nsExceptionHandler.h"
 #include "nsObjCExceptions.h"
@@ -5031,33 +5032,53 @@ GetIntegerDeltaForEvent(NSEvent* aEvent)
   } else if (usePreciseDeltas) {
     // This is on 10.6 or old touchpads that don't have any phase information.
     ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
                                 ScrollWheelInput::SCROLLMODE_INSTANT,
                                 ScrollWheelInput::SCROLLDELTA_PIXEL,
                                 position,
                                 preciseDelta.x,
                                 preciseDelta.y,
-                                false);
+                                false,
+                                // This parameter is used for wheel delta
+                                // adjustment, such as auto-dir scrolling,
+                                // but we do't need to do anything special here
+                                // since this wheel event is sent to
+                                // DispatchAPZWheelInputEvent, which turns this
+                                // ScrollWheelInput back into a WidgetWheelEvent
+                                // and then it goes through the regular handling
+                                // in APZInputBridge. So passing |eNone| won't
+                                // pass up the necessary wheel delta adjustment.
+                                WheelDeltaAdjustmentStrategy::eNone);
     wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
     wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
     wheelEvent.mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(theEvent);
     geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
   } else {
     ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT;
     if (gfxPrefs::SmoothScrollEnabled() && gfxPrefs::WheelSmoothScrollEnabled()) {
       scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
     }
     ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
                                 scrollMode,
                                 ScrollWheelInput::SCROLLDELTA_LINE,
                                 position,
                                 lineOrPageDelta.x,
                                 lineOrPageDelta.y,
-                                false);
+                                false,
+                                // This parameter is used for wheel delta
+                                // adjustment, such as auto-dir scrolling,
+                                // but we do't need to do anything special here
+                                // since this wheel event is sent to
+                                // DispatchAPZWheelInputEvent, which turns this
+                                // ScrollWheelInput back into a WidgetWheelEvent
+                                // and then it goes through the regular handling
+                                // in APZInputBridge. So passing |eNone| won't
+                                // pass up the necessary wheel delta adjustment.
+                                WheelDeltaAdjustmentStrategy::eNone);
     wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
     wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
     geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -9,16 +9,17 @@
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/ContentCache.h"
 #include "mozilla/GfxMessageUtils.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
+#include "mozilla/WheelHandlingHelper.h"    // for WheelDeltaAdjustmentStrategy
 #include "mozilla/dom/Selection.h"
 #include "InputData.h"
 
 namespace IPC
 {
 
 template<>
 struct ParamTraits<mozilla::EventMessage> :
@@ -1312,16 +1313,24 @@ template<>
 struct ParamTraits<mozilla::ScrollWheelInput::ScrollMode>
   : public ContiguousEnumSerializerInclusive<
              mozilla::ScrollWheelInput::ScrollMode,
              mozilla::ScrollWheelInput::ScrollMode::SCROLLMODE_INSTANT,
              mozilla::ScrollWheelInput::sHighestScrollMode>
 {};
 
 template<>
+struct ParamTraits<mozilla::WheelDeltaAdjustmentStrategy> :
+  public ContiguousEnumSerializer<
+           mozilla::WheelDeltaAdjustmentStrategy,
+           mozilla::WheelDeltaAdjustmentStrategy(0),
+           mozilla::WheelDeltaAdjustmentStrategy::eSentinel>
+{};
+
+template<>
 struct ParamTraits<mozilla::ScrollWheelInput>
 {
   typedef mozilla::ScrollWheelInput paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
     WriteParam(aMsg, aParam.mDeltaType);
@@ -1334,16 +1343,17 @@ struct ParamTraits<mozilla::ScrollWheelI
     WriteParam(aMsg, aParam.mLineOrPageDeltaX);
     WriteParam(aMsg, aParam.mLineOrPageDeltaY);
     WriteParam(aMsg, aParam.mScrollSeriesNumber);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierX);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierY);
     WriteParam(aMsg, aParam.mMayHaveMomentum);
     WriteParam(aMsg, aParam.mIsMomentum);
     WriteParam(aMsg, aParam.mAllowToOverrideSystemScrollSpeed);
+    WriteParam(aMsg, aParam.mWheelDeltaAdjustmentStrategy);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mDeltaType) &&
            ReadParam(aMsg, aIter, &aResult->mScrollMode) &&
            ReadParam(aMsg, aIter, &aResult->mOrigin) &&
@@ -1353,17 +1363,19 @@ struct ParamTraits<mozilla::ScrollWheelI
            ReadParam(aMsg, aIter, &aResult->mLocalOrigin) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
            ReadParam(aMsg, aIter, &aResult->mScrollSeriesNumber) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) &&
            ReadParam(aMsg, aIter, &aResult->mMayHaveMomentum) &&
            ReadParam(aMsg, aIter, &aResult->mIsMomentum) &&
-           ReadParam(aMsg, aIter, &aResult->mAllowToOverrideSystemScrollSpeed);
+           ReadParam(aMsg, aIter,
+                     &aResult->mAllowToOverrideSystemScrollSpeed) &&
+           ReadParam(aMsg, aIter, &aResult->mWheelDeltaAdjustmentStrategy);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::KeyboardInput::KeyboardEventType>
   : public ContiguousEnumSerializer<
              mozilla::KeyboardInput::KeyboardEventType,
              mozilla::KeyboardInput::KeyboardEventType::KEY_DOWN,