Bug 1358017 - Part 6: Implements the auto-dir scrolling feature(without the "honour root" functionality) in non-APZ r?masayuki draft
authorZhang Junzhi <zjz@zjz.name>
Mon, 19 Mar 2018 17:05:45 +0800
changeset 774638 e684b2af70231ea94d14071a42ba5d0cee9bfca2
parent 774637 e94ae64d66d5f11d5259da5c18101d73665ee5fb
child 774639 d7fdbbf270d784d1dff2cd63a93bd5fad5b776aa
child 774722 8736665b9c55bb3e645ea14e0cf15f81b8f81d62
push id104460
push userbmo:zjz@zjz.name
push dateThu, 29 Mar 2018 08:46:31 +0000
reviewersmasayuki
bugs1358017
milestone61.0a1
Bug 1358017 - Part 6: Implements the auto-dir scrolling feature(without the "honour root" functionality) in non-APZ r?masayuki This commit implements the auto-dir scrolling functionality in non-APZ, based on part 1 to part 3. However, the functionality of mousewheel.autodir.honourroot is unimplemented in this commit. MozReview-Commit-ID: 2vYABOx4RkK
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
dom/events/WheelHandlingHelper.cpp
dom/events/WheelHandlingHelper.h
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2517,16 +2517,38 @@ EventStateManager::ComputeScrollTarget(n
                                        WidgetWheelEvent* aEvent,
                                        ComputeScrollTargetOptions aOptions)
 {
   if ((aOptions & INCLUDE_PLUGIN_AS_TARGET) &&
       !WheelPrefs::WheelEventsEnabledOnPlugins()) {
     aOptions = RemovePluginFromTarget(aOptions);
   }
 
+  bool isAutoDir = false;
+  bool honoursRoot = false;
+  if (MAY_BE_ADJUSTED_BY_AUTO_DIR & aOptions) {
+    WheelDeltaAdjustmentStrategy strategy =
+      GetWheelDeltaAdjustmentStrategy(*aEvent);
+    switch (strategy) {
+      case WheelDeltaAdjustmentStrategy::eAutoDir:
+        isAutoDir = true;
+        honoursRoot = false;
+        break;
+      case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
+        isAutoDir = true;
+        honoursRoot = true;
+    }
+  }
+  // If the scroll is respected as auto-dir, aDirection* should always be
+  // equivalent to the event's delta vlaues, because an auto-dir scroll is an
+  // actual scroll, it'll never be a tentative scroll. so it should always
+  // provides its real delta values, not tentative values.
+  MOZ_ASSERT(!isAutoDir ||
+             aDirectionX == aEvent->mDeltaX && aDirectionY == aEvent->mDeltaY);
+
   if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
     // If the user recently scrolled with the mousewheel, then they probably
     // want to scroll the same view as before instead of the view under the
     // cursor.  WheelTransaction tracks the frame currently being
     // scrolled with the mousewheel. We consider the transaction ended when the
     // mouse moves more than "mousewheel.transaction.ignoremovedelay"
     // milliseconds after the last scroll operation, or any time the mouse moves
     // out of the frame, or when more than "mousewheel.transaction.timeout"
@@ -2541,32 +2563,51 @@ EventStateManager::ComputeScrollTarget(n
           return lastScrollFrame;
         }
       }
       nsIScrollableFrame* scrollableFrame =
         lastScrollFrame->GetScrollTargetFrame();
       if (scrollableFrame) {
         nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
         MOZ_ASSERT(frameToScroll);
+        if (isAutoDir) {
+          ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent,
+                                                *lastScrollFrame,
+                                                honoursRoot);
+          // Note that calling this function will not always cause the delta to
+          // be adjusted, it only adjusts the delta when it should, because
+          // Adjust() internally calls ShouldBeAdjusted() before making
+          // adjustment.
+          adjuster.Adjust();
+        }
         return frameToScroll;
       }
     }
   }
 
   // If the event doesn't cause scroll actually, we cannot find scroll target
   // because we check if the event can cause scroll actually on each found
   // scrollable frame.
   if (!aDirectionX && !aDirectionY) {
     return nullptr;
   }
 
