Bug 1358017 - Part 1: Introduces a new feature: smart wheel scrolling(only as per the current-target's writing-mode) r?masayuki, kats draft
authorZhang Junzhi <zjz@zjz.name>
Thu, 08 Mar 2018 20:50:03 +0800
changeset 764825 50e72e3b367b8a4ec0959674d5fa3b50c4e2a338
parent 764770 a6a32fb286fa9e5d5f6d5b3b77423ab6b96c9502
push id101864
push userbmo:zjz@zjz.name
push dateThu, 08 Mar 2018 12:50:36 +0000
reviewersmasayuki, kats
bugs1358017
milestone60.0a1
Bug 1358017 - Part 1: Introduces a new feature: smart wheel scrolling(only as per the current-target's writing-mode) r?masayuki, kats In some situations, the user would like to swap DeltaY with DeltaX in a wheel event. For example, the user is navigating a vertical writing page which only overflows horizontally, or is reading the text which overflows in a single-line text input in horizontal writing mode, but the user only has one vertical wheel with the mouse. It can be more convenient to scroll the content horizontally if the browser automatically swaps DeltaY with DeltaX in such situations. So this patch introduces a new feature: smart wheel scrolling, which implements the idea. This part only implements the solution where a smartized scrolling direction is based on the writing-mode of its target element, and also it's only for APZ. The other solution(A smartized scrolling direction is based on the writing-mode of the root scrollable element) and non-APZ part will be implemented in another commit. MozReview-Commit-ID: HAZpY3MebqX
browser/app/profile/firefox.js
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
dom/events/WheelHandlingHelper.cpp
dom/events/WheelHandlingHelper.h
gfx/layers/FrameMetrics.h
gfx/layers/apz/public/IAPZCTreeManager.cpp
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/Axis.cpp
gfx/layers/apz/src/Axis.h
gfx/layers/apz/src/WheelDeltaSmartizer.h
gfx/layers/apz/test/gtest/InputUtils.h
gfx/layers/apz/test/gtest/TestHitTesting.cpp
gfx/layers/apz/test/gtest/TestTreeManager.cpp
modules/libpref/init/all.js
widget/InputData.cpp
widget/InputData.h
widget/MouseEvents.h
widget/android/nsWindow.cpp
widget/cocoa/nsChildView.mm
widget/nsGUIEventIPC.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -619,20 +619,23 @@ pref("browser.gesture.twist.threshold", 
 pref("browser.gesture.twist.right", "cmd_gestureRotateRight");
 pref("browser.gesture.twist.left", "cmd_gestureRotateLeft");
 pref("browser.gesture.twist.end", "cmd_gestureRotateEnd");
 pref("browser.gesture.tap", "cmd_fullZoomReset");
 
 pref("browser.snapshots.limit", 0);
 
 // 0: Nothing happens
-// 1: Scrolling contents
+// 1: Scrolling contents smartly, that is, you can scroll a target in the
+//    direction orthogonal to the wheel if there's no scrollbar in the wheel's
+//    direction but a scrollbar in the wheel's orthogonal direction
 // 2: Go back or go forward, in your history
 // 3: Zoom in or out.
 // 4: Treat vertical wheel as horizontal scroll
+// 5: Scroll contents
 #ifdef XP_MACOSX
 // On macOS, if the wheel has one axis only, shift+wheel comes through as a
 // horizontal scroll event. Thus, we can't assign anything other than normal
 // scrolling to shift+wheel.
 pref("mousewheel.with_shift.action", 1);
 pref("mousewheel.with_alt.action", 2);
 // On MacOS X, control+wheel is typically handled by system and we don't
 // receive the event.  So, command key which is the main modifier key for
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3361,37 +3361,45 @@ EventStateManager::PostHandleEvent(nsPre
 
       // When APZ is enabled, the actual scroll animation might be handled by
       // the compositor.
       WheelPrefs::Action action =
         wheelEvent->mFlags.mHandledByAPZ ?
           WheelPrefs::ACTION_NONE :
           WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
 
-      // Make the wheel event a horizontal scroll event.  I.e., deltaY values
+      Maybe<WheelDeltaAdjustmentStrategy> strategy =
+        GetWheelDeltaAdjustmentStrategy(*wheelEvent);
+      // Adjust the delta values of the wheel event if the current default
+      // action is to horizontalize a vertical wheel scroll. I.e., deltaY values
       // are set to deltaX and deltaY and deltaZ values are set to 0.
-      // When AutoWheelDeltaAdjuster instance is destroyed, the delta values
-      // are restored and make overflow deltaX becomes 0.
-      AutoWheelDeltaAdjuster adjuster(*wheelEvent);
+      // In case of a horizontalized scroll, the delta values will be restored
+      // and its overflow deltaX will become 0 when the WheelDeltaHorizontalizer
+      // instance is being destroyed.
+      WheelDeltaHorizontalizer horizontalizer(*wheelEvent);
+      if (strategy == Some(WheelDeltaAdjustmentStrategy::eHorizontalize)) {
+        horizontalizer.horizontalize();
+      }
 
       // 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);
       nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll);
       if (pluginFrame) {
         MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction());
         action = WheelPrefs::ACTION_SEND_TO_PLUGIN;
       }
 
       switch (action) {
-        case WheelPrefs::ACTION_SCROLL:
-        case WheelPrefs::ACTION_HORIZONTAL_SCROLL: {
+        case WheelPrefs::ACTION_SMART_SCROLL:
+        case WheelPrefs::ACTION_HORIZONTAL_SCROLL:
+        case WheelPrefs::ACTION_SCROLL: {
           // For scrolling of default action, we should honor the mouse wheel
           // transaction.
 
           ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget, wheelEvent);
 
           if (aEvent->mMessage != eWheel ||
               (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
             break;
@@ -5879,20 +5887,21 @@ EventStateManager::WheelPrefs::Init(Even
 
   nsAutoCString prefNameZ(basePrefName);
   prefNameZ.AppendLiteral("delta_multiplier_z");
   mMultiplierZ[aIndex] =
     static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
 
   nsAutoCString prefNameAction(basePrefName);
   prefNameAction.AppendLiteral("action");
-  int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
+  int32_t action = Preferences::GetInt(prefNameAction.get(),
+                                       ACTION_SMART_SCROLL);
   if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
     NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
-    action = ACTION_SCROLL;
+    action = ACTION_SMART_SCROLL;
   }
   mActions[aIndex] = static_cast<Action>(action);
 
   // Compute action values overridden by .override_x pref.
   // At present, override is possible only for the x-direction
   // because this pref is introduced mainly for tilt wheels.
   // Note that ACTION_HORIZONTAL_SCROLL isn't a valid value for this pref
   // because it affects only to deltaY.
@@ -5916,17 +5925,17 @@ EventStateManager::WheelPrefs::GetMultip
                                  double* aMultiplierForDeltaY)
 {
   // If the event should be treated as horizontal wheel operation, deltaY
   // should be multiplied by mMultiplierY, however, it might be moved to
   // deltaX for handling default action.  In such case, we need to treat
   // mMultiplierX and mMultiplierY as swapped.
   *aMultiplierForDeltaX = mMultiplierX[aIndex];
   *aMultiplierForDeltaY = mMultiplierY[aIndex];
-  if (aEvent->mDeltaValuesAdjustedForDefaultHandler &&
+  if (aEvent->mDeltaValuesHorizontalizedForDefaultHandler &&
       ComputeActionFor(aEvent) == ACTION_HORIZONTAL_SCROLL) {
     std::swap(*aMultiplierForDeltaX, *aMultiplierForDeltaY);
   }
 }
 
 void
 EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(WidgetWheelEvent* aEvent)
 {
@@ -5989,29 +5998,33 @@ EventStateManager::WheelPrefs::ComputeAc
 {
   Index index = GetIndexFor(aEvent);
   Init(index);
 
   bool deltaXPreferred =
     (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
      Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
   Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
-  if (actions[index] == ACTION_NONE ||
-      actions[index] == ACTION_SCROLL ||
-      actions[index] == ACTION_HORIZONTAL_SCROLL) {
-    return actions[index];
+  switch (actions[index]) {
+    case ACTION_NONE:
+    case ACTION_SMART_SCROLL:
+    case ACTION_HORIZONTAL_SCROLL:
+    case ACTION_SCROLL:
+      return actions[index];
   }
 
   // Momentum events shouldn't run special actions.
   if (aEvent->mIsMomentum) {
     // Use the default action.  Note that user might kill the wheel scrolling.
     Init(INDEX_DEFAULT);
-    if (actions[INDEX_DEFAULT] == ACTION_SCROLL ||
-        actions[INDEX_DEFAULT] == ACTION_HORIZONTAL_SCROLL) {
-      return actions[INDEX_DEFAULT];
+    switch (actions[INDEX_DEFAULT]) {
+      case ACTION_SMART_SCROLL:
+      case ACTION_HORIZONTAL_SCROLL:
+      case ACTION_SCROLL:
+        return actions[INDEX_DEFAULT];
     }
     return ACTION_NONE;
   }
 
   return actions[index];
 }
 
 bool
@@ -6053,33 +6066,41 @@ EventStateManager::WheelPrefs::WheelEven
 
 // static
 bool
 EventStateManager::WheelEventIsScrollAction(const WidgetWheelEvent* aEvent)
 {
   if (aEvent->mMessage != eWheel) {
     return false;
   }
-  WheelPrefs::Action action =
-    WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
-  return action == WheelPrefs::ACTION_SCROLL ||
-         action == WheelPrefs::ACTION_HORIZONTAL_SCROLL;
+  switch (WheelPrefs::GetInstance()->ComputeActionFor(aEvent)) {
+    case WheelPrefs::ACTION_SMART_SCROLL:
+    case WheelPrefs::ACTION_HORIZONTAL_SCROLL:
+    case WheelPrefs::ACTION_SCROLL:
+      return true;
+  }
+  return false;
 }
 
 // static
-bool
-EventStateManager::WheelEventIsHorizontalScrollAction(
-                     const WidgetWheelEvent* aEvent)
+Maybe<WheelDeltaAdjustmentStrategy>
+EventStateManager::GetWheelDeltaAdjustmentStrategy(
+                     const WidgetWheelEvent& aEvent)
 {
-  if (aEvent->mMessage != eWheel) {
-    return false;
-  }
-  WheelPrefs::Action action =
-    WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
-  return action == WheelPrefs::ACTION_HORIZONTAL_SCROLL;
+  if (aEvent.mMessage != eWheel) {
+    return Nothing();
+  }
+  switch (WheelPrefs::GetInstance()->ComputeActionFor(&aEvent)) {
+    case WheelPrefs::ACTION_HORIZONTAL_SCROLL:
+      return Some(WheelDeltaAdjustmentStrategy::eHorizontalize);
+    case WheelPrefs::ACTION_SMART_SCROLL:
+      if (0 == aEvent.mDeltaZ)
+        return Some(WheelDeltaAdjustmentStrategy::eSmartize);
+  }
+  return Nothing();
 }
 
 void
 EventStateManager::GetUserPrefsForWheelEvent(const WidgetWheelEvent* aEvent,
                                              double* aOutMultiplierX,
                                              double* aOutMultiplierY)
 {
   WheelPrefs::GetInstance()->GetUserPrefsForEvent(
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -12,16 +12,17 @@
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIFrame.h"
 #include "Units.h"
+#include "WheelHandlingHelper.h"          // for WheelDeltaAdjustmentStrategy
 
 #define NS_USER_INTERACTION_INTERVAL 5000 // ms
 
 class nsFrameLoader;
 class nsIContent;
 class nsIDocument;
 class nsIDocShell;
 class nsIDocShellTreeItem;
@@ -297,19 +298,20 @@ public:
   // Sets the full-screen event state on aElement to aIsFullScreen.
   static void SetFullScreenState(dom::Element* aElement, bool aIsFullScreen);
 
   static bool IsRemoteTarget(nsIContent* aTarget);
 
   // Returns true if the given WidgetWheelEvent will resolve to a scroll action.
   static bool WheelEventIsScrollAction(const WidgetWheelEvent* aEvent);
 
-  // Returns true if the given WidgetWheelEvent will resolve to a horizontal
-  // scroll action but it's a vertical wheel operation.
-  static bool WheelEventIsHorizontalScrollAction(const WidgetWheelEvent* aEvet);
+  // In some situations, the delta values of WidgetWheelEvent may be adjusted
+  // according to a wheel delta adjustment strategy.
+  static Maybe<WheelDeltaAdjustmentStrategy>
+  GetWheelDeltaAdjustmentStrategy(const WidgetWheelEvent& aEvent);
 
   // Returns user-set multipliers for a wheel event.
   static void GetUserPrefsForWheelEvent(const WidgetWheelEvent* aEvent,
                                         double* aOutMultiplierX,
                                         double* aOutMultiplierY);
 
   // Holds the point in screen coords that a mouse event was dispatched to,
   // before we went into pointer lock mode. This is constantly updated while
@@ -546,21 +548,22 @@ protected:
     void CancelApplyingUserPrefsFromOverflowDelta(WidgetWheelEvent* aEvent);
 
     /**
      * Computes the default action for the aEvent with the prefs.
      */
     enum Action : uint8_t
     {
       ACTION_NONE = 0,
-      ACTION_SCROLL,
+      ACTION_SMART_SCROLL,
       ACTION_HISTORY,
       ACTION_ZOOM,
       ACTION_HORIZONTAL_SCROLL,
-      ACTION_LAST = ACTION_HORIZONTAL_SCROLL,
+      ACTION_SCROLL,
+      ACTION_LAST = ACTION_SCROLL,
       // Following actions are used only by internal processing.  So, cannot
       // specified by prefs.
       ACTION_SEND_TO_PLUGIN,
     };
     Action ComputeActionFor(const WidgetWheelEvent* aEvent);
 
     /**
      * NeedToComputeLineOrPageDelta() returns if the aEvent needs to be
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -1,16 +1,18 @@
 /* -*- 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/. */
 
 #include "WheelHandlingHelper.h"
 
+#include <utility>                      // for std::swap
+
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
@@ -578,51 +580,167 @@ WheelTransaction::Prefs::InitializeStati
     Preferences::AddUintVarCache(&sMouseWheelTransactionIgnoreMoveDelay,
                                  "mousewheel.transaction.ignoremovedelay", 100);
     Preferences::AddBoolVarCache(&sTestMouseScroll, "test.mousescroll", false);
     sIsInitialized = true;
   }
 }
 
 /******************************************************************/
-/* mozilla::AutoWheelDeltaAdjuster                                */
+/* mozilla::WheelDeltaHorizontalizer                              */
 /******************************************************************/
 
-AutoWheelDeltaAdjuster::AutoWheelDeltaAdjuster(WidgetWheelEvent& aWheelEvent)
-  : mWheelEvent(aWheelEvent)
-  , mOldDeltaX(aWheelEvent.mDeltaX)
-  , mOldDeltaZ(aWheelEvent.mDeltaZ)
-  , mOldOverflowDeltaX(aWheelEvent.mOverflowDeltaX)
-  , mOldLineOrPageDeltaX(aWheelEvent.mLineOrPageDeltaX)
-  , mTreatedVerticalWheelAsHorizontalScroll(false)
+void
+WheelDeltaHorizontalizer::horizontalize()
 {
-  MOZ_ASSERT(!aWheelEvent.mDeltaValuesAdjustedForDefaultHandler);
+  MOZ_ASSERT(!aWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler);
+
+  // Log the old values.
+  mOldDeltaX = mWheelEvent.mDeltaX;
+  mOldDeltaZ = mWheelEvent.mDeltaZ;
+  mOldOverflowDeltaX = mWheelEvent.mOverflowDeltaX;
+  mOldLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaX;
 
-  if (EventStateManager::WheelEventIsHorizontalScrollAction(&aWheelEvent)) {
-    // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
-    mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
-    mWheelEvent.mDeltaY = 0.0;
-    mWheelEvent.mDeltaZ = 0.0;
-    mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
-    mWheelEvent.mOverflowDeltaY = 0.0;
-    mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
-    mWheelEvent.mLineOrPageDeltaY = 0;
-    mWheelEvent.mDeltaValuesAdjustedForDefaultHandler = true;
-    mTreatedVerticalWheelAsHorizontalScroll = true;
-  }
+  // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
+  mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
+  mWheelEvent.mDeltaY = 0.0;
+  mWheelEvent.mDeltaZ = 0.0;
+  mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
+  mWheelEvent.mOverflowDeltaY = 0.0;
+  mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
+  mWheelEvent.mLineOrPageDeltaY = 0;
+  mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = true;
+
+  // Mark it horizontalized in order to restore the delta values when this
+  // instance is being destroyed.
+  mHorizontalized = true;
 }
 
-AutoWheelDeltaAdjuster::~AutoWheelDeltaAdjuster()
+WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer()
 {
-  if (mTreatedVerticalWheelAsHorizontalScroll &&
-      mWheelEvent.mDeltaValuesAdjustedForDefaultHandler) {
+  if (mHorizontalized &&
+      mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler) {
     mWheelEvent.mDeltaY = mWheelEvent.mDeltaX;
     mWheelEvent.mDeltaX = mOldDeltaX;
     mWheelEvent.mDeltaZ = mOldDeltaZ;
     mWheelEvent.mOverflowDeltaY = mWheelEvent.mOverflowDeltaX;
     mWheelEvent.mOverflowDeltaX = mOldOverflowDeltaX;
     mWheelEvent.mLineOrPageDeltaY = mWheelEvent.mLineOrPageDeltaX;
     mWheelEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
-    mWheelEvent.mDeltaValuesAdjustedForDefaultHandler = false;
+    mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = false;
   }
 }
 
+/******************************************************************/
+/* mozilla::WheelDeltaSmartizer                                   */
+/******************************************************************/
+
+bool
+WheelDeltaSmartizer::ShouldBeSmartized()
+{
+  // Sometimes, this function can be called more than one time. If we have
+  // already checked if the scroll should be smartized, there's no need to check
+  // it again.
+  if (mCheckedIfShouldBeSmartized) {
+    return mShouldBeSmartized;
+  }
+  mCheckedIfShouldBeSmartized = true;
+
+  // For a smart wheel scroll, if all the following conditions are met, we
+  // should switch X and Y values:
+  // 1. There is only one non-zero value between DeltaX and DeltaY.
+  // 2. There is only one direction for the target that overflows and is
+  //    scrollable with wheel.
+  // 3. The direction described in Condition 1 is orthogonal to the one
+  // described in Condition 2.
+  if (0 != mDeltaX) {
+    if (0 == mDeltaY) {
+      if (!CanScrollAlongXAxis()) {
+        mIsHorizontalContentRightToLeft = IsHorizontalContentRightToLeft();
+        if (mIsHorizontalContentRightToLeft) {
+          mShouldBeSmartized = mDeltaX > 0 ? CanScrollUpwards()
+                                           : CanScrollDownwards();
+        } else {
+          mShouldBeSmartized = mDeltaX < 0 ? CanScrollUpwards()
+                                           : CanScrollDownwards();
+        }
+        return mShouldBeSmartized;
+      }
+    }
+  } else if (0 != mDeltaY) {
+    if (!CanScrollAlongYAxis()) {
+      mIsHorizontalContentRightToLeft = IsHorizontalContentRightToLeft();
+      if (mIsHorizontalContentRightToLeft) {
+        mShouldBeSmartized = mDeltaY > 0 ? CanScrollLeftwards()
+                                         : CanScrollRightwards();
+      } else {
+        mShouldBeSmartized = mDeltaY < 0 ? CanScrollLeftwards()
+                                         : CanScrollRightwards();
+      }
+      return mShouldBeSmartized;
+    }
+  }
+  mShouldBeSmartized = false;
+  return false;
+}
+
+void
+WheelDeltaSmartizer::smartize()
+{
+  if (!ShouldBeSmartized()) {
+    return;
+  }
+  std::swap(mDeltaX, mDeltaY);
+  std::swap(mDeltaMultiplierX, mDeltaMultiplierY);
+  if (mIsHorizontalContentRightToLeft) {
+    mDeltaX = -mDeltaX;
+    mDeltaY = -mDeltaY;
+  }
+  mShouldBeSmartized = false;
+}
+
+// Ideally, it's not necessary to define bodies for pure virtual functions,
+// However, we want this class to be exported to some other component(i.e. APZ).
+// If we don't define them, it will fail with undefined reference errors at
+// linking stage.
+// virtual
+bool WheelDeltaSmartizer::CanScrollAlongXAxis() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::CanScrollAlongYAxis() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::CanScrollUpwards() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::CanScrollDownwards() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::CanScrollLeftwards() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::CanScrollRightwards() const // = 0
+{
+  return false;
+}
+
+// virtual
+bool WheelDeltaSmartizer::IsHorizontalContentRightToLeft() const // = 0
+{
+  return false;
+}
+
 } // namespace mozilla
--- a/dom/events/WheelHandlingHelper.h
+++ b/dom/events/WheelHandlingHelper.h
@@ -205,40 +205,138 @@ protected:
     static int32_t sMouseWheelAccelerationStart;
     static int32_t sMouseWheelAccelerationFactor;
     static uint32_t sMouseWheelTransactionTimeout;
     static uint32_t sMouseWheelTransactionIgnoreMoveDelay;
     static bool sTestMouseScroll;
   };
 };
 
+enum class WheelDeltaAdjustmentStrategy
+{
+  // Treat a *pure* vertical wheel event as if it was a horizontal scroll.
+  eHorizontalize,
+  // If the target is only wheel-scrollable in vertical direction, treat it as
+  // if it was a vertical scroll; If the target is only wheel-scrollable in
+  // horizontal direction, treat it as if it was a horizontal scroll.
+  eSmartize,
+};
+
 /**
- * When a wheel event should be treated as specially, e.g., it's a vertical
- * wheel operation but user wants to scroll the target horizontally, this
- * class adjust the delta values automatically.  Then, restores the original
- * value when the instance is destroyed.
+ * When a *pure* vertical wheel event should be treated as if it was a
+ * horizontal scroll because the user wants to hozizontalize the wheel scroll,
+ * an instance of this class will adjust the delta values upon calling
+ * hozizontalize(). And if hozizontalized, it will automatically restore the
+ * original delta values when it is being destroyed.
  */
-class MOZ_STACK_CLASS AutoWheelDeltaAdjuster final
+class MOZ_STACK_CLASS WheelDeltaHorizontalizer final
 {
 public:
   /**
-   * @param aWheelEvent        A wheel event.  The delta values may be
-   *                           modified for default handler.
-   *                           Its mDeltaValuesAdjustedForDefaultHandler
-   *                           must not be true because if it's true,
-   *                           the event has already been adjusted the
-   *                           delta values for default handler.
+   * @param aWheelEvent        A wheel event whose delta values will be adjusted
+   *                           upon calling hozizontalize().
    */
-  explicit AutoWheelDeltaAdjuster(WidgetWheelEvent& aWheelEvent);
-  ~AutoWheelDeltaAdjuster();
+  explicit WheelDeltaHorizontalizer(WidgetWheelEvent& aWheelEvent)
+    : mWheelEvent(aWheelEvent)
+    , mHorizontalized(false)
+  {
+  }
+  // |mDeltaValuesHorizontalizedForDefaultHandler| of the associated wheel event
+  // must not be true before calling this function, because if it's true, the
+  // wheel event must have already been horizontalized. It's logically erroneous
+  // to horizontalize a wheel event a second time.
+  void horizontalize();
+  ~WheelDeltaHorizontalizer();
 
 private:
   WidgetWheelEvent& mWheelEvent;
   double mOldDeltaX;
   double mOldDeltaZ;
   double mOldOverflowDeltaX;
   int32_t mOldLineOrPageDeltaX;
-  bool mTreatedVerticalWheelAsHorizontalScroll;
+  bool mHorizontalized;
+};
+
+/**
+ * For a smart wheel scroll, there's some situations where we should adjust a
+ * wheel event's delta values and their multiplier values. This class converts a
+ * regular delta and their multiplier values for smart scrolling. A smart wheel
+ * scroll lets the user scroll a frame with only one scrollbar, using either a
+ * vertical or a horzizontal wheel.
+ */
+class MOZ_STACK_CLASS WheelDeltaSmartizer
+{
+protected:
+  /**
+   * @param aDeltaX            DeltaX for a wheel event whose delta values will
+   *                           be adjusted upon calling smartize() if
+   *                           mShouldBeSmartized is true.
+   * @param aDeltaY            DeltaZ for a wheel event, like DeltaX.
+   * @param aDeltaMultiplierX  The multiplier of DeltaX for a wheel event whose
+   *                           delta multiplier values will be adjusted upon
+   *                           calling smartize() if ShouldBeSmartized() returns
+   *                           true.
+   * @param aDeltaMultiplierY  The multiplier of DeltaY for a wheel event, like
+   *                           aDeltaMultiplierX.
+   */
+  explicit WheelDeltaSmartizer(double& aDeltaX,
+                               double& aDeltaY,
+                               double& aDeltaMultiplierX,
+                               double& aDeltaMultiplierY)
+    : mDeltaX(aDeltaX)
+    , mDeltaY(aDeltaY)
+    , mDeltaMultiplierX(aDeltaMultiplierX)
+    , mDeltaMultiplierY(aDeltaMultiplierY)
+    , mCheckedIfShouldBeSmartized(false)
+  {
+  }
+
+public:
+  /**
+   * Gets whether the values of the delta and its multiplier should be adjusted
+   * for smart scrolling.
+   *
+   * @return Returns true if the values of the delta and its multiplier should
+   *         be adjusted for smart scrolling. Note that after calling
+   *         smartize(), this function will return false, this ensures a wheel
+   *         event won't be adjusted for smart scrolling a second time.
+   */
+  bool ShouldBeSmartized();
+  /**
+   * Adjusts the values of the scroll event's delta and multiplier for smart
+   * scrolling when ShouldBeSmartized() is true. If you call it when
+   * ShouldBeSmartized() returns false, it simply will do nothing.
+   */
+  void smartize();
+
+private:
+  virtual bool CanScrollAlongXAxis() const = 0;
+  virtual bool CanScrollAlongYAxis() const = 0;
+  virtual bool CanScrollUpwards() const = 0;
+  virtual bool CanScrollDownwards() const = 0;
+  virtual bool CanScrollLeftwards() const = 0;
+  virtual bool CanScrollRightwards() const = 0;
+  /*
+   * Gets whether the current frame's horizontal contents starts at rightside.
+   *
+   * @return 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.
+   */
+  virtual bool IsHorizontalContentRightToLeft() const = 0;
+
+  double& mDeltaX;
+  double& mDeltaY;
+  double& mDeltaMultiplierX;
+  double& mDeltaMultiplierY;
+  bool mCheckedIfShouldBeSmartized;
+  bool mIsHorizontalContentRightToLeft;
+  bool mShouldBeSmartized;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_WheelHandlingHelper_h_
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -481,16 +481,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/public/IAPZCTreeManager.cpp
+++ b/gfx/layers/apz/public/IAPZCTreeManager.cpp
@@ -110,31 +110,42 @@ IAPZCTreeManager::ReceiveInputEvent(
             ((wheelEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE &&
               gfxPrefs::WheelSmoothScrollEnabled()) ||
              (wheelEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE &&
               gfxPrefs::PageSmoothScrollEnabled())))
         {
           scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
         }
 
-        // AutoWheelDeltaAdjuster may adjust the delta values for default
-        // action hander.  The delta values will be restored automatically
-        // when its instance is destroyed.
-        AutoWheelDeltaAdjuster adjuster(wheelEvent);
+        Maybe<WheelDeltaAdjustmentStrategy> strategy =
+          EventStateManager::GetWheelDeltaAdjustmentStrategy(wheelEvent);
+        // Adjust the delta values of the wheel event if the current default
+        // action is to horizontalize a vertical wheel scroll. I.e., deltaY
+        // values are set to deltaX and deltaY and deltaZ values are set to 0.
+        // In case of a horizontalized scroll, the delta values will be restored
+        // and its overflow deltaX will become 0 when the
+        // WheelDeltaHorizontalizer instance is being destroyed.
+        WheelDeltaHorizontalizer horizontalizer(wheelEvent);
+        if (strategy == Some(WheelDeltaAdjustmentStrategy::eHorizontalize)) {
+          horizontalizer.horizontalize();
+        }
 
         // If the wheel event becomes no-op event, don't handle it as scroll.
         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 ==
+                                   Some(WheelDeltaAdjustmentStrategy::eSmartize)
+                                );
 
           // 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
@@ -23,16 +23,18 @@
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
 #include "InputData.h"                  // for MultiTouchInput, etc
 #include "InputBlockState.h"            // for InputBlockState, TouchBlockState
 #include "InputQueue.h"                 // for InputQueue
 #include "Overscroll.h"                 // for OverscrollAnimation
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "Units.h"                      // for CSSRect, CSSPoint, etc
 #include "UnitTransforms.h"             // for TransformTo
+// for APZWheelDeltaSmartizer, WheelDeltaAndMultiplier
+#include "WheelDeltaSmartizer.h"
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxTypes.h"                   // for gfxFloat
 #include "LayersLogging.h"              // for print_stderr
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/BasicEvents.h"        // for Modifiers, MODIFIER_*
 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
@@ -1695,53 +1697,68 @@ static bool
 AllowsScrollingMoreThanOnePage(double aMultiplier)
 {
   const int32_t kMinAllowPageScroll =
     EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
   return Abs(aMultiplier) >= kMinAllowPageScroll;
 }
 
 ParentLayerPoint
-AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent) const
+AsyncPanZoomController::GetScrollWheelDeltaInDevicePixels(
+                          const ScrollWheelInput& aEvent) const
+{
+  WheelDeltaAndMultiplier wheelDeltaAndMultiplier(
+                            aEvent.mDeltaX,
+                            aEvent.mDeltaY,
+                            aEvent.mUserDeltaMultiplierX,
+                            aEvent.mUserDeltaMultiplierY);
+  return GetScrollWheelDeltaInDevicePixels(aEvent, wheelDeltaAndMultiplier);
+}
+
+ParentLayerPoint
+AsyncPanZoomController::GetScrollWheelDeltaInDevicePixels(
+  const ScrollWheelInput& aEvent,
+  const WheelDeltaAndMultiplier& aWheelDeltaAndMultiplier) const
 {
   ParentLayerSize scrollAmount;
   ParentLayerSize pageScrollSize;
-
   {
     // Grab the lock to access the frame metrics.
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount();
     LayoutDeviceIntSize pageScrollSizeLD = mScrollMetadata.GetPageScrollAmount();
     scrollAmount = scrollAmountLD /
       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 = aWheelDeltaAndMultiplier.mDeltaX * scrollAmount.width;
+      delta.y = aWheelDeltaAndMultiplier.mDeltaY * scrollAmount.height;
       break;
     }
     case ScrollWheelInput::SCROLLDELTA_PAGE: {
-      delta.x = aEvent.mDeltaX * pageScrollSize.width;
-      delta.y = aEvent.mDeltaY * pageScrollSize.height;
+      delta.x = aWheelDeltaAndMultiplier.mDeltaX * pageScrollSize.width;
+      delta.y = aWheelDeltaAndMultiplier.mDeltaY * pageScrollSize.height;
       break;
     }
     case ScrollWheelInput::SCROLLDELTA_PIXEL: {
-      delta = ToParentLayerCoordinates(ScreenPoint(aEvent.mDeltaX, aEvent.mDeltaY), aEvent.mOrigin);
+      ScreenPoint pt(aWheelDeltaAndMultiplier.mDeltaX,
+                     aWheelDeltaAndMultiplier.mDeltaY);
+      delta = ToParentLayerCoordinates(pt, aEvent.mOrigin);
       break;
     }
   }
 
   // Apply user-set multipliers.
-  delta.x *= aEvent.mUserDeltaMultiplierX;
-  delta.y *= aEvent.mUserDeltaMultiplierY;
+  delta.x *= aWheelDeltaAndMultiplier.mDeltaMultiplierX;
+  delta.y *= aWheelDeltaAndMultiplier.mDeltaMultiplierY;
 
   // 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 &&
@@ -1933,34 +1950,54 @@ AsyncPanZoomController::GetKeyboardDesti
   return scrollDestination;
 }
 
 ParentLayerPoint
 AsyncPanZoomController::GetDeltaForEvent(const InputData& aEvent) const
 {
   ParentLayerPoint delta;
   if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
-    delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
+    delta = GetScrollWheelDeltaInDevicePixels(aEvent.AsScrollWheelInput());
   } else if (aEvent.mInputType == PANGESTURE_INPUT) {
     const PanGestureInput& panInput = aEvent.AsPanGestureInput();
     delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), panInput.mPanStartPoint);
   }
   return delta;
 }
 
 // Return whether or not the underlying layer can be scrolled on either axis.
 bool
 AsyncPanZoomController::CanScroll(const InputData& aEvent) const
 {
   ParentLayerPoint delta = GetDeltaForEvent(aEvent);
   if (!delta.x && !delta.y) {
     return false;
   }
-
-  return CanScrollWithWheel(delta);
+  if (SCROLLWHEEL_INPUT == aEvent.mInputType) {
+    const ScrollWheelInput& scrollWheelInput = aEvent.AsScrollWheelInput();
+    WheelDeltaAndMultiplier smartDeltaAndMultiplier(
+                              scrollWheelInput.mDeltaX,
+                              scrollWheelInput.mDeltaY,
+                              scrollWheelInput.mUserDeltaMultiplierX,
+                              scrollWheelInput.mUserDeltaMultiplierY);
+    bool isRTL;
+    {
+      RecursiveMutexAutoLock lock(mRecursiveMutex);
+      isRTL = mFrameMetrics.IsHorizontalContentRightToLeft();
+    }
+    APZWheelDeltaSmartizer smartizer(smartDeltaAndMultiplier, mX, mY, isRTL);
+    if (smartizer.ShouldBeSmartized()) {
+      // If delta values are needed to be smartized for a smart wheel scroll,
+      // then it is impossible to be unscrollable, so there is no need to
+      // continue checking.
+      return true;
+    }
+    return CanScrollWithWheel(delta);
+  }
+  return CanScroll(delta);
 }
 
 ScrollDirections
 AsyncPanZoomController::GetAllowedHandoffDirections() const
 {
   ScrollDirections result;
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   if (mX.OverscrollBehaviorAllowsHandoff()) {
@@ -1968,16 +2005,22 @@ AsyncPanZoomController::GetAllowedHandof
   }
   if (mY.OverscrollBehaviorAllowsHandoff()) {
     result += ScrollDirection::eVertical;
   }
   return result;
 }
 
 bool
+AsyncPanZoomController::CanScroll(const ParentLayerPoint& aDelta) const
+{
+  return mX.CanScroll(aDelta.x) || mY.CanScroll(aDelta.y);
+}
+
+bool
 AsyncPanZoomController::CanScrollWithWheel(const ParentLayerPoint& aDelta) const
 {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
   // For more details about the concept of a disregarded direction, refer to the
   // code in struct ScrollMetadata which defines mDisregardedDirection.
   Maybe<ScrollDirection> disregardedDirection =
      mScrollMetadata.GetDisregardedDirection();
@@ -2038,61 +2081,105 @@ 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)) {
-    // We can't scroll this apz anymore, so we simply drop the event.
+  // Get the scroll wheel's delta values in device pixels. But before getting
+  // the values, we need to check if it is a smart scroll and if it should be
+  // smartized, if both answers are yes, let's adjust X and Y values for a smart
+  // wheel scroll first, and then get the values in device pixels based on the
+  // smartized values.
+  bool isSmartized = false;
+  ParentLayerPoint deltaInDevicePixels;
+  if (aEvent.mIsSmart) {
+    WheelDeltaAndMultiplier smartDeltaAndMultiplier(
+                              aEvent.mDeltaX,
+                              aEvent.mDeltaY,
+                              aEvent.mUserDeltaMultiplierX,
+                              aEvent.mUserDeltaMultiplierY);
+    bool isRTL;
+    {
+      RecursiveMutexAutoLock lock(mRecursiveMutex);
+      isRTL = mFrameMetrics.IsHorizontalContentRightToLeft();
+    }
+    APZWheelDeltaSmartizer smartizer(smartDeltaAndMultiplier, mX, mY, isRTL);
+    if (smartizer.ShouldBeSmartized()) {
+      smartizer.smartize();
+      // If the original delta values are smartized, we pass them to replace the
+      // original delta values in |aEvent| so that the delta values in device
+      // pixels are caculated based on the smartized values, not the original
+      // ones.
+      deltaInDevicePixels = GetScrollWheelDeltaInDevicePixels(
+                              aEvent, smartDeltaAndMultiplier);
+      isSmartized = true;
+    }
+  }
+  if (!isSmartized) {
+    // If the original delta values are not smartized, just pass the |aEvent|,
+    // Caculate the delta values in device pixels based on the original delta
+    // values from |aEvent|.
+    deltaInDevicePixels = GetScrollWheelDeltaInDevicePixels(aEvent);
+  }
+  APZC_LOG("%p got a scroll-wheel with delta in device pixels: %s\n", this,
+           Stringify(deltaInDevicePixels).c_str());
+
+  if (isSmartized) {
+    MOZ_ASSERT(deltaInDevicePixels.x || deltaInDevicePixels.y,
+               "Smartized delta values can never be all-zero.");
+    APZC_LOG("%p got a scroll-wheel with smartized delta values\n", this);
+  } else if ((deltaInDevicePixels.x || deltaInDevicePixels.y) &&
+             !CanScrollWithWheel(deltaInDevicePixels)) {
+    // If we can't scroll this apz anymore, simply drop the event.
     if (mInputQueue->GetActiveWheelTransaction() &&
         gfxPrefs::MouseScrollTestingEnabled()) {
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
         controller->NotifyMozMouseScrollEvent(
           mFrameMetrics.GetScrollId(),
           NS_LITERAL_STRING("MozMouseScrollFailed"));
       }
     }
     return nsEventStatus_eConsumeNoDefault;
   }
 
   MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock());
-  AdjustDeltaForAllowedScrollDirections(delta,
+  AdjustDeltaForAllowedScrollDirections(deltaInDevicePixels,
       mInputQueue->GetCurrentWheelBlock()->GetAllowedScrollDirections());
 
-  if (delta.x == 0 && delta.y == 0) {
+  if (deltaInDevicePixels.x == 0 && deltaInDevicePixels.y == 0) {
     // Avoid spurious state changes and unnecessary work
     return nsEventStatus_eIgnore;
   }
 
   switch (aEvent.mScrollMode) {
     case ScrollWheelInput::SCROLLMODE_INSTANT: {
 
       // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
       // next snap point. Check for this, and adjust the delta to take into
       // account the snap point.
       CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
-      MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition);
+      MaybeAdjustDeltaForScrollSnapping(aEvent, deltaInDevicePixels,
+                                        startPosition);
 
       ScreenPoint distance = ToScreenCoordinates(
-        ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
+        ParentLayerPoint(fabs(deltaInDevicePixels.x),
+                         fabs(deltaInDevicePixels.y)),
+                         aEvent.mLocalOrigin);
 
       CancelAnimation();
 
       OverscrollHandoffState handoffState(
           *mInputQueue->GetCurrentWheelBlock()->GetOverscrollHandoffChain(),
           distance,
           ScrollSource::Wheel);
       ParentLayerPoint startPoint = aEvent.mLocalOrigin;
-      ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta;
+      ParentLayerPoint endPoint = aEvent.mLocalOrigin - deltaInDevicePixels;
       CallDispatchScroll(startPoint, endPoint, handoffState);
 
       SetState(NOTHING);
 
       // The calls above handle their own locking; moreover,
       // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock.
       RecursiveMutexAutoLock lock(mRecursiveMutex);
       RequestContentRepaint();
@@ -2113,17 +2200,18 @@ nsEventStatus AsyncPanZoomController::On
       // scroll position. Take this into account when finding a snap point.
       if (mState == WHEEL_SCROLL) {
         startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
       } else if (mState == SMOOTH_SCROLL) {
         startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
       } else if (mState == KEYBOARD_SCROLL) {
         startPosition = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
       }
-      if (MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition)) {
+      if (MaybeAdjustDeltaForScrollSnapping(aEvent, deltaInDevicePixels,
+                                            startPosition)) {
         // If we're scroll snapping, use a smooth scroll animation to get
         // the desired physics. Note that SmoothScrollTo() will re-use an
         // existing smooth scroll animation if there is one.
         APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str());
         SmoothScrollTo(startPosition);
         break;
       }
 
@@ -2133,17 +2221,17 @@ nsEventStatus AsyncPanZoomController::On
         SetState(WHEEL_SCROLL);
 
         nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
         StartAnimation(new WheelScrollAnimation(
           *this, initialPosition, aEvent.mDeltaType));
       }
 
       nsPoint deltaInAppUnits =
-        CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
+        CSSPoint::ToAppUnits(deltaInDevicePixels / mFrameMetrics.GetZoom());
 
       // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
       // then to appunits/second.
       nsPoint velocity =
         CSSPoint::ToAppUnits(
           ParentLayerPoint(mX.GetVelocity() * 1000.0f,
                            mY.GetVelocity() * 1000.0f) /
           mFrameMetrics.GetZoom());
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -58,16 +58,17 @@ class OverscrollHandoffChain;
 struct OverscrollHandoffState;
 class StateChangeNotificationBlocker;
 class CheckerboardEvent;
 class OverscrollEffectBase;
 class WidgetOverscrollEffect;
 class GenericOverscrollEffect;
 class AndroidSpecificState;
 struct KeyboardScrollAction;
+struct WheelDeltaAndMultiplier;
 
 // Base class for grouping platform-specific APZC state variables.
 class PlatformSpecificStateBase {
 public:
   virtual ~PlatformSpecificStateBase() {}
   virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; }
 };
 
@@ -467,16 +468,20 @@ public:
   bool CanScroll(const InputData& aEvent) const;
 
   // Return the directions in which this APZC allows handoff (as governed by
   // overscroll-behavior).
   ScrollDirections GetAllowedHandoffDirections() const;
 
   // Return whether or not a scroll delta will be able to scroll in either
   // direction.
+  bool CanScroll(const ParentLayerPoint& aDelta) const;
+
+  // Return whether or not a scroll delta will be able to scroll in either
+  // direction withe wheel.
   bool CanScrollWithWheel(const ParentLayerPoint& aDelta) const;
 
   // Return whether or not there is room to scroll this APZC
   // in the given direction.
   bool CanScroll(ScrollDirection aDirection) const;
 
   /**
    * Convert a point on the scrollbar from this APZC's ParentLayer coordinates
@@ -551,17 +556,34 @@ protected:
   nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent);
   nsEventStatus HandleEndOfPan();
 
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
-  ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const;
+  /**
+   * Gets the scroll wheel delta's values in device pixels from the original
+   * delta's value of a wheel input.
+   */
+  ParentLayerPoint GetScrollWheelDeltaInDevicePixels(
+    const ScrollWheelInput& aEvent) const;
+
+  /**
+   * This function is like GetScrollWheelDeltaInDevicePixels(aEvent).
+   * The difference is |aWheelDeltaAndMultiplier| provide values as alternatives
+   * to the original wheel input's values for caculating the delta's values in
+   * device pixels, so |aEvent|'s delta values are ignored, we only use some
+   * other member variables and functions of |aEvent|.
+   */
+  ParentLayerPoint
+  GetScrollWheelDeltaInDevicePixels(
+    const ScrollWheelInput& aEvent,
+    const WheelDeltaAndMultiplier& aWheelDeltaAndMultiplier) const;
 
   /**
    * Helper methods for handling keyboard events.
    */
   nsEventStatus OnKeyboard(const KeyboardInput& aEvent);
 
   CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const;
 
--- 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 Side::eLeft:
+      return CanScroll(-COORDINATE_EPSILON * 2);
+    case Side::eRight:
+      return CanScroll(COORDINATE_EPSILON * 2);
+    default:
+      MOZ_ASSERT_UNREACHABLE("aSide is out of range of enum");
+      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 Side::eTop:
+      return CanScroll(-COORDINATE_EPSILON * 2);
+    case Side::eBottom:
+      return CanScroll(COORDINATE_EPSILON * 2);
+    default:
+      MOZ_ASSERT_UNREACHABLE("aSide is out of range of enum");
+      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
@@ -308,29 +308,41 @@ 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;
+  enum class Side
+  {
+    eLeft,
+    eRight,
+  };
+  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;
+  enum class Side
+  {
+    eTop,
+    eBottom,
+  };
+  bool CanScrollTo(Side aSide) const;
 private:
   virtual OverscrollBehavior GetOverscrollBehavior() const override;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/WheelDeltaSmartizer.h
@@ -0,0 +1,120 @@
+/* -*- 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_WheelDeltaSmartizer_h__
+#define __mozilla_layers_WheelDeltaSmartizer_h__
+
+#include "Axis.h"                        // for AxisX, AxisY
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaSmartizer
+
+namespace mozilla {
+namespace layers {
+
+struct MOZ_STACK_CLASS WheelDeltaAndMultiplier final
+{
+  explicit
+  WheelDeltaAndMultiplier(double aDeltaX,
+                          double aDeltaY,
+                          double aDeltaMultiplierX,
+                          double aDeltaMultiplierY)
+    : mDeltaX(aDeltaX)
+    , mDeltaY(aDeltaY)
+    , mDeltaMultiplierX(aDeltaMultiplierX)
+    , mDeltaMultiplierY(aDeltaMultiplierY)
+  {
+  }
+
+  double mDeltaX;
+  double mDeltaY;
+  double mDeltaMultiplierX;
+  double mDeltaMultiplierY;
+};
+
+/**
+ * About WheelDeltaSmartizer:
+ * For a smart wheel scroll, there's some situations where we should adjust a
+ * wheel event's delta values and their multiplier values. WheelDeltaSmartizer
+ * converts a regular delta and their multiplier values for smart scrolling. A
+ * smart wheel scroll lets the user scroll a frame with only one scrollbar,
+ * using either a vertical or a horzizontal wheel.
+ *
+ * This is the APZ implementation for WheelDeltaSmartizer.
+ */
+class MOZ_STACK_CLASS APZWheelDeltaSmartizer final: public WheelDeltaSmartizer
+{
+public:
+  /**
+   * @param aWheelDeltaAndMultiplier
+   *                           The delta and its multiplier for a wheel event
+   *                           whose delta values will be adjusted upon calling
+   *                           smartize() if ShouldBeSmartized() returns true.
+   * @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
+   *                           Indicats whether the current frame's horizontal
+   *                           contents starts at rightside. For more
+   *                           information, refer to the comment of
+   *                           IsHorizontalContentRightToLeft() for the base
+   *                           class WheelDeltaSmartizer.
+   */
+  explicit
+  APZWheelDeltaSmartizer(WheelDeltaAndMultiplier& aWheelDeltaAndMultiplier,
+                         const AxisX& aAxisX,
+                         const AxisY& aAxisY,
+                         bool aIsHorizontalContentRightToLeft)
+    : WheelDeltaSmartizer(aWheelDeltaAndMultiplier.mDeltaX,
+                          aWheelDeltaAndMultiplier.mDeltaY,
+                          aWheelDeltaAndMultiplier.mDeltaMultiplierX,
+                          aWheelDeltaAndMultiplier.mDeltaMultiplierY)
+    , 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(AxisY::Side::eTop);
+  }
+  virtual bool CanScrollDownwards() const override
+  {
+    return mAxisY.CanScrollTo(AxisY::Side::eBottom);
+  }
+  virtual bool CanScrollLeftwards() const override
+  {
+    return mAxisX.CanScrollTo(AxisX::Side::eLeft);
+  }
+  virtual bool CanScrollRightwards() const override
+  {
+    return mAxisX.CanScrollTo(AxisX::Side::eRight);
+  }
+  virtual bool IsHorizontalContentRightToLeft() const override
+  {
+    return mIsHorizontalContentRightToLeft;
+  }
+
+  const AxisX& mAxisX;
+  const AxisY& mAxisY;
+  bool mIsHorizontalContentRightToLeft;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // __mozilla_layers_WheelDeltaSmartizer_h__
--- 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, false);
   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, false);
   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, false);
     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, false);
   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, false);
   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, false);
   uint64_t blockId;
   manager->ReceiveInputEvent(swi, nullptr, &blockId);
   manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true);
 }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2782,34 +2782,37 @@ pref("mousewheel.acceleration.factor", 1
 // settings, the override isn't executed.
 pref("mousewheel.system_scroll_override_on_root_content.vertical.factor", 200);
 pref("mousewheel.system_scroll_override_on_root_content.horizontal.factor", 200);
 
 // mousewheel.*.action can specify the action when you use mosue wheel.
 // When no modifier keys are pressed or two or more modifires are pressed,
 // .default is used.
 // 0: Nothing happens
-// 1: Scrolling contents
+// 1: Scrolling contents smartly, that is, you can scroll a target in the
+//    direction orthogonal to the wheel if there's no scrollbar in the wheel's
+//    direction but a scrollbar in the wheel's orthogonal direction
 // 2: Go back or go forward, in your history
 // 3: Zoom in or out.
 // 4: Treat vertical wheel as horizontal scroll
 //      This treats vertical wheel operation (i.e., deltaY) as horizontal
 //      scroll.  deltaX and deltaZ are always ignored.  So, only
 //      "delta_multiplier_y" pref affects the scroll speed.
+// 5: Scroll contents
 pref("mousewheel.default.action", 1);
 pref("mousewheel.with_alt.action", 2);
 pref("mousewheel.with_control.action", 3);
 pref("mousewheel.with_meta.action", 1);  // command key on Mac
 pref("mousewheel.with_shift.action", 4);
 pref("mousewheel.with_win.action", 1);
 
 // mousewheel.*.action.override_x will override the action
 // when the mouse wheel is rotated along the x direction.
 // -1: Don't override the action.
-// 0 to 3: Override the action with the specified value.
+// 0 to 3, or 5: Override the action with the specified value.
 // Note that 4 isn't available because it doesn't make sense to apply the
 // default action only for y direction to this pref.
 pref("mousewheel.default.action.override_x", -1);
 pref("mousewheel.with_alt.action.override_x", -1);
 pref("mousewheel.with_control.action.override_x", -1);
 pref("mousewheel.with_meta.action.override_x", -1);  // command key on Mac
 pref("mousewheel.with_shift.action.override_x", -1);
 pref("mousewheel.with_win.action.override_x", -1);
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -666,32 +666,34 @@ 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,
+                                   bool aIsSmart)
   : 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)
