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 85596 899a12aeff6cb8d35a7d2a0ddea9b0f6acb427f7
parent 85595 d83fa420aa7bc1d50eaf1fb3aefb6182fa1a11c6
child 85597 821b2b0daf21a4f3cb0dba5f67eb20b951c19707
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs716793
milestone12.0a1
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();
 }