Bug 1323185 - Add window (tab) handling support for Fennec. r=ato a=test-only
authorHenrik Skupin <mail@hskupin.info>
Thu, 26 Jan 2017 16:42:35 +0100
changeset 375689 addfb6ec7605e8a96b456df1fdf1f2cae693ee9e
parent 375688 53f4b1138f2676c47f878240b863cb074a3176af
child 375690 918d36ef8b13b8965912e2133afa6177f2ae8ed5
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato, test-only
bugs1323185
milestone53.0a2
Bug 1323185 - Add window (tab) handling support for Fennec. r=ato a=test-only So far Marionette did support Fennec but not any of its tab handling features. As such most of the commands fail because they do not take BrowserApp into account. This patch adds support for retrieving window handles, switching between windows, and closing tabs. Additionally to those changes a couple of unit tests are getting updated, and added. MozReview-Commit-ID: 7sbVIblm0Hw
testing/marionette/browser.js
testing/marionette/driver.js
testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_handles.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
testing/marionette/harness/marionette_harness/www/windowHandles.html
--- a/testing/marionette/browser.js
+++ b/testing/marionette/browser.js
@@ -11,16 +11,69 @@ Cu.import("chrome://marionette/content/e
 Cu.import("chrome://marionette/content/frame.js");
 
 this.EXPORTED_SYMBOLS = ["browser"];
 
 this.browser = {};
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+
+/**
+ * Get the <xul:browser> for the specified tab.
+ *
+ * @param {<xul:tab>} tab
+ *     The tab whose browser needs to be returned.
+ *
+ * @return {<xul:browser>}
+ *     The linked browser for the tab.
+ *
+ * @throws UnsupportedOperationError
+ *     If tab handling for the current application isn't supported.
+ */
+browser.getBrowserForTab = function (tab) {
+  if (tab.hasOwnProperty("browser")) {
+    // Fennec
+    return tab.browser;
+
+  } else if (tab.hasOwnProperty("linkedBrowser")) {
+    // Firefox
+    return tab.linkedBrowser;
+
+  } else {
+      new UnsupportedOperationError("getBrowserForTab() not supported.");
+  }
+};
+
+/**
+ * Return the tab browser for the specified chrome window.
+ *
+ * @param {nsIDOMWindow} win
+ *     The window whose tabbrowser needs to be accessed.
+ *
+ * @return {<xul:tabbrowser>}
+ *     Tab browser or null if it's not a browser window.
+ *
+ * @throws UnsupportedOperationError
+ *     If tab handling for the current application isn't supported.
+ */
+browser.getTabBrowser = function (win) {
+  if (win.hasOwnProperty("BrowserApp")) {
+    // Fennec
+    return win.BrowserApp;
+
+  } else if (win.hasOwnProperty("gBrowser")) {
+    // Firefox
+    return win.gBrowser;
+
+  } else {
+      new UnsupportedOperationError("getBrowserForTab() not supported.");
+  }
+};
+
 /**
  * Creates a browsing context wrapper.
  *
  * Browsing contexts handle interactions with the browser, according to
  * the current environment (desktop, B2G, Fennec, &c).
  *
  * @param {nsIDOMWindow} win
  *     The window whose browser needs to be accessed.
@@ -36,18 +89,17 @@ browser.Context = class {
    *     Reference to driver instance.
    */
   constructor(win, driver) {
     this.window = win;
     this.driver = driver;
 
     // In Firefox this is <xul:tabbrowser> (not <xul:browser>!)
     // and BrowserApp in Fennec
-    this.browser = undefined;
-    this.setBrowser(win);
+    this.tabBrowser = browser.getTabBrowser(win);
 
     this.knownFrames = [];
 
     // Used in B2G to identify the homescreen content page
     this.mainContentId = null;
 
     // Used to set curFrameId upon new session
     this.newSession = true;
@@ -74,87 +126,54 @@ browser.Context = class {
     this.getIdForBrowser = driver.getIdForBrowser.bind(driver);
     this.updateIdForBrowser = driver.updateIdForBrowser.bind(driver);
     this._curFrameId = null;
     this._browserWasRemote = null;
     this._hasRemotenessChange = false;
   }
 
   /**
-   * Get the <xul:browser> for the current tab in this tab browser.
-   *
-   * @return {<xul:browser>}
-   *     Browser linked to |this.tab| or the tab browser's
-   *     |selectedBrowser|.
-   */
-  get browserForTab() {
-    if (this.browser.getBrowserForTab) {
-      return this.browser.getBrowserForTab(this.tab);
-    } else {
-      return this.browser.selectedBrowser;
-    }
-  }
-
-  /**
    * The current frame ID is managed per browser element on desktop in
    * case the ID needs to be refreshed. The currently selected window is
    * identified by a tab.
    */
   get curFrameId() {
     let rv = null;
     if (this.driver.appName == "B2G") {
       rv = this._curFrameId;
     } else if (this.tab) {
-      rv = this.getIdForBrowser(this.browserForTab);
+      rv = this.getIdForBrowser(browser.getBrowserForTab(this.tab));
     }
     return rv;
   }
 
   set curFrameId(id) {
     if (this.driver.appName != "Firefox") {
       this._curFrameId = id;
     }
   }
 
   /**
    * Retrieves the current tabmodal UI object.  According to the browser
    * associated with the currently selected tab.
    */
   getTabModalUI() {
-    let br = this.browserForTab;
+    let br = browser.getBrowserForTab(this.tab);
     if (!br.hasAttribute("tabmodalPromptShowing")) {
       return null;
     }
 
     // The modal is a direct sibling of the browser element.
     // See tabbrowser.xml's getTabModalPromptBox.
     let modals = br.parentNode.getElementsByTagNameNS(
         XUL_NS, "tabmodalprompt");
     return modals[0].ui;
   }
 
   /**
-   * Set the browser if the application is not B2G.
-   *
-   * @param {nsIDOMWindow} win
-   *     Current window reference.
-   */
-  setBrowser(win) {
-    switch (this.driver.appName) {
-      case "Firefox":
-        this.browser = win.gBrowser;
-        break;
-
-      case "Fennec":
-        this.browser = win.BrowserApp;
-        break;
-    }
-  }
-
-  /**
    * Close the current window.
    *
    * @return {Promise}
    *     A promise which is resolved when the current window has been closed.
    */
   closeWindow() {
     return new Promise(resolve => {
       this.window.addEventListener("unload", ev => {
@@ -169,90 +188,120 @@ browser.Context = class {
     callback(win, newSession);
   }
 
   /**
    * Close the current tab.
    *
    * @return {Promise}
    *     A promise which is resolved when the current tab has been closed.
+   *
+   * @throws UnsupportedOperationError
+   *     If tab handling for the current application isn't supported.
    */
   closeTab() {
     // If the current window is not a browser then close it directly. Do the
     // same if only one remaining tab is open, or no tab selected at all.
-    if (!this.browser || !this.tab || this.browser.browsers.length == 1) {
+    if (!this.tabBrowser || this.tabBrowser.tabs.length === 1 || !this.tab) {
       return this.closeWindow();
     }
 
     return new Promise((resolve, reject) => {
-      if (this.browser.removeTab) {
+      if (this.tabBrowser.closeTab) {
+        // Fennec
+        this.tabBrowser.deck.addEventListener("TabClose", ev => {
+          resolve();
+        }, {once: true});
+        this.tabBrowser.closeTab(this.tab);
+
+      } else if (this.tabBrowser.removeTab) {
+        // Firefox
         this.tab.addEventListener("TabClose", ev => {
           resolve();
         }, {once: true});
-        this.browser.removeTab(this.tab);
+        this.tabBrowser.removeTab(this.tab);
+
       } else {
         reject(new UnsupportedOperationError(
             `closeTab() not supported in ${this.driver.appName}`));
       }
     });
   }
 
   /**
    * Opens a tab with given URI.
    *
    * @param {string} uri
    *      URI to open.
    */
   addTab(uri) {
-    return this.browser.addTab(uri, true);
+    return this.tabBrowser.addTab(uri, true);
   }
 
   /**
-   * Re-sets current tab and updates remoteness tracking.
+   * Set the current tab and update remoteness tracking if a tabbrowser is available.
    *
-   * If a window is provided, the internal reference is updated before
-   * proceeding.
+   * @param {number=} index
+   *     Tab index to switch to. If the parameter is undefined,
+   *     the currently selected tab will be used.
+   * @param {nsIDOMWindow=} win
+   *     Switch to this window before selecting the tab.
    */
-  switchToTab(ind, win) {
+  switchToTab(index, win) {
     if (win) {
       this.window = win;
-      this.setBrowser(win);
+      this.tabBrowser = browser.getTabBrowser(win);
+    }
+
+    if (!this.tabBrowser) {
+      return;
     }
-    if (this.browser.selectTabAtIndex) {
-      this.browser.selectTabAtIndex(ind);
-      this.tab = this.browser.selectedTab;
-      this._browserWasRemote = this.browserForTab.isRemoteBrowser;
+
+    if (typeof index == "undefined") {
+      this.tab = this.tabBrowser.selectedTab;
+    } else {
+      this.tab = this.tabBrowser.tabs[index];
+
+      if (this.tabBrowser.selectTab) {
+        // Fennec
+        this.tabBrowser.selectTab(this.tab);
+
+      } else {
+        // Firefox
+        this.tabBrowser.selectedTab = this.tab;
+      }
     }
-    else {
-      this.tab = this.browser.selectedTab;
+
+    if (this.driver.appName == "Firefox") {
+      this._browserWasRemote = browser.getBrowserForTab(this.tab).isRemoteBrowser;
+      this._hasRemotenessChange = false;
     }
-    this._hasRemotenessChange = false;
   }
 
   /**
    * Registers a new frame, and sets its current frame id to this frame
    * if it is not already assigned, and if a) we already have a session
    * or b) we're starting a new session and it is the right start frame.
    *
    * @param {string} uid
    *     Frame uid for use by Marionette.
    * @param the XUL <browser> that was the target of the originating message.
    */
   register(uid, target) {
     let remotenessChange = this.hasRemotenessChange();
     if (this.curFrameId === null || remotenessChange) {
-      if (this.browser) {
+      if (this.tabBrowser) {
         // If we're setting up a new session on Firefox, we only process the
         // registration for this frame if it belongs to the current tab.
         if (!this.tab) {
-          this.switchToTab(this.browser.selectedIndex);
+          this.switchToTab();
         }
 
-        if (target == this.browserForTab) {
-          this.updateIdForBrowser(this.browserForTab, uid);
+        if (target == browser.getBrowserForTab(this.tab)) {
+          this.updateIdForBrowser(browser.getBrowserForTab(this.tab), uid);
           this.mainContentId = uid;
         }
       } else {
         this._curFrameId = uid;
         this.mainContentId = uid;
       }
     }
 
@@ -266,25 +315,25 @@ browser.Context = class {
    * process, we need to take measures not to lose contact with a listener
    * script. This function does the necessary bookkeeping.
    */
   hasRemotenessChange() {
     // None of these checks are relevant on b2g or if we don't have a tab yet,
     // and may not apply on Fennec.
     if (this.driver.appName != "Firefox" ||
         this.tab === null ||
-        this.browserForTab === null) {
+        browser.getBrowserForTab(this.tab) === null) {
       return false;
     }
 
     if (this._hasRemotenessChange) {
       return true;
     }
 
-    let currentIsRemote = this.browserForTab.isRemoteBrowser;
+    let currentIsRemote = browser.getBrowserForTab(this.tab).isRemoteBrowser;
     this._hasRemotenessChange = this._browserWasRemote !== currentIsRemote;
     this._browserWasRemote = currentIsRemote;
     return this._hasRemotenessChange;
   }
 
   /**
    * Flushes any pending commands queued when a remoteness change is being
    * processed and mark this remotenessUpdate as complete.
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -186,24 +186,25 @@ Object.defineProperty(GeckoDriver.protot
 
 Object.defineProperty(GeckoDriver.prototype, "windowHandles", {
   get: function () {
     let hs = [];
     let winEn = Services.wm.getEnumerator(null);
 
     while (winEn.hasMoreElements()) {
       let win = winEn.getNext();
-      if (win.gBrowser) {
-        let tabbrowser = win.gBrowser;
-        for (let i = 0; i < tabbrowser.browsers.length; ++i) {
-          let winId = this.getIdForBrowser(tabbrowser.getBrowserAtIndex(i));
+      let tabBrowser = browser.getTabBrowser(win);
+
+      if (tabBrowser) {
+        tabBrowser.tabs.forEach(tab => {
+          let winId = this.getIdForBrowser(browser.getBrowserForTab(tab));
           if (winId !== null) {
             hs.push(winId);
           }
-        }
+        });
       } else {
         // For other chrome windows beside the browser window, only count the window itself.
         let winId = win.QueryInterface(Ci.nsIInterfaceRequestor)
             .getInterface(Ci.nsIDOMWindowUtils)
             .outerWindowID;
         hs.push(winId.toString());
       }
     }
@@ -650,17 +651,19 @@ GeckoDriver.prototype.newSession = funct
     }, BROWSER_STARTUP_FINISHED, false);
   } else {
     runSessionStart.call(this);
   }
 
   yield registerBrowsers;
   yield browserListening;
 
-  this.curBrowser.browserForTab.focus();
+  if (this.curBrowser.tab) {
+    browser.getBrowserForTab(this.curBrowser.tab).focus();
+  }
 
   return {
     sessionId: this.sessionId,
     capabilities: this.capabilities,
   };
 };
 
 /**
@@ -809,17 +812,17 @@ GeckoDriver.prototype.executeScript = fu
  *     Arguments exposed to the script in {@code arguments}.  The array
  *     items must be serialisable to the WebDriver protocol.
  * @param {number} scriptTimeout
  *     Duration in milliseconds of when to interrupt and abort the
  *     script evaluation.
  * @param {string=} sandbox
  *     Name of the sandbox to evaluate the script in.  The sandbox is
  *     cached for later re-use on the same Window object if
- *     {@code newSandbox} is false.  If he parameter is undefined,
+ *     {@code newSandbox} is false.  If the parameter is undefined,
  *     the script is evaluated in a mutable sandbox.  If the parameter
  *     is "system", it will be evaluted in a sandbox with elevated system
  *     privileges, equivalent to chrome space.
  * @param {boolean=} newSandbox
  *     Forces the script to be evaluated in a fresh sandbox.  Note that if
  *     it is undefined, the script will normally be evaluted in a fresh
  *     sandbox.
  * @param {string=} filename
@@ -967,17 +970,17 @@ GeckoDriver.prototype.get = function*(cm
     cmd.parameters.command_id = id;
     cmd.parameters.pageTimeout = this.timeouts.pageLoad;
     this.mm.broadcastAsyncMessage(
         "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
         cmd.parameters);
   });
 
   yield get;
-  this.curBrowser.browserForTab.focus();
+  browser.getBrowserForTab(this.curBrowser.tab).focus();
 };
 
 /**
  * Get a string representing the current URL.
  *
  * On Desktop this returns a string representation of the URL of the
  * current top level browsing context.  This is equivalent to
  * document.location.href.
@@ -1194,17 +1197,16 @@ GeckoDriver.prototype.setWindowPosition 
  * Switch current top-level browsing context by name or server-assigned ID.
  * Searches for windows by name, then ID.  Content windows take precedence.
  *
  * @param {string} name
  *     Target name or ID of the window to switch to.
  */
 GeckoDriver.prototype.switchToWindow = function* (cmd, resp) {
   let switchTo = cmd.parameters.name;
-  let isMobile = this.appName == "Fennec";
   let found;
 
   let getOuterWindowId = function (win) {
     let rv = win.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils)
         .outerWindowID;
     return rv;
   };
@@ -1214,22 +1216,23 @@ GeckoDriver.prototype.switchToWindow = f
         switchTo == contentWindowId ||
         switchTo == outerId;
   };
 
   let winEn = Services.wm.getEnumerator(null);
   while (winEn.hasMoreElements()) {
     let win = winEn.getNext();
     let outerId = getOuterWindowId(win);
+    let tabbrowser = browser.getTabBrowser(win);
 
-    if (win.gBrowser && !isMobile) {
-      let tabbrowser = win.gBrowser;
-      for (let i = 0; i < tabbrowser.browsers.length; ++i) {
-        let browser = tabbrowser.getBrowserAtIndex(i);
-        let contentWindowId = this.getIdForBrowser(browser);
+    if (tabbrowser) {
+      for (let i = 0; i < tabbrowser.tabs.length; ++i) {
+        let contentBrowser = browser.getBrowserForTab(tabbrowser.tabs[i]);
+        let contentWindowId = this.getIdForBrowser(contentBrowser);
+
         if (byNameOrId(win.name, contentWindowId, outerId)) {
           found = {
             win: win,
             outerId: outerId,
             tabIndex: i,
           };
           break;
         }
@@ -2109,18 +2112,19 @@ GeckoDriver.prototype.deleteCookie = fun
 GeckoDriver.prototype.close = function (cmd, resp) {
   let nwins = 0;
   let winEn = Services.wm.getEnumerator(null);
 
   while (winEn.hasMoreElements()) {
     let win = winEn.getNext();
 
     // For browser windows count the tabs. Otherwise take the window itself.
-    if (win.gBrowser) {
-      nwins += win.gBrowser.browsers.length;
+    let tabbrowser = browser.getTabBrowser(win);
+    if (tabbrowser) {
+      nwins += tabbrowser.tabs.length;
     } else {
       nwins++;
     }
   }
 
   // If there is only 1 window left, do not close it. Instead return a faked
   // empty array of window handles. This will instruct geckodriver to terminate
   // the application.
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
@@ -1,114 +1,137 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from marionette_driver.by import By
+from marionette_driver import By, Wait
 from marionette_driver.keys import Keys
 
-from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
+from marionette_harness import MarionetteTestCase, skip, skip_if_mobile, WindowManagerMixin
 
 
 class TestAboutPages(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestAboutPages, self).setUp()
 
         if self.marionette.session_capabilities['platformName'] == 'darwin':
             self.mod_key = Keys.META
         else:
             self.mod_key = Keys.CONTROL
 
-        self.remote_uri = self.marionette.absolute_url("javascriptPage.html")
-        self.marionette.navigate(self.remote_uri)
+        self.remote_uri = self.marionette.absolute_url("windowHandles.html")
 
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestAboutPages, self).tearDown()
 
-    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
+    def open_tab_with_link(self):
+        with self.marionette.using_context("content"):
+            self.marionette.navigate(self.remote_uri)
+
+            link = self.marionette.find_element(By.ID, "new-tab")
+            link.click()
+
+    @skip_if_mobile("Bug 1333209 - Process killed because of connection loss")
     def test_back_forward(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
-        new_tab = self.open_tab(trigger='menu')
+        new_tab = self.open_tab(trigger=self.open_tab_with_link)
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate("about:blank")
         self.marionette.navigate(self.remote_uri)
-
-        self.marionette.navigate("about:preferences")
+        self.marionette.navigate("about:support")
 
         self.marionette.go_back()
+        Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri,
+                                    message="'{}' hasn't been loaded".format(self.remote_uri))
 
-        self.wait_for_condition(
-            lambda mn: mn.get_url() == self.remote_uri)
+        # Bug 1332998 - Timeout loading the page
+        # self.marionette.go_forward()
+        # Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri,
+        #                             message="'about:support' hasn't been loaded")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
+    @skip_if_mobile("Bug 1333209 - Process killed because of connection loss")
     def test_navigate_non_remote_about_pages(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
-        new_tab = self.open_tab(trigger='menu')
+        new_tab = self.open_tab(trigger=self.open_tab_with_link)
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate("about:blank")
         self.assertEqual(self.marionette.get_url(), "about:blank")
-        self.marionette.navigate("about:preferences")
-        self.assertEqual(self.marionette.get_url(), "about:preferences")
+        self.marionette.navigate("about:support")
+        self.assertEqual(self.marionette.get_url(), "about:support")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
     @skip_if_mobile("On Android no shortcuts are available")
     def test_navigate_shortcut_key(self):
         def open_with_shortcut():
+            self.marionette.navigate(self.remote_uri)
             with self.marionette.using_context("chrome"):
                 main_win = self.marionette.find_element(By.ID, "main-window")
                 main_win.send_keys(self.mod_key, Keys.SHIFT, 'a')
 
         new_tab = self.open_tab(trigger=open_with_shortcut)
         self.marionette.switch_to_window(new_tab)
 
-        self.wait_for_condition(lambda mn: mn.get_url() == "about:addons")
+        Wait(self.marionette).until(lambda mn: mn.get_url() == "about:addons",
+                                    message="'about:addons' hasn't been loaded")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
+    @skip("Bug 1334137 - Intermittent: Process killed because of hang in getCurrentUrl()")
+    @skip_if_mobile("Interacting with chrome elements not available for Fennec")
     def test_type_to_non_remote_tab(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
-        new_tab = self.open_tab(trigger='menu')
+        new_tab = self.open_tab(trigger=self.open_tab_with_link)
         self.marionette.switch_to_window(new_tab)
 
         with self.marionette.using_context("chrome"):
             urlbar = self.marionette.find_element(By.ID, 'urlbar')
             urlbar.send_keys(self.mod_key + 'a')
             urlbar.send_keys(self.mod_key + 'x')
-            urlbar.send_keys('about:preferences' + Keys.ENTER)
-        self.wait_for_condition(lambda mn: mn.get_url() == "about:preferences")
+            urlbar.send_keys('about:support' + Keys.ENTER)
+        Wait(self.marionette).until(lambda mn: mn.get_url() == "about:support",
+                                    message="'about:support' hasn't been loaded")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
     @skip_if_mobile("Interacting with chrome elements not available for Fennec")
     def test_type_to_remote_tab(self):
+        # Bug 1311041 - Prevent changing of window handle by forcing the test
+        # to be run in a new tab.
+        new_tab = self.open_tab(trigger=self.open_tab_with_link)
+        self.marionette.switch_to_window(new_tab)
+
         # about:blank keeps remoteness from remote_uri
         self.marionette.navigate("about:blank")
         with self.marionette.using_context("chrome"):
             urlbar = self.marionette.find_element(By.ID, 'urlbar')
+            urlbar.send_keys(self.mod_key + 'a')
+            urlbar.send_keys(self.mod_key + 'x')
             urlbar.send_keys(self.remote_uri + Keys.ENTER)
 
-        self.wait_for_condition(lambda mn: mn.get_url() == self.remote_uri)
+        Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri,
+                                    message="'{}' hasn't been loaded".format(self.remote_uri))
 
-    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
+    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_hang(self):
-        # Open a new tab and close the first one
-        new_tab = self.open_tab(trigger="menu")
+        # Bug 1311041 - Prevent changing of window handle by forcing the test
+        # to be run in a new tab.
+        new_tab = self.open_tab(trigger=self.open_tab_with_link)
 
+        # Close the start tab
         self.marionette.close()
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate(self.remote_uri)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
@@ -0,0 +1,181 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette_driver import By, Wait
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(TestWindowHandles, self).setUp()
+
+        self.empty_page = self.marionette.absolute_url("empty.html")
+        self.test_page = self.marionette.absolute_url("windowHandles.html")
+        self.marionette.navigate(self.test_page)
+
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.close_all_windows()
+        self.close_all_tabs()
+
+        super(TestWindowHandles, self).tearDown()
+
+    def test_chrome_window_handles_with_scopes(self):
+        # Open a browser and a non-browser (about window) chrome window
+        self.open_window(
+            trigger=lambda: self.marionette.execute_script("window.open();"))
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+        self.open_window(
+            trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+        chrome_window_handles_in_chrome_scope = self.marionette.chrome_window_handles
+        window_handles_in_chrome_scope = self.marionette.window_handles
+
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.chrome_window_handles,
+                             chrome_window_handles_in_chrome_scope)
+            self.assertEqual(self.marionette.window_handles,
+                             window_handles_in_chrome_scope)
+
+    def test_chrome_window_handles_after_opening_new_window(self):
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                link = self.marionette.find_element(By.ID, "new-window")
+                link.click()
+
+        # We open a new window but are actually interested in the new tab
+        new_win = self.open_window(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+        # Check that the new tab has the correct page loaded
+        self.marionette.switch_to_window(new_win)
+        self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+        # Close the opened window and carry on in our original tab.
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+
+        self.marionette.switch_to_window(self.start_window)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.test_page)
+
+    def test_window_handles_after_opening_new_tab(self):
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                link = self.marionette.find_element(By.ID, "new-tab")
+                link.click()
+
+        new_tab = self.open_tab(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.test_page)
+
+        self.marionette.switch_to_window(new_tab)
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+    def test_window_handles_after_opening_new_window(self):
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                link = self.marionette.find_element(By.ID, "new-window")
+                link.click()
+
+        # We open a new window but are actually interested in the new tab
+        new_tab = self.open_tab(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+        # Check that the new tab has the correct page loaded
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+        # Close the opened window and carry on in our original tab.
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.test_page)
+
+    def test_window_handles_after_closing_original_tab(self):
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                link = self.marionette.find_element(By.ID, "new-tab")
+                link.click()
+
+        new_tab = self.open_tab(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+    def test_window_handles_no_switch(self):
+        """Regression test for bug 1294456.
+        This test is testing the case where Marionette attempts to send a
+        command to a window handle when the browser has opened and selected
+        a new tab. Before bug 1294456 landed, the Marionette driver was getting
+        confused about which window handle the client cared about, and assumed
+        it was the window handle for the newly opened and selected tab.
+
+        This caused Marionette to think that the browser needed to do a remoteness
+        flip in the e10s case, since the tab opened by menu_newNavigatorTab is
+        about:newtab (which is currently non-remote). This meant that commands
+        sent to what should have been the original window handle would be
+        queued and never sent, since the remoteness flip in the new tab was
+        never going to happen.
+        """
+        def open_with_menu():
+            menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab')
+            menu_new_tab.click()
+
+        new_tab = self.open_tab(trigger=open_with_menu)
+
+        # We still have the default tab set as our window handle. This
+        # get_url command should be sent immediately, and not be forever-queued.
+        with self.marionette.using_context("content"):
+            self.assertEqual(self.marionette.get_url(), self.test_page)
+
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
rename from testing/marionette/harness/marionette_harness/tests/unit/test_window_handles.py
rename to testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
@@ -1,183 +1,85 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from marionette_driver import By, Wait
-from marionette_driver.keys import Keys
+from marionette_driver import By
 
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestWindowHandles, self).setUp()
 
+        self.empty_page = self.marionette.absolute_url("empty.html")
         self.test_page = self.marionette.absolute_url("windowHandles.html")
         self.marionette.navigate(self.test_page)
 
     def tearDown(self):
-        self.close_all_windows()
         self.close_all_tabs()
 
         super(TestWindowHandles, self).tearDown()
 
-    def test_new_tab_window_handles(self):
-        keys = []
-        if self.marionette.session_capabilities['platformName'] == 'darwin':
-            keys.append(Keys.META)
-        else:
-            keys.append(Keys.CONTROL)
-        keys.append('t')
-
-        def open_with_shortcut():
-            with self.marionette.using_context("chrome"):
-                main_win = self.marionette.find_element(By.ID, "main-window")
-                main_win.send_keys(*keys)
-
-        new_tab = self.open_tab(trigger=open_with_shortcut)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.marionette.switch_to_window(new_tab)
-        self.assertEqual(self.marionette.get_url(), "about:newtab")
-
-        self.marionette.close()
-        self.marionette.switch_to_window(self.start_tab)
-
-    def test_new_tab_window_handles_no_switch(self):
-        """Regression test for bug 1294456.
-        This test is testing the case where Marionette attempts to send a
-        command to a window handle when the browser has opened and selected
-        a new tab. Before bug 1294456 landed, the Marionette driver was getting
-        confused about which window handle the client cared about, and assumed
-        it was the window handle for the newly opened and selected tab.
-
-        This caused Marionette to think that the browser needed to do a remoteness
-        flip in the e10s case, since the tab opened by menu_newNavigatorTab is
-        about:newtab (which is currently non-remote). This meant that commands
-        sent to what should have been the original window handle would be
-        queued and never sent, since the remoteness flip in the new tab was
-        never going to happen.
-        """
-        def open_with_menu():
-            with self.marionette.using_context("chrome"):
-                menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab')
-                menu_new_tab.click()
-
-        new_tab = self.open_tab(trigger=open_with_menu)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-
-        # We still have the default tab set as our window handle. This
-        # get_url command should be sent immediately, and not be forever-queued.
-        self.assertEqual(self.marionette.get_url(), self.test_page)
-
-        self.marionette.switch_to_window(new_tab)
-        self.marionette.close()
-        self.marionette.switch_to_window(self.start_tab)
-
-    def test_link_opened_tab_window_handles(self):
+    def test_window_handles_after_opening_new_tab(self):
         def open_with_link():
             link = self.marionette.find_element(By.ID, "new-tab")
             link.click()
 
         new_tab = self.open_tab(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
         self.marionette.switch_to_window(new_tab)
-        self.assertEqual(self.marionette.get_url(), "about:blank")
-        self.assertEqual(self.marionette.title, "")
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertEqual(self.marionette.get_url(), self.test_page)
+
+        self.marionette.switch_to_window(new_tab)
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
         self.marionette.switch_to_window(self.start_tab)
-
-        self.assertEqual(self.marionette.get_url(), self.test_page)
-        self.assertEqual(self.marionette.title, "Marionette New Tab Link")
-
-        self.marionette.close()
-        self.marionette.switch_to_window(new_tab)
-        self.assertEqual(self.marionette.get_url(), "about:blank")
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-    def test_chrome_windows(self):
-        # We open a chrome window but are actually interested in the new tab.
-        new_window = self.open_window(
-            trigger=lambda: self.marionette.find_element(By.ID, "new-window").click())
-        with self.marionette.using_context("chrome"):
-            self.marionette.switch_to_window(new_window)
+    def test_window_handles_after_opening_new_window(self):
+        def open_with_link():
+            link = self.marionette.find_element(By.ID, "new-window")
+            link.click()
 
-        # Check that the new tab is available and wait until it has been loaded.
+        # We open a new window but are actually interested in the new tab
+        new_tab = self.open_tab(trigger=open_with_link)
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
-        new_tab = self.marionette.current_window_handle
-        Wait(self.marionette).until(lambda _: self.marionette.get_url() == self.test_page,
-                                    message="Test page hasn't been loaded for newly opened tab")
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-        link_new_tab = self.marionette.find_element(By.ID, "new-tab")
-        for i in range(3):
-            self.open_tab(trigger=lambda: link_new_tab.click())
-            self.marionette.switch_to_window(new_tab)
-            # No more chrome windows should be opened
-            self.assertEqual(len(self.marionette.chrome_window_handles),
-                             len(self.start_windows) + 1)
-
-        self.marionette.close_chrome_window()
-        self.marionette.switch_to_window(self.start_window)
+        # Check that the new tab has the correct page loaded
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        self.assertEqual(self.marionette.get_url(), self.empty_page)
 
-    def test_chrome_window_handles_with_scopes(self):
-        # Ensure that we work in chrome scope so we don't have any limitations
-        with self.marionette.using_context("chrome"):
-            # Open a browser and a non-browser (about window) chrome window
-            self.open_window(
-                trigger=lambda: self.marionette.execute_script("window.open();"))
-            self.open_window(
-                trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
+        # Close the opened window and carry on in our original tab.
+        self.marionette.close()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
-            handles_in_chrome_scope = self.marionette.chrome_window_handles
-            with self.marionette.using_context("content"):
-                self.assertEqual(self.marionette.chrome_window_handles,
-                                 handles_in_chrome_scope)
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertEqual(self.marionette.get_url(), self.test_page)
 
-    def test_tab_and_window_handles(self):
-        window_open_page = self.marionette.absolute_url("test_windows.html")
-        results_page = self.marionette.absolute_url("resultPage.html")
-
-        # Open a new tab and switch to it.
-        def open_tab_with_link():
+    def test_window_handles_after_closing_original_tab(self):
+        def open_with_link():
             link = self.marionette.find_element(By.ID, "new-tab")
             link.click()
 
-        second_tab = self.open_tab(trigger=open_tab_with_link)
-        self.assertEqual(len(self.marionette.chrome_window_handles), 1)
-        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
-
-        self.marionette.switch_to_window(second_tab)
-        self.assertEqual(self.marionette.get_url(), "about:blank")
-
-        # Open a new window from the new tab and only care about the second new tab
-        def open_window_with_link():
-            link = self.marionette.find_element(By.LINK_TEXT, "Open new window")
-            link.click()
-
-        # We open a new window but are actually interested in the new tab
-        self.marionette.navigate(window_open_page)
-        third_tab = self.open_tab(trigger=open_window_with_link)
-        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+        new_tab = self.open_tab(trigger=open_with_link)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertNotEqual(self.marionette.current_window_handle, new_tab)
 
-        # Check that the new tab has the correct page loaded
-        self.marionette.switch_to_window(third_tab)
-        self.assertEqual(self.marionette.get_url(), results_page)
-
-        self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
-
-        # Return to our original tab and close it.
-        self.marionette.switch_to_window(self.start_tab)
         self.marionette.close()
-        self.assertEquals(len(self.marionette.window_handles), 2)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
-        # Close the opened window and carry on in our second tab.
-        self.marionette.switch_to_window(third_tab)
-        self.marionette.close()
-        self.assertEqual(len(self.marionette.window_handles), 1)
-
-        self.marionette.switch_to_window(second_tab)
-        self.assertEqual(self.marionette.get_url(), results_page)
-        self.marionette.navigate("about:blank")
-
-        self.assertEqual(len(self.marionette.chrome_window_handles), 1)
-        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+        self.assertEqual(self.marionette.get_url(), self.empty_page)
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -70,17 +70,18 @@ skip-if = appname == 'fennec'
 skip-if = appname == 'fennec'
 [test_window_management.py]
 skip-if = appname == 'fennec'
 [test_window_close_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_close_content.py]
 [test_window_position.py]
 skip-if = appname == 'fennec'
-[test_window_handles.py]
+[test_window_handles_content.py]
+[test_window_handles_chrome.py]
 skip-if = appname == 'fennec'
 
 [test_screenshot.py]
 [test_cookies.py]
 [test_window_title.py]
 [test_window_title_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_type.py]
--- a/testing/marionette/harness/marionette_harness/www/windowHandles.html
+++ b/testing/marionette/harness/marionette_harness/www/windowHandles.html
@@ -3,12 +3,12 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!DOCTYPE html>
 <html>
 <head>
 <title>Marionette New Tab Link</title>
 </head>
 <body>
-  <a href="about:blank" id="new-tab" target="_blank">Click me!</a>
-  <a href="about:blank" id="new-window" onClick='javascript:window.open("windowHandles.html", null, "location=1,toolbar=1");'>Click me!</a>
+  <a href="empty.html" id="new-tab" target="_blank">Click me!</a>
+  <a href="" id="new-window" onClick='javascript:window.open("empty.html", null, "location=1,toolbar=1");'>Click me!</a>
 </body>
 </html>