Bug 716793. Dispatch synthetic mousemove off the refresh driver, not as fast as we can. r=roc
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 10 Jan 2012 00:23:29 -0500
changeset 84359 899a12aeff6cb8d35a7d2a0ddea9b0f6acb427f7
parent 84358 d83fa420aa7bc1d50eaf1fb3aefb6182fa1a11c6
child 84360 821b2b0daf21a4f3cb0dba5f67eb20b951c19707
push id4863
push userbzbarsky@mozilla.com
push dateThu, 12 Jan 2012 22:00:49 +0000
treeherdermozilla-inbound@d41fbe450000 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs716793
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 716793. Dispatch synthetic mousemove off the refresh driver, not as fast as we can. r=roc We use Flush_Display here because mousemoves flush out layout, so we want to do the synthetic one after we've done our normal layout flushing
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
layout/reftests/bugs/478811-2-ref.html
layout/reftests/bugs/478811-2.html
layout/reftests/bugs/478811-3-ref.html
layout/reftests/bugs/478811-3.html
layout/reftests/bugs/478811-4-ref.html
layout/reftests/bugs/478811-4.html
widget/tests/native_mouse_mac_window.xul
widget/tests/test_bug596600.xul
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5241,17 +5241,18 @@ void PresShell::SynthesizeMouseMove(bool
 
   if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
     return;
 
   if (!mSynthMouseMoveEvent.IsPending()) {
     nsRefPtr<nsSynthMouseMoveEvent> ev =
         new nsSynthMouseMoveEvent(this, aFromScroll);
 
-    if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
+    if (!GetPresContext()->RefreshDriver()->AddRefreshObserver(ev,
+                                                               Flush_Display)) {
       NS_WARNING("failed to dispatch nsSynthMouseMoveEvent");
       return;
     }
 
     mSynthMouseMoveEvent = ev;
   }
 }
 
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -847,27 +847,38 @@ private:
   // the mouse pointer may have changed without the mouse moving (eg scrolling,
   // change to the document contents).
   // It is set only on a presshell for a root document, this value represents
   // the last observed location of the mouse relative to that root document. It
   // is set to (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if the mouse isn't
   // over our window or there is no last observed mouse location for some
   // reason.
   nsPoint mMouseLocation;
