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
MozReview-Commit-ID: 9hCJ3wpkOah
--- a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -156,22 +156,56 @@ function synthesizeNativeWheelAndWaitFor
// aX and aY are relative to the top-left of |aElement|'s containing window.
function synthesizeNativeMouseMove(aElement, aX, aY) {
var pt = coordinatesRelativeToWindow(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 = coordinatesRelativeToWindow(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);
}
@@ -222,19 +256,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