Bug 801101 Allow to scoll over one page scroll with mouse wheel if delta mulitipliers are large enough r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 31 Oct 2012 08:22:23 +0900
changeset 111968 7546a81c1c389b20cd572b453adf9b9b01029fc9
parent 111967 f38bee8c58b9df639ed829ad6b4fdb6444be66da
child 111969 27978aa4d430a11e6c5255cf7c3e44ab28d59650
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerssmaug
bugs801101
milestone19.0a1
Bug 801101 Allow to scoll over one page scroll with mouse wheel if delta mulitipliers are large enough r=smaug
content/events/src/nsEventStateManager.cpp
content/events/src/nsEventStateManager.h
content/events/test/window_wheel_default_action.html
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -2881,27 +2881,30 @@ nsEventStateManager::DoScrollText(nsIScr
     case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
       origin = nsGkAtoms::pixels;
       break;
     default:
       MOZ_NOT_REACHED("Invalid deltaMode value comes");
       return;
   }
 
-  // We shouldn't scroll more one page at once.
+  // We shouldn't scroll more one page at once except when over one page scroll
+  // is allowed for the event.
   nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
   nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
                              pc->AppUnitsToDevPixels(pageSize.height));
-  if (NS_ABS(actualDevPixelScrollAmount.x) > devPixelPageSize.width) {
+  if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
+      NS_ABS(actualDevPixelScrollAmount.x) > devPixelPageSize.width) {
     actualDevPixelScrollAmount.x =
       (actualDevPixelScrollAmount.x >= 0) ? devPixelPageSize.width :
                                             -devPixelPageSize.width;
   }
 
-  if (NS_ABS(actualDevPixelScrollAmount.y) > devPixelPageSize.height) {
+  if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
+      NS_ABS(actualDevPixelScrollAmount.y) > devPixelPageSize.height) {
     actualDevPixelScrollAmount.y =
       (actualDevPixelScrollAmount.y >= 0) ? devPixelPageSize.height :
                                             -devPixelPageSize.height;
   }
 
   bool isDeltaModePixel =
     (aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL);
 
@@ -5538,8 +5541,28 @@ nsEventStateManager::WheelPrefs::NeedToC
                                    widget::WheelEvent* aEvent)
 {
   Index index = GetIndexFor(aEvent);
   Init(index);
 
   return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
          (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
 }
+
+bool
+nsEventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
+                                   widget::WheelEvent* aEvent)
+{
+  Index index = GetIndexFor(aEvent);
+  Init(index);
+  return NS_ABS(mMultiplierX[index]) >=
+           MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
+}
+
+bool
+nsEventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
+                                   widget::WheelEvent* aEvent)
+{
+  Index index = GetIndexFor(aEvent);
+  Init(index);
+  return NS_ABS(mMultiplierY[index]) >=
+           MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
+}
--- a/content/events/src/nsEventStateManager.h
+++ b/content/events/src/nsEventStateManager.h
@@ -363,16 +363,23 @@ protected:
     Action ComputeActionFor(mozilla::widget::WheelEvent* aEvent);
 
     /**
      * NeedToComputeLineOrPageDelta() returns if the aEvent needs to be
      * computed the lineOrPageDelta values.
      */
     bool NeedToComputeLineOrPageDelta(mozilla::widget::WheelEvent* aEvent);
 
+    /**
+     * IsOverOnePageScrollAllowed*() checks whether wheel scroll amount should
+     * be rounded down to the page width/height (false) or not (true).
+     */
+    bool IsOverOnePageScrollAllowedX(mozilla::widget::WheelEvent* aEvent);
+    bool IsOverOnePageScrollAllowedY(mozilla::widget::WheelEvent* aEvent);
+
   private:
     WheelPrefs();
     ~WheelPrefs();
 
     static int OnPrefChanged(const char* aPrefName, void* aClosure);
 
     enum Index
     {
@@ -404,16 +411,25 @@ protected:
      *                      "mousewheel.default.".
      */
     void GetBasePrefName(Index aIndex, nsACString& aBasePrefName);
 
     void Init(Index aIndex);
 
     void Reset();
 
+    /**
+     * If the abosolute values of mMultiplierX and/or mMultiplierY are equals or
+     * larger than this value, the computed scroll amount isn't rounded down to
+     * the page width or height.
+     */
+    enum {
+      MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000
+    };
+
     bool mInit[COUNT_OF_MULTIPLIERS];
     double mMultiplierX[COUNT_OF_MULTIPLIERS];
     double mMultiplierY[COUNT_OF_MULTIPLIERS];
     double mMultiplierZ[COUNT_OF_MULTIPLIERS];
     Action mActions[COUNT_OF_MULTIPLIERS];
 
     static WheelPrefs* sInstance;
   };
--- a/content/events/test/window_wheel_default_action.html
+++ b/content/events/test/window_wheel_default_action.html
@@ -1130,17 +1130,17 @@ function doTestZoom(aSettings, aCallback
 
       synthesizeKey("0", { accelKey: true });
       hitEventLoop(doNextTest, 20);
     }, 20);
   }
   doNextTest();
 }
 
