Bug 1276107 - Avoid the footgun where, on Windows, when synthesizing a mouse move event, if the mouse is already at the target location the event is never dispatched. r=kats, a=lizzard
authorBotond Ballo <botond@mozilla.com>
Fri, 03 Jun 2016 13:12:36 -0400
changeset 339639 e579b0b53a2081789aa29b7fa336377c79ae210c
parent 339638 2393f2456c18149405eb75b2de561a53ef145cd9
child 339640 804b78a09df71fb081761064750cbbc6fc47611c
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, lizzard
bugs1276107
milestone49.0a2
Bug 1276107 - Avoid the footgun where, on Windows, when synthesizing a mouse move event, if the mouse is already at the target location the event is never dispatched. r=kats, a=lizzard MozReview-Commit-ID: 9hCJ3wpkOah
gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
--- a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -170,22 +170,56 @@ function synthesizeNativeWheelAndWaitFor
 // aX and aY are relative to the top-left of |aElement|'s containing window.
 function synthesizeNativeMouseMove(aElement, aX, aY) {
   var pt = coordinatesRelativeToScreen(aX, aY, aElement);
   var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
   utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseMoveEventMsg(), 0, aElement);
   return true;
 }
 
+function equalPoints(aPt1, aPt2) {
+  if (!aPt1 || !aPt2) {
+    return false;
+  }
+  return aPt1.x == aPt2.x &&
+         aPt1.y == aPt2.y;
+}
+
 // Synthesizes a native mouse move event and invokes the callback once the
 // mouse move event is dispatched to |aElement|'s containing window. If the event
 // targets content in a subdocument, |aElement| should be inside the
 // subdocument. See synthesizeNativeMouseMove for details on the other
 // parameters.
 function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallback) {
+  // Initialize, if necessary, a place to a store state that will persist
+  // across calls to this function.
+  var func = synthesizeNativeMouseMoveAndWaitForMoveEvent;  // just so it's shorter
+  if (typeof func.persistentState == 'undefined') {
+    // If we're in a subtest, the test driver provides a variable where
+    // we can store state that persists across subtests. Otherwise, just
+    // create a variable that lives as long as this function.
+    if (typeof window.statePersistentAcrossSubtests == 'undefined') {
+      func.persistentState = {}
+    } else {
+      func.persistentState = window.statePersistentAcrossSubtests;
+    }
+  }
+
+  // On Windows, if the mouse is already at the target location, the mouse
+  // move event never gets dispatched. This is a significant potential footgun
+  // (if we try waiting for it when it'll never come); to avoid it, we store
+  // the last location we moved the mouse to, and just call the callback
+  // right away if the new location is the same.
+  var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+  if (equalPoints(func.persistentState.lastMouseMoveLocation, pt)) {
+    setTimeout(aCallback, 0);
+    return true;
+  }
+  func.persistentState.lastMouseMoveLocation = pt;
+
   var targetWindow = aElement.ownerDocument.defaultView;
   targetWindow.addEventListener("mousemove", function mousemoveWaiter(e) {
     targetWindow.removeEventListener("mousemove", mousemoveWaiter);
     setTimeout(aCallback, 0);
   });
   return synthesizeNativeMouseMove(aElement, aX, aY);
 }
 
@@ -245,19 +279,14 @@ function synthesizeNativeClick(aElement,
 // Move the mouse to (dx, dy) relative to |element|, and scroll the wheel
 // at that location.
 // Moving the mouse is necessary to avoid wheel events from two consecutive
 // scrollWheelOver() calls on different elements being incorreclty considered
 // as part of t he same wheel transaction.
 // We also wait for the mouse move event to be processed before sending the
 // wheel event, otherwise there is a chance they might get reordered, and
 // we have the transaction problem again.
-// XXX FOOTGUN: On Windows, if the mouse cursor is already at the target
-//              position, the mouse-move event doesn't get dispatched, and
-//              we end up hanging waiting for it. As a result, care must
-//              be taken that the mouse isn't already at the target position
-//              when using this function.
 function moveMouseAndScrollWheelOver(element, dx, dy, testDriver) {
   return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
     synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, testDriver);
   });
 }
 
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -143,16 +143,20 @@ function waitForApzFlushedRepaints(aCall
 // functions provided by SimpleTest are also mapped into the subtest's window.
 // For other things from the parent, the subtest can use window.opener.<whatever>
 // to access objects.
 function runSubtestsSeriallyInFreshWindows(aSubtests) {
   return new Promise(function(resolve, reject) {
     var testIndex = -1;
     var w = null;
 
+    // Some state that persists across subtests. This is made available to
+    // subtests to put things into / read things out of.
+    var statePersistentAcrossSubtests = {};
+
     function advanceSubtestExecution() {
       var test = aSubtests[testIndex];
       if (w) {
         if (typeof test.dp_suppression != 'undefined') {
           // We modified the suppression when starting the test, so now undo that.
           SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(!test.dp_suppression);
         }
         if (test.prefs) {
@@ -184,16 +188,17 @@ function runSubtestsSeriallyInFreshWindo
         // entire test which is more deterministic.
         SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(test.dp_suppression);
       }
 
       function spawnTest(aFile) {
         w = window.open('', "_blank");
         w.subtestDone = advanceSubtestExecution;
         w.SimpleTest = SimpleTest;
+        w.statePersistentAcrossSubtests = statePersistentAcrossSubtests;
         w.is = function(a, b, msg) { return is(a, b, aFile + " | " + msg); };
         w.ok = function(cond, name, diag) { return ok(cond, aFile + " | " + name, diag); };
         w.location = location.href.substring(0, location.href.lastIndexOf('/') + 1) + aFile;
         return w;
       }
 
       if (test.prefs) {
         // Got some prefs for this subtest, push them
--- a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
@@ -25,20 +25,17 @@ function* test(testDriver) {
   scrollPos = fpos.scrollTop;
   yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled");
   // wait for it to layerize fully and then try again
   yield waitForAllPaints(function() {
     flushApzRepaints(testDriver);
   });
   scrollPos = fpos.scrollTop;
-  // The mouse is already at the right position. If we call moveMouseAndScrollWheelOver it
-  // hangs on windows waiting for the mouse-move, so instead we just synthesize
-  // the wheel directly.
-  yield synthesizeNativeWheelAndWaitForScrollEvent(fpos, 50, 150, 0, -10, testDriver);
+  yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled after layerization");
 
   // same, but using the top-level window's position:sticky element
   scrollPos = window.scrollY;
   yield moveMouseAndScrollWheelOver(document.body, 50, 150, testDriver);
   ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
 
   // same, but using the top-level window's position:fixed element