-  class nsSynthMouseMoveEvent : public nsRunnable {
+  class nsSynthMouseMoveEvent : public nsARefreshObserver {
   public:
     nsSynthMouseMoveEvent(PresShell* aPresShell, bool aFromScroll)
       : mPresShell(aPresShell), mFromScroll(aFromScroll) {
       NS_ASSERTION(mPresShell, "null parameter");
     }
-    void Revoke() { mPresShell = nsnull; }
-    NS_IMETHOD Run() {
+    ~nsSynthMouseMoveEvent() {
+      Revoke();
+    }
+
+    NS_INLINE_DECL_REFCOUNTING(nsSynthMouseMoveEvent)
+    
+    void Revoke() {
+      if (mPresShell) {
+        mPresShell->GetPresContext()->RefreshDriver()->
+          RemoveRefreshObserver(this, Flush_Display);
+        mPresShell = nsnull;
+      }
+    }
+    virtual void WillRefresh(mozilla::TimeStamp aTime) {
       if (mPresShell)
         mPresShell->ProcessSynthMouseMoveEvent(mFromScroll);
-      return NS_OK;
     }
   private:
     PresShell* mPresShell;
     bool mFromScroll;
   };
   nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
   void ProcessSynthMouseMoveEvent(bool aFromScroll);
 
--- a/layout/reftests/bugs/478811-2-ref.html
+++ b/layout/reftests/bugs/478811-2-ref.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
 <html style="height: 100%">
   <body style="height: 100%">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button style="position: relative; display: table; width: 50%;
-                     height: 50%; top: 25%; left: 25%">
+                     height: 50%; top: 25%; left: 25%; pointer-events: none">
       <div style="position: absolute; left: 0; right: 0; top: 0; bottom: 0;
                   background: green"></div>
     </button>
   </body>
 </html>
--- a/layout/reftests/bugs/478811-2.html
+++ b/layout/reftests/bugs/478811-2.html
@@ -7,13 +7,16 @@
         d.setAttribute("style",
                        "position: absolute; left: 0; right: 0; top: 0; " +
                        "bottom: 0; background: green");
         document.getElementById("x").appendChild(d);
       }
     </script>
   </head>
   <body style="height: 100%" onload="doIt()">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button id="x" style="position: relative; display: table; width: 50%;
-                          height: 50%; top: 25%; left: 25%">
+                          height: 50%; top: 25%; left: 25%;
+                          pointer-events: none">
     </button>
   </body>
 </html>
--- a/layout/reftests/bugs/478811-3-ref.html
+++ b/layout/reftests/bugs/478811-3-ref.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
 <html style="height: 100%">
   <body style="height: 100%">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button style="position: relative; display: table-cell; width: 50%;
-                     height: 50%; top: 25%; left: 25%">
+                     height: 50%; top: 25%; left: 25%; pointer-events: none">
       <div style="position: absolute; left: 0; right: 0; top: 0; bottom: 0;
                   background: green"></div>
     </button>
   </body>
 </html>
--- a/layout/reftests/bugs/478811-3.html
+++ b/layout/reftests/bugs/478811-3.html
@@ -7,13 +7,16 @@
         d.setAttribute("style",
                        "position: absolute; left: 0; right: 0; top: 0; " +
                        "bottom: 0; background: green");
         document.getElementById("x").appendChild(d);
       }
     </script>
   </head>
   <body style="height: 100%" onload="doIt()">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button id="x" style="position: relative; display: table-cell; width: 50%;
-                          height: 50%; top: 25%; left: 25%">
+                          height: 50%; top: 25%; left: 25%;
+                          pointer-events: none">
     </button>
   </body>
 </html>
--- a/layout/reftests/bugs/478811-4-ref.html
+++ b/layout/reftests/bugs/478811-4-ref.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
 <html style="height: 100%">
   <body style="height: 100%">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button style="position: relative; display: table-row; width: 50%;
-                     height: 50%; top: 25%; left: 25%">
+                     height: 50%; top: 25%; left: 25%; pointer-events: none">
       <div style="position: absolute; left: 0; right: 0; top: 0; bottom: 0;
                   background: green"></div>
     </button>
   </body>
 </html>
--- a/layout/reftests/bugs/478811-4.html
+++ b/layout/reftests/bugs/478811-4.html
@@ -7,13 +7,16 @@
         d.setAttribute("style",
                        "position: absolute; left: 0; right: 0; top: 0; " +
                        "bottom: 0; background: green");
         document.getElementById("x").appendChild(d);
       }
     </script>
   </head>
   <body style="height: 100%" onload="doIt()">
+    <!-- Button shouldn't get pointer events because it confuses tests if it
+         can be hovered -->
     <button id="x" style="position: relative; display: table-row; width: 50%;
-                          height: 50%; top: 25%; left: 25%">
+                          height: 50%; top: 25%; left: 25%;
+                          pointer-events: none">
     </button>
   </body>
 </html>