-function doTestZoomedScroll()
+function doTestZoomedScroll(aCallback)
 {
   function testZoomedPixelScroll()
   {
     // Reset zoom and store the scroll amount into the data.
     synthesizeKey("0", { accelKey: true });
     gScrollableElement.scrollTop = 1000;
     gScrollableElement.scrollLeft = 1000;
     // Ensure not to be in reflow.
@@ -1257,28 +1257,285 @@ function doTestZoomedScroll()
                  gScrollableElement.scrollLeft + ", scrolledX=" + scrolledX);
             ok(Math.abs(gScrollableElement.scrollTop - scrolledY) <= 1,
                "doTestZoomedScroll: zoomed vertical scroll amount by line wheel event is different from normal, scrollTop=" +
                  gScrollableElement.scrollTop + ", scrolledY=" + scrolledY);
 
             window.removeEventListener("MozMousePixelScroll", handler, true);
 
             synthesizeKey("0", { accelKey: true });
+
+            SimpleTest.executeSoon(aCallback);
           }, 20);
         }, 20);
       }, 20);
     }, 20);
   }
 
   // XXX It's too difficult to test page scroll because the page scroll amount
   //     is computed by complex logic.
 
   testZoomedPixelScroll();
 }
 
+function doTestWholeScroll(aCallback)
+{
+  SpecialPowers.setIntPref("mousewheel.default.delta_multiplier_x", 99999999);
+  SpecialPowers.setIntPref("mousewheel.default.delta_multiplier_y", 99999999);
+
+  const kTests = [
+    { description: "try whole-scroll to top (line)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to top when scrollTop is already top-most (line)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom (line)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 0.0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom when scrollTop is already bottom-most (line)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to left (line)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: -1.0, deltaY: 0.0,
+               lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to left when scrollLeft is already left-most (line)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: -1.0, deltaY: 0,
+               lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to right (line)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+    { description: "try whole-scroll to right when scrollLeft is already right-most (line)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_LINE,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+
+
+    { description: "try whole-scroll to top (pixel)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to top when scrollTop is already top-most (pixel)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom (pixel)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 0.0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom when scrollTop is already bottom-most (pixel)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to left (pixel)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: -1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to left when scrollLeft is already left-most (pixel)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: -1.0, deltaY: 0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to right (pixel)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+    { description: "try whole-scroll to right when scrollLeft is already right-most (pixel)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+
+
+    { description: "try whole-scroll to top (page)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to top when scrollTop is already top-most (page)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 0.0, deltaY: -1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 },
+      expectedScrollTop: 0,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom (page)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 0.0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to bottom when scrollTop is already bottom-most (page)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 0, deltaY: 1.0,
+               lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 },
+      expectedScrollTop: gScrollableElement.scrollTopMax,
+      expectedScrollLeft: 1000
+    },
+    { description: "try whole-scroll to left (page)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: -1.0, deltaY: 0.0,
+               lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to left when scrollLeft is already left-most (page)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: -1.0, deltaY: 0,
+               lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: 0
+    },
+    { description: "try whole-scroll to right (page)",
+      prepare: function () {
+        gScrollableElement.scrollTop = 1000;
+        gScrollableElement.scrollLeft = 1000;
+      },
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+    { description: "try whole-scroll to right when scrollLeft is already right-most (page)",
+      event: { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+               deltaX: 1.0, deltaY: 0.0,
+               lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 },
+      expectedScrollTop: 1000,
+      expectedScrollLeft: gScrollableElement.scrollLeftMax
+    },
+  ];
+
+  var index = 0;
+
+  function doIt()
+  {
+    const kTest = kTests[index];
+    if (kTest.prepare) {
+      kTest.prepare();
+    }
+    synthesizeWheel(gScrollableElement, 10, 10, kTest.event);
+    hitEventLoop(function () {
+      is(gScrollableElement.scrollTop, kTest.expectedScrollTop,
+         "doTestWholeScroll, " + kTest.description + ": unexpected scrollTop");
+      is(gScrollableElement.scrollLeft, kTest.expectedScrollLeft,
+         "doTestWholeScroll, " + kTest.description + ": unexpected scrollLeft");
+      if (++index == kTests.length) {
+        SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_x");
+        SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_y");
+        SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_z");
+        SimpleTest.executeSoon(aCallback);
+      } else {
+        doIt();
+      }
+    }, 20);
+  }
+  doIt();
+}
+
 function runTests()
 {
   SpecialPowers.setBoolPref("general.smoothScroll", false);
 
   SpecialPowers.setIntPref("mousewheel.default.action", 1);      // scroll
   SpecialPowers.setIntPref("mousewheel.with_shift.action", 2);   // history
   SpecialPowers.setIntPref("mousewheel.with_control.action", 3); // zoom
 
@@ -1308,18 +1565,21 @@ function runTests()
   var index = 0;
 
   function doTest() {
     setDeltaMultiplierSettings(kSettings[index]);
     doTestScroll(kSettings[index], function () {
         doTestZoom(kSettings[index], function() {
           if (++index == kSettings.length) {
             setDeltaMultiplierSettings(kSettings[0]);
-            doTestZoomedScroll();
-            finishTests();
+            doTestZoomedScroll(function() {
+              doTestWholeScroll(function() {
+                finishTests();
+              });
+            });
           } else {
             doTest();
           }
         });
       });
   }
   doTest();
 }