+  , mIsSmart(aIsSmart)
 {
 }
 
 ScrollWheelInput::ScrollWheelInput(const WidgetWheelEvent& aWheelEvent)
   : InputData(SCROLLWHEEL_INPUT, aWheelEvent.mTime, aWheelEvent.mTimeStamp,
               aWheelEvent.mModifiers)
   , mDeltaType(DeltaTypeForDeltaMode(aWheelEvent.mDeltaMode))
   , mScrollMode(SCROLLMODE_INSTANT)
@@ -702,16 +704,17 @@ ScrollWheelInput::ScrollWheelInput(const
   , mLineOrPageDeltaY(aWheelEvent.mLineOrPageDeltaY)
   , mScrollSeriesNumber(0)
   , mUserDeltaMultiplierX(1.0)
   , mUserDeltaMultiplierY(1.0)
   , mMayHaveMomentum(aWheelEvent.mMayHaveMomentum)
   , mIsMomentum(aWheelEvent.mIsMomentum)
   , mAllowToOverrideSystemScrollSpeed(
       aWheelEvent.mAllowToOverrideSystemScrollSpeed)
+  , mIsSmart(false)
 {
   mOrigin =
     ScreenPoint(ViewAs<ScreenPixel>(aWheelEvent.mRefPoint,
       PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
 }
 
 ScrollWheelInput::ScrollDeltaType
 ScrollWheelInput::DeltaTypeForDeltaMode(uint32_t aDeltaMode)
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -528,17 +528,17 @@ 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, bool aIsSmart);
   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);
@@ -578,16 +578,18 @@ public:
 
   // User-set delta multipliers.
   double mUserDeltaMultiplierX;
   double mUserDeltaMultiplierY;
 
   bool mMayHaveMomentum;
   bool mIsMomentum;
   bool mAllowToOverrideSystemScrollSpeed;
+
+  bool mIsSmart;
 };
 
 class KeyboardInput : public InputData
 {
 public:
   typedef mozilla::layers::KeyboardScrollAction KeyboardScrollAction;
 
   enum KeyboardEventType
--- a/widget/MouseEvents.h
+++ b/widget/MouseEvents.h
@@ -508,17 +508,17 @@ private:
     , mLineOrPageDeltaY(0)
     , mScrollType(SCROLL_DEFAULT)
     , mCustomizedByUserPrefs(false)
     , mIsMomentum(false)
     , mIsNoLineOrPageDelta(false)
     , mViewPortIsOverscrolled(false)
     , mCanTriggerSwipe(false)
     , mAllowToOverrideSystemScrollSpeed(false)
-    , mDeltaValuesAdjustedForDefaultHandler(false)
+    , mDeltaValuesHorizontalizedForDefaultHandler(false)
   {
   }
 
 public:
   virtual WidgetWheelEvent* AsWheelEvent() override { return this; }
 
   WidgetWheelEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
     : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, eWheelEventClass)