--- a/widget/tests/native_mouse_mac_window.xul
+++ b/widget/tests/native_mouse_mac_window.xul
@@ -130,32 +130,50 @@
 
     var gExpectedEvents = [];
     var gRightWindow = null, gPopup = null;
     var gCurrentMouseX = 0, gCurrentMouseY = 0;
     var gAfterLoopExecution = 0;
 
     function testMouse(x, y, msg, elem, win, exp, flags, callback) {
       clearExpectedEvents();
+      var syntheticEvent = null;
       exp.forEach(function (expEv) {
         expEv.screenX = x;
         expEv.screenY = y;
+        if (expEv.synthetic) {
+          is(syntheticEvent, null,
+             "Can't handle two synthetic events in a single testMouse call");
+          syntheticEvent = expEv;
+        }
         gExpectedEvents.push(expEv);
       });
       printDebug("sending event: " + x + ", " + y + " (" + msg + ")\n");
       gCurrentMouseX = x;
       gCurrentMouseY = y;
       netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
       var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                                      getInterface(Components.interfaces.nsIDOMWindowUtils);
-      utils.sendNativeMouseEvent(x, y, msg, flags || 0, elem);
-      gAfterLoopExecution = setTimeout(function () {
+      var callbackFunc = function() {
         clearExpectedEvents();
         callback();
-      }, 0);
+      }
+      if (syntheticEvent) {
+        // Set up this listener before we sendNativeMouseEvent, just
+        // in case that synchronously calls us.
+        eventListenOnce(syntheticEvent.target, syntheticEvent.type,
+                        // Trigger callbackFunc async, so we're not assuming
+                        // anything about how our listener gets ordered with
+                        // others.
+                        function () { SimpleTest.executeSoon(callbackFunc) });
+      }
+      utils.sendNativeMouseEvent(x, y, msg, flags || 0, elem);
+      if (!syntheticEvent) {
+        gAfterLoopExecution = setTimeout(callbackFunc, 0);
+      }
     }
 
     function eventListenOnce(elem, name, callback) {
       elem.addEventListener(name, function(e) {
         elem.removeEventListener(name, arguments.callee, false);
         callback(e);
       }, false);
     }
@@ -295,17 +313,17 @@
         ]],
         [400, 150, NSOtherMouseUp, null, right, [
           { type: "mouseup", target: rightElem },
           { type: "click", target: rightElem },
         ]],
         // Clicking an inactive window should make it active and fire a mouseover
         // event.
         [400, 150, NSLeftMouseDown, null, right, [
-          { type: "mouseover", target: rightElem },
+          { type: "mouseover", target: rightElem, synthetic: true },
         ]],
         [400, 150, NSLeftMouseUp, null, right, [
         ]],
         // Now it's focused, so we should get a mousedown event when clicking.
         [400, 150, NSLeftMouseDown, null, right, [
           { type: "mousedown", target: rightElem },
         ]],
         // Let's drag to the right without letting the button go.
@@ -338,17 +356,25 @@
           { type: "mouseout", target: leftElem, shouldFireButDoesnt: true },
         ]],
         // Right clicking hasn't focused it, so the window is still inactive.
         // Let's focus it; this time without the mouse, for variaton's sake.
         // Still, mouseout and mouseover events should fire.
         function raiseLeftWindow(callback) {
           clearExpectedEvents();
           gExpectedEvents.push({ screenX: 150, screenY: 170, type: "mouseover", target: leftElem });
-          focusAndThen(left, function () { SimpleTest.executeSoon(callback); });
+          // We have to be a bit careful here.  The synthetic mouse event may
+          // not fire for a bit after we focus the left window.
+          eventListenOnce(leftElem, "mouseover", function() {
+            // Trigger callback async, so we're not assuming
+            // anything about how our listener gets ordered with others.
+            SimpleTest.executeSoon(callback);
+          });
+          printDebug("focusing left window");
+          left.focus();
         },
         // It's active, so it should respond to mousemove events now.
         [150, 170, NSMouseMoved, null, left, [
           { type: "mousemove", target: leftElem },
         ]],
         // This was boring... let's introduce a popup. It will overlap both the left
         // and the right window.
         function openPopupInLeftWindow(callback) {
@@ -379,17 +405,17 @@
           { type: "mouseout", target: gPopup },
         ]],
         // Again, no mouse events please, even though a popup is open. (bug 425556)
         [400, 180, NSMouseMoved, null, right, [
         ]],
         // Activate the right window with a click.
         // This will close the popup and make the mouse enter the right window.
         [400, 180, NSLeftMouseDown, null, right, [
-          { type: "mouseover", target: rightElem },
+          { type: "mouseover", target: rightElem, synthetic: true },
         ]],
         [400, 180, NSLeftMouseUp, null, right, [
         ]],
         function verifyPopupClosed2(callback) {
           is(gPopup.popupBoxObject.popupState, "closed", "popup should have closed when clicking");
           callback();
         },
         // Now the right window is active; click it again, just for fun.
