Bug 1136910: Fix browser_tab_dragdrop.js to avoid CPOWs and correctly test clicks. r=mconley, r=ttaubert
authorDave Townsend <dtownsend@oxymoronical.com>
Mon, 09 Mar 2015 15:53:01 -0700
changeset 234146 2f0f28241140a8500622bb9b0a8273ddc4317a58
parent 234145 ad6136c1a4d05e745c0b23200a7647ec610f4a72
child 234147 e25b43e52e8847e8ae727609ba4a0d7371ee23b6
push id57048
push userkwierso@gmail.com
push dateWed, 18 Mar 2015 02:06:29 +0000
treeherdermozilla-inbound@e5a94f80f342 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley, ttaubert
bugs1136910
milestone39.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 1136910: Fix browser_tab_dragdrop.js to avoid CPOWs and correctly test clicks. r=mconley, r=ttaubert Switches the whole test to task based and removes most of the CPOW traffic, the plugin checks are the notable exception. This also makes ContentTask act sanely in the presence of tab detach by using browser.permanentKey as the indication that the framescript has been loaded and just listening to all frames. Also adds a click in the upper-left of the content area to browser_tabopen_reflows.js to make sure the mouse isn't hovered over one of the tiles causing an unexpected reflow.
browser/base/content/test/general/browser_tab_dragdrop.js
browser/base/content/test/general/browser_tabopen_reflows.js
testing/mochitest/BrowserTestUtils/ContentTask.jsm
--- a/browser/base/content/test/general/browser_tab_dragdrop.js
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -1,131 +1,117 @@
-function test()
-{
-  var embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
+function swapTabsAndCloseOther(a, b) {
+  gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
+}
+
+let getClicks = function(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, {}, function() {
+    return content.wrappedJSObject.clicks;
+  });
+}
+
+let clickTest = Task.async(function*(tab) {
+  let clicks = yield getClicks(tab);
 
-  waitForExplicitFinish();
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
+    let target = content.document.body;
+    let rect = target.getBoundingClientRect();
+    let left = (rect.left + rect.right) / 2;
+    let top = (rect.top + rect.bottom) / 2;
+
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+
+  let newClicks = yield getClicks(tab);
+  is(newClicks, clicks + 1, "adding 1 more click on BODY");
+});
+
+function loadURI(tab, url) {
+  tab.linkedBrowser.loadURI(url);
+  return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+}
+
+add_task(function*() {
+  let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
 
   // create a few tabs
-  var tabs = [
+  let tabs = [
     gBrowser.tabs[0],
     gBrowser.addTab("about:blank", {skipAnimation: true}),
     gBrowser.addTab("about:blank", {skipAnimation: true}),
     gBrowser.addTab("about:blank", {skipAnimation: true}),
     gBrowser.addTab("about:blank", {skipAnimation: true})
   ];
 
-  function setLocation(i, url) {
-    tabs[i].linkedBrowser.contentWindow.location = url;
-  }
-  function moveTabTo(a, b) {
-    gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
-  }
-  function clickTest(tab, doc, win) {
-    var clicks = doc.defaultView.clicks;
-
-    yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
-      let target = content.document.body;
-      let rect = target.getBoundingClientRect();
-      let left = (rect.left + rect.right) / 2;
-      let top = (rect.top + rect.bottom) / 2;
-
-      let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                         .getInterface(Components.interfaces.nsIDOMWindowUtils);
-      utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
-      utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
-    });
-
-    is(doc.defaultView.clicks, clicks+1, "adding 1 more click on BODY");
-  }
-  function test1() {
-    moveTabTo(2, 3); // now: 0 1 2 4
-    is(gBrowser.tabs[1], tabs[1], "tab1");
-    is(gBrowser.tabs[2], tabs[3], "tab3");
-
-    var plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
-    var tab4_plugin_object = plugin.getObjectValue();
-
-    gBrowser.selectedTab = gBrowser.tabs[2];
-    moveTabTo(3, 2); // now: 0 1 4
-    gBrowser.selectedTab = tabs[4];
-    var doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
-    plugin = doc.body.firstChild;
-    ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
-    is(gBrowser.tabs[1], tabs[1], "tab1");
-    is(gBrowser.tabs[2], tabs[3], "tab4");
-    is(doc.defaultView.clicks, 0, "no click on BODY so far");
-    clickTest(gBrowser.tabs[2], doc, window);
-
-    moveTabTo(2, 1); // now: 0 4
-    is(gBrowser.tabs[1], tabs[1], "tab1");
-    doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
-    plugin = doc.body.firstChild;
-    ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
-    clickTest(gBrowser.tabs[1], doc, window);
-
-    // Load a new document (about:blank) in tab4, then detach that tab into a new window.
-    // In the new window, navigate back to the original document and click on its <body>,
-    // verify that its onclick was called.
-    var t = tabs[1];
-    var b = t.linkedBrowser;
-    gBrowser.selectedTab = t;
-    b.addEventListener("load", function() {
-      b.removeEventListener("load", arguments.callee, true);
-
-      executeSoon(function () {
-        var win = gBrowser.replaceTabWithWindow(t);
-        whenDelayedStartupFinished(win, function () {
-          // Verify that the original window now only has the initial tab left in it.
-          is(gBrowser.tabs[0], tabs[0], "tab0");
-          is(gBrowser.tabs[0].linkedBrowser.contentWindow.location, "about:blank", "tab0 uri");
-
-          executeSoon(function () {
-            win.gBrowser.addEventListener("pageshow", function () {
-              win.gBrowser.removeEventListener("pageshow", arguments.callee, false);
-              executeSoon(function () {
-                t = win.gBrowser.tabs[0];
-                b = t.linkedBrowser;
-                var doc = b.contentDocument.wrappedJSObject;
-                clickTest(t, doc, win);
-                win.close();
-                finish();
-              });
-            }, false);
-            win.gBrowser.goBack();
-          });
-        });
-      });
-    }, true);
-    b.loadURI("about:blank");
-
-  }
-
-  var loads = 0;
-  function waitForLoad(event, tab, listenerContainer) {
-    var b = tabs[tab].linkedBrowser;
-    if (b.contentDocument != event.target) {
-      return;
-    }
-    gBrowser.tabs[tab].linkedBrowser.removeEventListener("load", listenerContainer.listener, true);
-    ++loads;
-    if (loads == tabs.length - 1) {
-      executeSoon(test1);
-    }
-  }
-
-  function fn(f, arg) {
-    var listenerContainer = { listener: null }
-    listenerContainer.listener = function (event) { return f(event, arg, listenerContainer); };
-    return listenerContainer.listener;
-  }
-  for (var i = 1; i < tabs.length; ++i) {
-    tabs[i].linkedBrowser.addEventListener("load", fn(waitForLoad,i), true);
-  }
-
-  setLocation(1, "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
-  setLocation(2, "data:text/plain;charset=utf-8,tab2");
-  setLocation(3, "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
-  setLocation(4, "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
+  // Initially 0 1 2 3 4
+  yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
+  yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2");
+  yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
+  yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
   gBrowser.selectedTab = tabs[3];
 
-}
+  swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
+  is(gBrowser.tabs[1], tabs[1], "tab1");
+  is(gBrowser.tabs[2], tabs[3], "tab3");
+  is(gBrowser.tabs[3], tabs[4], "tab4");
+
+  let plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
+  let tab4_plugin_object = plugin.getObjectValue();
+
+  swapTabsAndCloseOther(3, 2); // now: 0 1 4
+  gBrowser.selectedTab = gBrowser.tabs[2];
+
+  let doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
+  plugin = doc.body.firstChild;
+  ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+
+  is(gBrowser.tabs[1], tabs[1], "tab1");
+  is(gBrowser.tabs[2], tabs[3], "tab4");
+
+  let clicks = yield getClicks(gBrowser.tabs[2]);
+  is(clicks, 0, "no click on BODY so far");
+  yield clickTest(gBrowser.tabs[2]);
+
+  swapTabsAndCloseOther(2, 1); // now: 0 4
+  is(gBrowser.tabs[1], tabs[1], "tab1");
+
+  doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
+  plugin = doc.body.firstChild;
+  ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+
+  yield clickTest(gBrowser.tabs[1]);
+
+  // Load a new document (about:blank) in tab4, then detach that tab into a new window.
+  // In the new window, navigate back to the original document and click on its <body>,
+  // verify that its onclick was called.
+  gBrowser.selectedTab = tabs[1];
+  yield loadURI(tabs[1], "about:blank");
+  let key = tabs[1].linkedBrowser.permanentKey;
+
+  let win = gBrowser.replaceTabWithWindow(tabs[1]);
+  yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+
+  // Verify that the original window now only has the initial tab left in it.
+  is(gBrowser.tabs[0], tabs[0], "tab0");
+  is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri");
+
+  let tab = win.gBrowser.tabs[0];
+  is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
+
+  let pageshowPromise = ContentTask.spawn(tab.linkedBrowser, {}, function*() {
+    return new Promise(resolve => {
+      let listener = function() {
+        removeEventListener("pageshow", listener, false);
+        resolve();
+      }
+      addEventListener("pageshow", listener, false);
+    });
+  });
+  win.gBrowser.goBack();
+  yield pageshowPromise;
+
+  yield clickTest(tab);
+  promiseWindowClosed(win);
+});
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -50,18 +50,17 @@ const EXPECTED_REFLOWS = [
 
 const PREF_PRELOAD = "browser.newtab.preload";
 const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
-function test() {
-  waitForExplicitFinish();
+add_task(function*() {
   let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
   let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
   let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 
   // resolves promise when directory links are downloaded and written to disk
   function watchLinksChangeOnce() {
     let deferred = Promise.defer();
     let observer = {
@@ -80,35 +79,46 @@ function test() {
 
   let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref(PREF_PRELOAD);
     Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
     return watchLinksChangeOnce();
   });
 
-  // run tests when directory source change completes
-  watchLinksChangeOnce().then(() => {
-    // Add a reflow observer and open a new tab.
-    docShell.addWeakReflowObserver(observer);
-    BrowserOpenTab();
-
-    // Wait until the tabopen animation has finished.
-    waitForTransitionEnd(function () {
-      // Remove reflow observer and clean up.
-      docShell.removeWeakReflowObserver(observer);
-      gBrowser.removeCurrentTab();
-      finish();
-    });
-  });
-
   Services.prefs.setBoolPref(PREF_PRELOAD, false);
   // set directory source to dummy/empty links
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
-}
+
+  // run tests when directory source change completes
+  yield watchLinksChangeOnce();
+
+  // Perform a click in the top left of content to ensure the mouse isn't
+  // hovering over any of the tiles
+  let target = gBrowser.selectedBrowser;
+  let rect = target.getBoundingClientRect();
+  let left = rect.left + 1;
+  let top = rect.top + 1;
+
+  let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+  utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+  utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+  // Add a reflow observer and open a new tab.
+  docShell.addWeakReflowObserver(observer);
+  BrowserOpenTab();
+
+  // Wait until the tabopen animation has finished.
+  yield waitForTransitionEnd();
+
+  // Remove reflow observer and clean up.
+  docShell.removeWeakReflowObserver(observer);
+  gBrowser.removeCurrentTab();
+});
 
 let observer = {
   reflow: function (start, end) {
     // Gather information about the current code path.
     let path = (new Error().stack).split("\n").slice(1).map(line => {
       return line.replace(/:\d+:\d+$/, "");
     }).join("|");
     let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
@@ -132,17 +142,19 @@ let observer = {
   reflowInterruptible: function (start, end) {
     // We're not interested in interruptible reflows.
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
-function waitForTransitionEnd(callback) {
-  let tab = gBrowser.selectedTab;
-  tab.addEventListener("transitionend", function onEnd(event) {
-    if (event.propertyName === "max-width") {
-      tab.removeEventListener("transitionend", onEnd);
-      executeSoon(callback);
-    }
+function waitForTransitionEnd() {
+  return new Promise(resolve => {
+    let tab = gBrowser.selectedTab;
+    tab.addEventListener("transitionend", function onEnd(event) {
+      if (event.propertyName === "max-width") {
+        tab.removeEventListener("transitionend", onEnd);
+        resolve();
+      }
+    });
   });
 }
--- a/testing/mochitest/BrowserTestUtils/ContentTask.jsm
+++ b/testing/mochitest/BrowserTestUtils/ContentTask.jsm
@@ -5,17 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "ContentTask"
 ];
 
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 Cu.import("resource://gre/modules/Promise.jsm");
 
 /**
  * Set of browsers which have loaded the content-task frame script.
  */
 let gScriptLoadedSet = new WeakSet();
 
 /**
@@ -45,23 +45,22 @@ this.ContentTask = {
    *          sent to the remote browser.
    * @return A promise object where you can register completion callbacks to be
    *         called when the task terminates.
    * @resolves With the final returned value of the task if it executes
    *           successfully.
    * @rejects An error message if execution fails.
    */
   spawn: function ContentTask_spawn(browser, arg, task) {
-    if(!gScriptLoadedSet.has(browser)) {
+    if(!gScriptLoadedSet.has(browser.permanentKey)) {
       let mm = browser.messageManager;
-      mm.addMessageListener("content-task:complete", ContentMessageListener);
       mm.loadFrameScript(
         "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js", true);
 
-      gScriptLoadedSet.add(browser);
+      gScriptLoadedSet.add(browser.permanentKey);
     }
 
     let deferred = {};
     deferred.promise = new Promise((resolve, reject) => {
       deferred.resolve = resolve;
       deferred.reject = reject;
     });
 
@@ -88,8 +87,10 @@ let ContentMessageListener = {
 
     if (aMessage.data.error) {
       deferred.reject(aMessage.data.error);
     } else {
       deferred.resolve(aMessage.data.result);
     }
   },
 };
+Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager)
+  .addMessageListener("content-task:complete", ContentMessageListener);