-  bool checkIfScrollableX =
-    aDirectionX && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
-  bool checkIfScrollableY =
-    aDirectionY && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
+  bool checkIfScrollableX;
+  bool checkIfScrollableY;
+  if (isAutoDir) {
+    // Always check the frame's scrollability in both the two directions for an
+    // auto-dir scroll.
+    checkIfScrollableX = true;
+    checkIfScrollableY = true;
+  } else {
+    checkIfScrollableX =
+      aDirectionX && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
+    checkIfScrollableY =
+      aDirectionY && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
+  }
 
   nsIFrame* scrollFrame =
     !(aOptions & START_FROM_PARENT) ? aTargetFrame :
                                       GetParentFrameToScroll(aTargetFrame);
   for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
     // Check whether the frame wants to provide us with a scrollable view.
     nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
     if (!scrollableFrame) {
@@ -2619,34 +2660,51 @@ EventStateManager::ComputeScrollTarget(n
     bool hiddenForV = (NS_STYLE_OVERFLOW_HIDDEN == ss.mVertical);
     bool hiddenForH = (NS_STYLE_OVERFLOW_HIDDEN == ss.mHorizontal);
     if ((hiddenForV && hiddenForH) ||
         (checkIfScrollableY && !checkIfScrollableX && hiddenForV) ||
         (checkIfScrollableX && !checkIfScrollableY && hiddenForH)) {
       continue;
     }
 
-    // For default action, we should climb up the tree if cannot scroll it
-    // by the event actually.
-    bool canScroll = WheelHandlingUtils::CanScrollOn(scrollableFrame,
-                                                     aDirectionX, aDirectionY);
+    // Computes whether the currently checked frame is scrollable by this wheel
+    // event.
+    bool canScroll = false;
+    if (isAutoDir) {
+      ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *scrollFrame, honoursRoot);
+      if (adjuster.ShouldBeAdjusted()) {
+        adjuster.Adjust();
+        canScroll = true;
+      } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame,
+                                                 aDirectionX, aDirectionY)) {
+        canScroll = true;
+      }
+    } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame,
+                                               aDirectionX, aDirectionY)) {
+      canScroll = true;
+    }
+
     // Comboboxes need special care.
     nsIComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame);
     if (comboBox) {
       if (comboBox->IsDroppedDown()) {
         // Don't propagate to parent when drop down menu is active.
         return canScroll ? frameToScroll : nullptr;
       }
       // Always propagate when not dropped down (even if focused).
       continue;
     }
 
     if (canScroll) {
       return frameToScroll;
     }
+
+    // Where we are at is the block ending in a for loop.
+    // The current frame has been checked to be unscrollable by this wheel
+    // event, continue the loop to check its parent, if any.
   }
 
   nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrame(
       aTargetFrame->PresShell()->GetRootFrame());
   aOptions =
     static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
   return newFrame ? ComputeScrollTarget(newFrame, aEvent, aOptions) : nullptr;
 }