--- a/widget/tests/test_bug596600.xul
+++ b/widget/tests/test_bug596600.xul
@@ -104,40 +104,65 @@ function test2() {
   box.style.top = "50px";
   box.style.width = "100px";
   box.style.height = "100px";
   box.style.backgroundColor = "green";
   doc.body.appendChild(box);
 
   ok(!box.mozMatchesSelector(":hover"), "Box shouldn't be hovered (since the mouse isn't over it and since it's in a non-clickthrough iframe in a background window)");
 
+  // A function to waitForFocus and then wait for synthetic mouse
+  // events to happen.  Note that those happen off the refresh driver,
+  // and happen after animation frame requests.
+  function changeFocusAndAwaitSyntheticMouse(callback, winToFocus,
+                                             elementToWatchForMouseEventOn) {
+     function mouseWatcher() {
+       elementToWatchForMouseEventOn.removeEventListener("mouseover",
+                                                         mouseWatcher,
+                                                         false);
+       elementToWatchForMouseEventOn.removeEventListener("mouseout",
+                                                         mouseWatcher,
+                                                         false);
+       SimpleTest.executeSoon(callback);
+     }
+     elementToWatchForMouseEventOn.addEventListener("mouseover",
+                                                    mouseWatcher,
+                                                    false);
+     elementToWatchForMouseEventOn.addEventListener("mouseout",
+                                                    mouseWatcher,
+                                                    false);
+     // Just pass a dummy function to waitForFocus; the mouseout/over listener
+     // will actually handle things for us.
+     SimpleTest.waitForFocus(function() {}, winToFocus);
+  }
+
   // Move the mouse over the box.
   moveMouseTo(100, 150, function () {
     ok(!box.mozMatchesSelector(":hover"), "Box shouldn't be hovered (since it's in a non-clickthrough iframe in a background window)");
     // Activate the left window.
-    SimpleTest.waitForFocus(function () {
+    changeFocusAndAwaitSyntheticMouse(function () {
       ok(gIFrame.mozMatchesSelector(":hover"), "iframe should be hovered");
       ok(box.mozMatchesSelector(":hover"), "Box should be hovered");
       // De-activate the window (by activating the right window).
-      SimpleTest.waitForFocus(function () {
+      changeFocusAndAwaitSyntheticMouse(function () {
         ok(!gIFrame.mozMatchesSelector(":hover"), "iframe shouldn't be hovered");
         ok(!box.mozMatchesSelector(":hover"), "Box shouldn't be hovered");
         // Re-activate it.
-        SimpleTest.waitForFocus(function () {
+        changeFocusAndAwaitSyntheticMouse(function () {
           ok(gIFrame.mozMatchesSelector(":hover"), "iframe should be hovered");
           ok(box.mozMatchesSelector(":hover"), "Box should be hovered");
           // Unhover box and iframe by moving the mouse outside the window.
           moveMouseTo(0, 150, function () {
             ok(!gIFrame.mozMatchesSelector(":hover"), "iframe shouldn't be hovered");
             ok(!box.mozMatchesSelector(":hover"), "box shouldn't be hovered");
             finalize();
           });
-        }, gLeftWindow);
-      }, gRightWindow);
-    }, gLeftWindow);
+        }, gLeftWindow, box);
+      }, gRightWindow, box);
+    }, gLeftWindow, box);
   });
 }
 
 function finalize() {
   gRightWindow.close();
   gLeftWindow.close();
   SimpleTest.finish();
 }