Bug 1358017 - Part 4: Implements the auto-dir scrolling feature(without the "honour root" functionality) in APZ r=kats
authorZhang Junzhi <zjz@zjz.name>
Fri, 16 Mar 2018 19:23:53 +0800
changeset 469165 509187cd4c1a71d88e7d1e51ee5b614c6095a6d0
parent 469164 bf95e0e290de7c4ff2d77fdd5d314f9e87e46e92
child 469166 5ce6e1682420b489773156bd85caae8446a48e7b
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1358017
milestone61.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 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
@@ -1728,16 +1729,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();
@@ -1745,34 +1759,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 &&
@@ -1793,23 +1808,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;
 }
@@ -1982,16 +1997,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
 {
@@ -2041,16 +2076,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;
@@ -2079,20 +2124,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"
@@ -504,16 +504,23 @@ public:
    */
   CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
                                  const ScrollbarData& 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;
 
   /**
@@ -570,19 +577,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(LayersId{0}, root, false, LayersId{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, LayersId{0}, root, mcc);
   manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{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, LayersId{0}, root, mcc);
   manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{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, LayersId{0}, root, mcc);
   manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{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
@@ -683,32 +683,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)
@@ -719,16 +722,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,57 @@ 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;
+      default:
+        // Prevent compilation errors generated by -Werror=switch
+        break;
+    }
+    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 +607,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?
+                               WheelDeltaAdjustmentStrategy::eNone);
 
         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"
@@ -5008,33 +5009,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> :
@@ -1348,16 +1349,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);
@@ -1370,16 +1379,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) &&
@@ -1389,17 +1399,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,