@@ -533,17 +533,17 @@ public:
     , mScrollType(SCROLL_DEFAULT)
     , mCustomizedByUserPrefs(false)
     , mMayHaveMomentum(false)
     , mIsMomentum(false)
     , mIsNoLineOrPageDelta(false)
     , mViewPortIsOverscrolled(false)
     , mCanTriggerSwipe(false)
     , mAllowToOverrideSystemScrollSpeed(true)
-    , mDeltaValuesAdjustedForDefaultHandler(false)
+    , mDeltaValuesHorizontalizedForDefaultHandler(false)
   {
   }
 
   virtual WidgetEvent* Duplicate() const override
   {
     MOZ_ASSERT(mClass == eWheelEventClass,
                "Duplicate() must be overridden by sub class");
     // Not copying widget, it is a weak reference.
@@ -651,19 +651,19 @@ public:
   // viewport.
   bool mCanTriggerSwipe;
 
   // If mAllowToOverrideSystemScrollSpeed is true, the scroll speed may be
   // overridden.  Otherwise, the scroll speed won't be overridden even if
   // it's enabled by the pref.
   bool mAllowToOverrideSystemScrollSpeed;
 
-  // While default handler handles a wheel event specially (e.g., treating
-  // mDeltaY as horizontal scroll), this is set to true.
-  bool mDeltaValuesAdjustedForDefaultHandler;
+  // While default handler handles a *pure* vertical wheel event by treating
+  // mDeltaY as horizontal scroll, this is set to true.
+  bool mDeltaValuesHorizontalizedForDefaultHandler;
 
   void AssignWheelEventData(const WidgetWheelEvent& aEvent, bool aCopyTargets)
   {
     AssignMouseEventBaseData(aEvent, aCopyTargets);
 
     mDeltaX = aEvent.mDeltaX;
     mDeltaY = aEvent.mDeltaY;
     mDeltaZ = aEvent.mDeltaZ;
@@ -676,18 +676,18 @@ public:
     mLineOrPageDeltaY = aEvent.mLineOrPageDeltaY;
     mScrollType = aEvent.mScrollType;
     mOverflowDeltaX = aEvent.mOverflowDeltaX;
     mOverflowDeltaY = aEvent.mOverflowDeltaY;
     mViewPortIsOverscrolled = aEvent.mViewPortIsOverscrolled;
     mCanTriggerSwipe = aEvent.mCanTriggerSwipe;
     mAllowToOverrideSystemScrollSpeed =
       aEvent.mAllowToOverrideSystemScrollSpeed;
-    mDeltaValuesAdjustedForDefaultHandler =
-      aEvent.mDeltaValuesAdjustedForDefaultHandler;
+    mDeltaValuesHorizontalizedForDefaultHandler =
+      aEvent.mDeltaValuesHorizontalizedForDefaultHandler;
   }
 
   // System scroll speed settings may be too slow at using Gecko.  In such
   // case, we should override the scroll speed computed with system settings.
   // Following methods return preferred delta values which are multiplied by
   // factors specified by prefs.  If system scroll speed shouldn't be
   // overridden (e.g., this feature is disabled by pref), they return raw
   // delta values.
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -478,16 +478,22 @@ public:
             aVScroll = -aVScroll;
         }
 
         ScrollWheelInput input(aTime, GetEventTimeStamp(aTime), GetModifiers(aMetaState),
                                ScrollWheelInput::SCROLLMODE_SMOOTH,
                                ScrollWheelInput::SCROLLDELTA_PIXEL,
                                origin,
                                aHScroll, aVScroll,
+                               false,
+                               // XXX Do we need to support smart scroll for
+                               // Android widgets with a wheel device?
+                               // Currently, I just keep it unimplemented. If we
+                               // need to implement it, what's the extra work we
+                               // should do?
                                false);
 
         ScrollableLayerGuid guid;
         uint64_t blockId;
         nsEventStatus status = controller->ReceiveInputEvent(input, &guid, &blockId);
 
         if (status == nsEventStatus_eConsumeNoDefault) {
             return true;
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -5029,32 +5029,44 @@ 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,
+                                // XXX Do we need to support smart scrolling for
+                                // iOS widgets with a wheel device? Currently, I
+                                // just keep it unimplemented. If we need to
+                                // implement it, what's the extra work we should
+                                // do?
                                 false);
     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,