@@ -2808,24 +2866,26 @@ EventStateManager::DoScrollText(nsIScrol
   // should be same as delta* values since they may be used as gesture event by
   // widget.  However, if there is another scrollable element in the ancestor
   // along the axis, probably users don't want the operation to cause
   // additional action such as moving history.  In such case, overflowDelta
   // values should stay zero.
   if (scrollFrameWeak.IsAlive()) {
     if (aEvent->mDeltaX &&
         overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
-        !ComputeScrollTarget(scrollFrame, aEvent,
-                             COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS)) {
+        !ComputeScrollTarget(
+           scrollFrame, aEvent,
+           COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR)) {
       aEvent->mOverflowDeltaX = aEvent->mDeltaX;
     }
     if (aEvent->mDeltaY &&
         overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
-        !ComputeScrollTarget(scrollFrame, aEvent,
-                             COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS)) {
+        !ComputeScrollTarget(
+           scrollFrame, aEvent,
+           COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR)) {
       aEvent->mOverflowDeltaY = aEvent->mDeltaY;
     }
   }
 
   NS_ASSERTION(aEvent->mOverflowDeltaX == 0 ||
     (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
     "The sign of mOverflowDeltaX is different from the scroll direction");
   NS_ASSERTION(aEvent->mOverflowDeltaY == 0 ||
@@ -3335,18 +3395,20 @@ EventStateManager::PostHandleEvent(nsPre
     }
     break;
   case eWheelOperationEnd:
     {
       MOZ_ASSERT(aEvent->IsTrusted());
       ScrollbarsForWheel::MayInactivate();
       WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
       nsIScrollableFrame* scrollTarget =
-        do_QueryFrame(ComputeScrollTarget(mCurrentTarget, wheelEvent,
-                                          COMPUTE_DEFAULT_ACTION_TARGET));
+        do_QueryFrame(
+          ComputeScrollTarget(
+            mCurrentTarget, wheelEvent,
+            COMPUTE_DEFAULT_ACTION_TARGET_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR));
       if (scrollTarget) {
         scrollTarget->ScrollSnap();
       }
     }
     break;
   case eWheel:
   case eWheelOperationStart:
     {
@@ -3375,22 +3437,30 @@ EventStateManager::PostHandleEvent(nsPre
       // If horizontalized, the delta values will be restored and its overflow
       // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
       // being destroyed.
       WheelDeltaHorizontalizer horizontalizer(*wheelEvent);
       if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
         horizontalizer.Horizontalize();
       }
 
+      // Since ComputeScrollTarget() may adjust the delta if the event is
+      // auto-dir. So we use |ESMAutoDirWheelDeltaRestorer| here.
+      // An instance of |ESMAutoDirWheelDeltaRestorer| is used to monitor
+      // auto-dir adjustment which may happen during its lifetime. If the delta
+      // values is adjusted during its lifetime, the instance will restore the
+      // adjusted delta when it's being destrcuted.
+      ESMAutoDirWheelDeltaRestorer restorer(*wheelEvent);
       // Check if the frame to scroll before checking the default action
       // because if the scroll target is a plugin, the default action should be
       // chosen by the plugin rather than by our prefs.
       nsIFrame* frameToScroll =
-        ComputeScrollTarget(mCurrentTarget, wheelEvent,
-                            COMPUTE_DEFAULT_ACTION_TARGET);
+        ComputeScrollTarget(
+          mCurrentTarget, wheelEvent,
+          COMPUTE_DEFAULT_ACTION_TARGET_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR);
       nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll);
       if (pluginFrame) {
         MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction());
         // Plugins should receive original values instead of adjusted values.
         horizontalizer.CancelHorizontalization();
         action = WheelPrefs::ACTION_SEND_TO_PLUGIN;
       }
 
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -771,17 +771,20 @@ protected:
   // These flags are used in ComputeScrollTarget(). Callers should use
   // COMPUTE_*.
   enum
   {
     PREFER_MOUSE_WHEEL_TRANSACTION               = 0x00000001,
     PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS = 0x00000002,
     PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS = 0x00000004,
     START_FROM_PARENT                            = 0x00000008,
-    INCLUDE_PLUGIN_AS_TARGET                     = 0x00000010
+    INCLUDE_PLUGIN_AS_TARGET                     = 0x00000010,
+    // Indicates the wheel scroll event being computed is an auto-dir scroll, so
+    // its delta may be adjusted after being computed.
+    MAY_BE_ADJUSTED_BY_AUTO_DIR                  = 0x00000020,
   };
   enum ComputeScrollTargetOptions
   {
     // At computing scroll target for legacy mouse events, we should return
     // first scrollable element even when it's not scrollable to the direction.
     COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET     = 0,
     // Default action prefers the scrolled element immediately before if it's
     // still under the mouse cursor.  Otherwise, it prefers the nearest
@@ -790,34 +793,50 @@ protected:
       (PREFER_MOUSE_WHEEL_TRANSACTION |
        PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS |
        PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS),
     // When this is specified, the result may be nsPluginFrame.  In such case,
     // the frame doesn't have nsIScrollableFrame interface.
     COMPUTE_DEFAULT_ACTION_TARGET                =
       (COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN |
        INCLUDE_PLUGIN_AS_TARGET),
+    COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR =
+      (COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN |
+       MAY_BE_ADJUSTED_BY_AUTO_DIR),
+    COMPUTE_DEFAULT_ACTION_TARGET_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR =
+      (COMPUTE_DEFAULT_ACTION_TARGET |
+       MAY_BE_ADJUSTED_BY_AUTO_DIR),
     // Look for the nearest scrollable ancestor which can be scrollable with
     // aEvent.
     COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS     =
       (PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS | START_FROM_PARENT),
     COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS     =
-      (PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS | START_FROM_PARENT)
+      (PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS | START_FROM_PARENT),
+    COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR =
+      (COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS | MAY_BE_ADJUSTED_BY_AUTO_DIR),
+    COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR =
+      (COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS | MAY_BE_ADJUSTED_BY_AUTO_DIR),
   };
   static ComputeScrollTargetOptions RemovePluginFromTarget(
                                       ComputeScrollTargetOptions aOptions)
   {
     switch (aOptions) {
       case COMPUTE_DEFAULT_ACTION_TARGET:
         return COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN;
+      case COMPUTE_DEFAULT_ACTION_TARGET_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR:
+        return COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN_AND_MAY_ADJUST_DELTA_FOR_AUTO_DIR;
       default:
         MOZ_ASSERT(!(aOptions & INCLUDE_PLUGIN_AS_TARGET));
         return aOptions;
     }
   }
+
+  // Be aware that this function might adjust the delta values in |aEvent| if it
+  // is auto-dir. For information on auto-dir,
+  // @see mozilla::WheelDeltaAdjustmentStrategy
   nsIFrame* ComputeScrollTarget(nsIFrame* aTargetFrame,
                                 WidgetWheelEvent* aEvent,
                                 ComputeScrollTargetOptions aOptions);
 
   nsIFrame* ComputeScrollTarget(nsIFrame* aTargetFrame,
                                 double aDirectionX,
                                 double aDirectionY,
                                 WidgetWheelEvent* aEvent,
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -701,9 +701,194 @@ AutoDirWheelDeltaAdjuster::Adjust()
   if (IsHorizontalContentRightToLeft()) {
     mDeltaX *= -1;
     mDeltaY *= -1;
   }
   mShouldBeAdjusted = false;
   OnAdjusted();
 }
 
+/******************************************************************/
+/* mozilla::ESMAutoDirWheelDeltaAdjuster                          */
+/******************************************************************/
+
+ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
+                                WidgetWheelEvent& aEvent,
+                                nsIFrame& aScrollFrame,
+                                bool aHonoursRoot)
+  : AutoDirWheelDeltaAdjuster(aEvent.mDeltaX, aEvent.mDeltaY)
+  , mLineOrPageDeltaX(aEvent.mLineOrPageDeltaX)
+  , mLineOrPageDeltaY(aEvent.mLineOrPageDeltaY)
+  , mOverflowDeltaX(aEvent.mOverflowDeltaX)
+  , mOverflowDeltaY(aEvent.mOverflowDeltaY)
+{
+  mScrollTargetFrame = aScrollFrame.GetScrollTargetFrame();
+  MOZ_ASSERT(mScrollTargetFrame);
+
+  // TODO Currently, the honoured target is always the current scrolling frame.
+  nsIFrame* honouredFrame = &aScrollFrame;
+
+  WritingMode writingMode = honouredFrame->GetWritingMode();
+  WritingMode::BlockDir blockDir = writingMode.GetBlockDir();
+  WritingMode::InlineDir inlineDir = writingMode.GetInlineDir();
+  // Get whether the honoured frame's content in the horizontal direction starts
+  // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
+  // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
+  mIsHorizontalContentRightToLeft =
+    blockDir == WritingMode::BlockDir::eBlockRL ||
+    (blockDir == WritingMode::BlockDir::eBlockTB &&
+      inlineDir == WritingMode::InlineDir::eInlineRTL);
+}
+
+void
+ESMAutoDirWheelDeltaAdjuster::OnAdjusted()
+{
+  // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
+  // ESM, we should continue to adjust line-or-page and overflow values.
+  if (mDeltaX) {
+    // A vertical scroll was adjusted to be horizontal.
+    MOZ_ASSERT(0 == mDeltaY);
+
+    mLineOrPageDeltaX = mLineOrPageDeltaY;
+    mLineOrPageDeltaY = 0;
+    mOverflowDeltaX = mOverflowDeltaY;
+    mOverflowDeltaY = 0;
+  } else {
+    // A horizontal scroll was adjusted to be vertical.
+    MOZ_ASSERT(0 != mDeltaY);
+
+    mLineOrPageDeltaY = mLineOrPageDeltaX;
+    mLineOrPageDeltaX = 0;
+    mOverflowDeltaY = mOverflowDeltaX;
+    mOverflowDeltaX = 0;
+  }
+  if (mIsHorizontalContentRightToLeft) {
+    // If in RTL writing mode, reverse the side the scroll will go towards.
+    mLineOrPageDeltaX *= -1;
+    mLineOrPageDeltaY *= -1;
+    mOverflowDeltaX *= -1;
+    mOverflowDeltaY *= -1;
+  }
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const
+{
+  return mScrollTargetFrame->GetPerceivedScrollingDirections() &
+           nsIScrollableFrame::HORIZONTAL;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const
+{
+  return mScrollTargetFrame->GetPerceivedScrollingDirections() &
+           nsIScrollableFrame::VERTICAL;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const
+{
+  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+  return static_cast<double>(scrollRange.y) < scrollPt.y;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const
+{
+  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+  return static_cast<double>(scrollRange.YMost()) > scrollPt.y;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const
+{
+  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+  return static_cast<double>(scrollRange.x) < scrollPt.x;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const
+{
+  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+  return static_cast<double>(scrollRange.XMost()) > scrollPt.x;
+}
+
+bool
+ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const
+{
+  return mIsHorizontalContentRightToLeft;
+}
+
+/******************************************************************/
+/* mozilla::ESMAutoDirWheelDeltaRestorer                          */
+/******************************************************************/
+
+ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
+                                WidgetWheelEvent& aEvent)
+  : mEvent (aEvent)
+  , mOldDeltaX (aEvent.mDeltaX)
+  , mOldDeltaY (aEvent.mDeltaY)
+  , mOldLineOrPageDeltaX (aEvent.mLineOrPageDeltaX)
+  , mOldLineOrPageDeltaY (aEvent.mLineOrPageDeltaY)
+  , mOldOverflowDeltaX (aEvent.mOverflowDeltaX)
+  , mOldOverflowDeltaY (aEvent.mOverflowDeltaY)
+{
+}
+
+ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer()
+{
+  if (mOldDeltaX == mEvent.mDeltaX || mOldDeltaY == mEvent.mDeltaY) {
+    // The delta of the event wasn't adjusted during the lifetime of this
+    // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
+    return;
+  }
+
+  bool forRTL = false;
+
+  // First, restore the basic deltaX and deltaY.
+  std::swap(mEvent.mDeltaX, mEvent.mDeltaY);
+  if (mOldDeltaX != mEvent.mDeltaX || mOldDeltaY != mEvent.mDeltaY) {
+    // If X and Y still don't equal to their original values after being
+    // swapped, then it must be because they were adjusted for RTL.
+    forRTL = true;
+    mEvent.mDeltaX *= -1;
+    mEvent.mDeltaY *= -1;
+    MOZ_ASSERT(mOldDeltaX == mEvent.mDeltaX && mOldDeltaY == mEvent.mDeltaY);
+  }
+
+  if (mEvent.mDeltaX) {
+    // A horizontal scroll was adjusted to be vertical during the lifetime of
+    // this instance.
+    MOZ_ASSERT(0 == mEvent.mDeltaY);
+
+    // Restore the line-or-page and overflow values to be horizontal.
+    if (forRTL) {
+      mEvent.mOverflowDeltaX = -mEvent.mOverflowDeltaY;
+      mEvent.mLineOrPageDeltaX = -mEvent.mLineOrPageDeltaY;
+    } else {
+      mEvent.mOverflowDeltaX = mEvent.mOverflowDeltaY;
+      mEvent.mLineOrPageDeltaX = mEvent.mLineOrPageDeltaY;
+    }
+    mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
+    mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
+  } else {
+    // A vertical scroll was adjusted to be horizontal during the lifetime of
+    // this instance.
+    MOZ_ASSERT(0 != mEvent.mDeltaY);
+
+    // Restore the line-or-page and overflow values to be vertical.
+    if (forRTL) {
+      mEvent.mOverflowDeltaY = -mEvent.mOverflowDeltaX;
+      mEvent.mLineOrPageDeltaY = -mEvent.mLineOrPageDeltaX;
+    } else {
+      mEvent.mOverflowDeltaY = mEvent.mOverflowDeltaX;
+      mEvent.mLineOrPageDeltaY = mEvent.mLineOrPageDeltaX;
+    }
+    mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
+    mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
+  }
+}
+
 } // namespace mozilla
--- a/dom/events/WheelHandlingHelper.h
+++ b/dom/events/WheelHandlingHelper.h
@@ -339,16 +339,18 @@ public:
   bool ShouldBeAdjusted();
   /**
    * Adjusts the values of the delta values for auto-dir scrolling when
    * ShouldBeAdjusted() returns true. If you call it when ShouldBeAdjusted()
    * returns false, this function will simply do nothing.
    */
   void Adjust();
 
+  using DeltaValueType = double;
+
 private:
   /**
    * Called by Adjust() if Adjust() successfully adjusted the delta values.
    */
   virtual void OnAdjusted()
   {
   }
 
@@ -369,19 +371,90 @@ private:
    *         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.
    */
   virtual bool IsHorizontalContentRightToLeft() const = 0;
 
 protected:
-  double& mDeltaX;
-  double& mDeltaY;
+  DeltaValueType& mDeltaX;
+  DeltaValueType& mDeltaY;
 
 private:
   bool mCheckedIfShouldBeAdjusted;
   bool mShouldBeAdjusted;
 };
 
+/**
+ * This is the implementation of AutoDirWheelDeltaAdjuster for EventStateManager
+ *
+ * Detailed comments about some member functions are given in the base class
+ * AutoDirWheelDeltaAdjuster.
+ */
+class MOZ_STACK_CLASS ESMAutoDirWheelDeltaAdjuster final
+                        : public AutoDirWheelDeltaAdjuster
+{
+public:
+  /**
+   * @param aEvent             The auto-dir wheel scroll event.
+   * @param aScrollFrame       The scroll target for the event.
+   * @param aHonoursRoot       If set to true, the honoured frame is the root
+   *                           frame in the same document where the target is;
+   *                           If false, the honoured frame is the scroll
+   *                           target. For the concept of an honoured target,
+   *                           @see mozilla::WheelDeltaAdjustmentStrategy
+   */
+  ESMAutoDirWheelDeltaAdjuster(WidgetWheelEvent& aEvent,
+                               nsIFrame& aScrollFrame,
+                               bool aHonoursRoot);
+
+  using LineOrPageDeltaValueType = int32_t;
+  using OverflowDeltaValueType = double;
+
+private:
+  virtual void OnAdjusted() override;
+  virtual bool CanScrollAlongXAxis() const override;
+  virtual bool CanScrollAlongYAxis() const override;
+  virtual bool CanScrollUpwards() const override;
+  virtual bool CanScrollDownwards() const override;
+  virtual bool CanScrollLeftwards() const override;
+  virtual bool CanScrollRightwards() const override;
+  virtual bool IsHorizontalContentRightToLeft() const override;
+
+  nsIScrollableFrame* mScrollTargetFrame;
+  bool mIsHorizontalContentRightToLeft;
+
+  LineOrPageDeltaValueType& mLineOrPageDeltaX;
+  LineOrPageDeltaValueType& mLineOrPageDeltaY;
+  OverflowDeltaValueType& mOverflowDeltaX;
+  OverflowDeltaValueType& mOverflowDeltaY;
+};
+
+/**
+ * This class is used for restoring the delta in an auto-dir wheel.
+ *
+ * An instance of this calss monitors auto-dir adjustment which may happen
+ * during its lifetime. If the delta values is adjusted during its lifetime, the
+ * instance will restore the adjusted delta when it's being destrcuted.
+ */
+class MOZ_STACK_CLASS ESMAutoDirWheelDeltaRestorer final
+{
+public:
+  /**
+   * @param aEvent             The wheel scroll event to be monitored.
+   */
+  ESMAutoDirWheelDeltaRestorer(WidgetWheelEvent& aEvent);
+  ~ESMAutoDirWheelDeltaRestorer();
+
+private:
+  WidgetWheelEvent& mEvent;
+  AutoDirWheelDeltaAdjuster::DeltaValueType mOldDeltaX;
+  AutoDirWheelDeltaAdjuster::DeltaValueType mOldDeltaY;
+  ESMAutoDirWheelDeltaAdjuster::LineOrPageDeltaValueType mOldLineOrPageDeltaX;
+  ESMAutoDirWheelDeltaAdjuster::LineOrPageDeltaValueType mOldLineOrPageDeltaY;
+  ESMAutoDirWheelDeltaAdjuster::OverflowDeltaValueType mOldOverflowDeltaX;
+  ESMAutoDirWheelDeltaAdjuster::OverflowDeltaValueType mOldOverflowDeltaY;
+};
+
 } // namespace mozilla
 
 #endif // mozilla_WheelHandlingHelper_h_