+                                // XXX Do we need to support smart scrolling for
+                                // iOS widgets with a wheel device? Currently, I
+                                // just keep it unimplemented. If we need to
+                                // implement it, what's the extra work we should
+                                // do?
                                 false);
     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
@@ -182,17 +182,17 @@ struct ParamTraits<mozilla::WidgetWheelE
     WriteParam(aMsg, aParam.mLineOrPageDeltaX);
     WriteParam(aMsg, aParam.mLineOrPageDeltaY);
     WriteParam(aMsg, static_cast<uint8_t>(aParam.mScrollType));
     WriteParam(aMsg, aParam.mOverflowDeltaX);
     WriteParam(aMsg, aParam.mOverflowDeltaY);
     WriteParam(aMsg, aParam.mViewPortIsOverscrolled);
     WriteParam(aMsg, aParam.mCanTriggerSwipe);
     WriteParam(aMsg, aParam.mAllowToOverrideSystemScrollSpeed);
-    WriteParam(aMsg, aParam.mDeltaValuesAdjustedForDefaultHandler);
+    WriteParam(aMsg, aParam.mDeltaValuesHorizontalizedForDefaultHandler);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     uint8_t scrollType = 0;
     bool rv =
       ReadParam(aMsg, aIter,
                 static_cast<mozilla::WidgetMouseEventBase*>(aResult)) &&
@@ -207,17 +207,18 @@ struct ParamTraits<mozilla::WidgetWheelE
       ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
       ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
       ReadParam(aMsg, aIter, &scrollType) &&
       ReadParam(aMsg, aIter, &aResult->mOverflowDeltaX) &&
       ReadParam(aMsg, aIter, &aResult->mOverflowDeltaY) &&
       ReadParam(aMsg, aIter, &aResult->mViewPortIsOverscrolled) &&
       ReadParam(aMsg, aIter, &aResult->mCanTriggerSwipe) &&
       ReadParam(aMsg, aIter, &aResult->mAllowToOverrideSystemScrollSpeed) &&
-      ReadParam(aMsg, aIter, &aResult->mDeltaValuesAdjustedForDefaultHandler);
+      ReadParam(aMsg, aIter,
+                &aResult->mDeltaValuesHorizontalizedForDefaultHandler);
     aResult->mScrollType =
       static_cast<mozilla::WidgetWheelEvent::ScrollType>(scrollType);
     return rv;
   }
 };
 
 template<>
 struct ParamTraits<mozilla::WidgetPointerHelper>
@@ -1333,16 +1334,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.mIsSmart);
   }
 
   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,16 +1355,17 @@ struct ParamTraits<mozilla::ScrollWheelI
            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->mIsSmart);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::KeyboardInput::KeyboardEventType>
   : public ContiguousEnumSerializer<
              mozilla::KeyboardInput::KeyboardEventType,
              mozilla::KeyboardInput::KeyboardEventType::KEY_DOWN,