Bug 1322383 - Add missing checks for valid chrome window. draft
authorHenrik Skupin <mail@hskupin.info>
Mon, 20 Mar 2017 15:13:32 +0100
changeset 502992 8aa93c5827ee321b03e94778c9d605be332a01cb
parent 502991 b06f54ca5a3cf372b7ab86636bdb8b3978418a4b
child 502993 76d829952d2954cde9df91e19d187fd509d0782a
push id50441
push userbmo:hskupin@gmail.com
push dateWed, 22 Mar 2017 15:51:36 +0000
bugs1322383
milestone55.0a1
Bug 1322383 - Add missing checks for valid chrome window. Update necessary commands in driver.js to conform with webdriver spec by checking for a valid chrome window, before executing the actual command. MozReview-Commit-ID: Ad67SPx8vBx
testing/marionette/assert.js
testing/marionette/driver.js
testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_management.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_status_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_status_content.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_type_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
--- a/testing/marionette/assert.js
+++ b/testing/marionette/assert.js
@@ -113,16 +113,35 @@ assert.content = function (context, msg 
  *     If the current browser is not B2G or Fennec.
  */
 assert.mobile = function (msg = "") {
   msg = msg || "Only supported in Fennec or B2G";
   assert.that(() => isFennec() || isB2G(), msg, UnsupportedOperationError)();
 };
 
 /**
+ * Asserts that |win| is open.
+ *
+ * @param {ChromeWindow} win
+ *     Chrome window to test.
+ * @param {string=} msg
+ *     Custom error message.
+ *
+ * @return {ChromeWindow}
+ *     |win| is returned unaltered.
+ *
+ * @throws {NoSuchWindowError}
+ *     If |win| has been closed.
+ */
+assert.window = function (win, msg = "") {
+  msg = msg || "Unable to locate window";
+  return assert.that(w => w && w.document.defaultView, msg, NoSuchWindowError)(win);
+}
+
+/**
  * Asserts that |obj| is defined.
  *
  * @param {?} obj
  *     Value to test.
  * @param {string=} msg
  *     Custom error message.
  *
  * @return {?}
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -300,32 +300,44 @@ GeckoDriver.prototype.sendTargettedAsync
         throw new WebDriverError(e);
     }
   }
 };
 
 /**
  * Gets the current active window.
  *
+ * @param {Context=} forcedContext
+ *     Optional name of the context to use for the checks.
+ *     Defaults to the current context.
+ *
  * @return {nsIDOMWindow}
  */
-GeckoDriver.prototype.getCurrentWindow = function() {
-  let typ = null;
+GeckoDriver.prototype.getCurrentWindow = function (forcedContext = undefined) {
+  let context = typeof forcedContext == "undefined" ? this.context : forcedContext;
+  let win = null;
+
   if (this.curFrame === null) {
     if (this.curBrowser === null) {
-      if (this.context == Context.CONTENT) {
-        typ = "navigator:browser";
+      let typ = (context === Context.CONTENT) ? "navigator:browser" : null;
+      win = Services.wm.getMostRecentWindow(typ);
+    } else {
+      if (context === Context.CHROME) {
+        win = this.curBrowser.window;
+      } else {
+        if (this.curBrowser.tab && browser.getBrowserForTab(this.curBrowser.tab)) {
+          win = this.curBrowser.window;
+        }
       }
-      return Services.wm.getMostRecentWindow(typ);
-    } else {
-      return this.curBrowser.window;
     }
   } else {
-    return this.curFrame;
+    win = this.curFrame;
   }
+
+  return win;
 };
 
 GeckoDriver.prototype.addFrameCloseListener = function (action) {
   let win = this.getCurrentWindow();
   this.mozBrowserClose = e => {
     if (e.target.id == this.oopFrameId) {
       win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
       this.switchToGlobalMessageManager();
@@ -742,16 +754,18 @@ GeckoDriver.prototype.getContext = funct
  *
  * @throws ScriptTimeoutError
  *     If the script was interrupted due to reaching the {@code
  *     scriptTimeout} or default timeout.
  * @throws JavaScriptError
  *     If an Error was thrown whilst evaluating the script.
  */
 GeckoDriver.prototype.executeScript = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let {script, args, scriptTimeout} = cmd.parameters;
   scriptTimeout = scriptTimeout || this.timeouts.script;
 
   let opts = {
     sandboxName: cmd.parameters.sandbox,
     newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
         cmd.parameters.newSandbox,
     filename: cmd.parameters.filename,
@@ -815,16 +829,18 @@ GeckoDriver.prototype.executeScript = fu
  *
  * @throws ScriptTimeoutError
  *     If the script was interrupted due to reaching the {@code
  *     scriptTimeout} or default timeout.
  * @throws JavaScriptError
  *     If an Error was thrown whilst evaluating the script.
  */
 GeckoDriver.prototype.executeAsyncScript = function* (cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let {script, args, scriptTimeout} = cmd.parameters;
   scriptTimeout = scriptTimeout || this.timeouts.script;
 
   let opts = {
     sandboxName: cmd.parameters.sandbox,
     newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
         cmd.parameters.newSandbox,
     filename: cmd.parameters.filename,
@@ -865,28 +881,29 @@ GeckoDriver.prototype.execute_ = functio
 
 /**
  * Execute pure JavaScript.  Used to execute simpletest harness tests,
  * which are like mochitests only injected using Marionette.
  *
  * Scripts are expected to call the {@code finish} global when done.
  */
 GeckoDriver.prototype.executeJSScript = function* (cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {script, args, scriptTimeout} = cmd.parameters;
   scriptTimeout = scriptTimeout || this.timeouts.script;
 
   let opts = {
     filename: cmd.parameters.filename,
     line: cmd.parameters.line,
     async: cmd.parameters.async,
   };
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let wargs = element.fromJson(args, this.curBrowser.seenEls, win);
       let harness = new simpletest.Harness(
           win,
           Context.CHROME,
           this.marionetteLog,
           scriptTimeout,
           function() {},
           this.testName);
@@ -925,16 +942,17 @@ GeckoDriver.prototype.executeJSScript = 
  * the supplied URL and wait until document.readyState equals "complete"
  * or the page timeout duration has elapsed.
  *
  * @param {string} url
  *     URL to navigate to.
  */
 GeckoDriver.prototype.get = function*(cmd, resp) {
   assert.content(this.context);
+  assert.window(this.getCurrentWindow());
 
   let url = cmd.parameters.url;
 
   let get = this.listener.get({url: url, pageTimeout: this.timeouts.pageLoad});
 
   // If a remoteness update interrupts our page load, this will never return
   // We need to re-issue this request to correctly poll for readyState and
   // send errors.
@@ -960,67 +978,72 @@ GeckoDriver.prototype.get = function*(cm
  * On Desktop this returns a string representation of the URL of the
  * current top level browsing context.  This is equivalent to
  * document.location.href.
  *
  * When in the context of the chrome, this returns the canonical URL
  * of the current resource.
  */
 GeckoDriver.prototype.getCurrentUrl = function (cmd) {
+  let win = assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
-      return this.getCurrentWindow().location.href;
+      return win.location.href;
 
     case Context.CONTENT:
-      let isB2G = this.appName == "B2G";
-      return this.listener.getCurrentUrl(isB2G);
+      return this.listener.getCurrentUrl();
   }
 };
 
 /** Gets the current title of the window. */
 GeckoDriver.prototype.getTitle = function* (cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       resp.body.value = win.document.documentElement.getAttribute("title");
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getTitle();
       break;
   }
 };
 
 /** Gets the current type of the window. */
 GeckoDriver.prototype.getWindowType = function (cmd, resp) {
-  let win = this.getCurrentWindow();
+  let win = assert.window(this.getCurrentWindow());
+
   resp.body.value = win.document.documentElement.getAttribute("windowtype");
 };
 
 /** Gets the page source of the content document. */
 GeckoDriver.prototype.getPageSource = function* (cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let s = new win.XMLSerializer();
       resp.body.value = s.serializeToString(win.document);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getPageSource();
       break;
   }
 };
 
 /**
  * Cause the browser to traverse one step backward in the joint history
  * of the current browsing context.
  */
 GeckoDriver.prototype.goBack = function* (cmd, resp) {
   assert.content(this.context);
+  assert.window(this.getCurrentWindow());
 
   if (!this.curBrowser.tab) {
     // Navigation does not work for non-browser windows
     return;
   }
 
   let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
   if (!contentBrowser.webNavigation.canGoBack) {
@@ -1052,16 +1075,17 @@ GeckoDriver.prototype.goBack = function*
 };
 
 /**
  * Cause the browser to traverse one step forward in the joint history
  * of the current browsing context.
  */
 GeckoDriver.prototype.goForward = function* (cmd, resp) {
   assert.content(this.context);
+  assert.window(this.getCurrentWindow());
 
   if (!this.curBrowser.tab) {
     // Navigation does not work for non-browser windows
     return;
   }
 
   let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
   if (!contentBrowser.webNavigation.canGoForward) {
@@ -1090,16 +1114,17 @@ GeckoDriver.prototype.goForward = functi
   });
 
   yield goForward;
 };
 
 /** Refresh the page. */
 GeckoDriver.prototype.refresh = function*(cmd, resp) {
   assert.content(this.context);
+  assert.window(this.getCurrentWindow());
 
   yield this.listener.refresh();
 };
 
 /**
  * Forces an update for the given browser's id.
  */
 GeckoDriver.prototype.updateIdForBrowser = function (browser, newId) {
@@ -1136,16 +1161,18 @@ GeckoDriver.prototype.getIdForBrowser = 
  * Return an opaque server-assigned identifier to this window that
  * uniquely identifies it within this Marionette instance.  This can
  * be used to switch to this window at a later point.
  *
  * @return {string}
  *     Unique window handle.
  */
 GeckoDriver.prototype.getWindowHandle = function (cmd, resp) {
+  assert.window(this.getCurrentWindow(Context.CONTENT));
+
   // curFrameId always holds the current tab.
   if (this.curBrowser.curFrameId) {
     resp.body.value = this.curBrowser.curFrameId;
     return;
   }
 
   for (let i in this.browsers) {
     if (this.curBrowser == this.browsers[i]) {
@@ -1177,16 +1204,18 @@ GeckoDriver.prototype.getWindowHandles =
  * Return an opaque server-assigned identifier to this window that
  * uniquely identifies it within this Marionette instance.  This can
  * be used to switch to this window at a later point.
  *
  * @return {string}
  *     Unique window handle.
  */
 GeckoDriver.prototype.getChromeWindowHandle = function (cmd, resp) {
+  assert.window(this.getCurrentWindow(Context.CHROME));
+
   for (let i in this.browsers) {
     if (this.curBrowser == this.browsers[i]) {
       resp.body.value = i;
       return;
     }
   }
 };
 
@@ -1203,17 +1232,18 @@ GeckoDriver.prototype.getChromeWindowHan
 
 /**
  * Get the current window position.
  *
  * @return {Object.<string, number>}
  *     Object with |x| and |y| coordinates.
  */
 GeckoDriver.prototype.getWindowPosition = function (cmd, resp) {
-  let win = this.getCurrentWindow();
+  let win = assert.window(this.getCurrentWindow());
+
   return {
     x: win.screenX,
     y: win.screenY,
   };
 };
 
 /**
  * Set the window position of the browser on the OS Window Manager
@@ -1329,16 +1359,18 @@ GeckoDriver.prototype.switchToWindow = f
       }
     }
   } else {
     throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
   }
 };
 
 GeckoDriver.prototype.getActiveFrame = function (cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       // no frame means top-level
       resp.body.value = null;
       if (this.curFrame) {
         let elRef = this.curBrowser.seenEls
             .add(this.curFrame.frameElement);
         let el = element.makeWebElement(elRef);
@@ -1352,29 +1384,33 @@ GeckoDriver.prototype.getActiveFrame = f
         let el = element.makeWebElement(this.currentFrameElement);
         resp.body.value = el;
       }
       break;
   }
 };
 
 GeckoDriver.prototype.switchToParentFrame = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let res = yield this.listener.switchToParentFrame();
 };
 
 /**
  * Switch to a given frame within the current window.
  *
  * @param {Object} element
  *     A web element reference to the element to switch to.
  * @param {(string|number)} id
  *     If element is not defined, then this holds either the id, name,
  *     or index of the frame to switch to.
  */
 GeckoDriver.prototype.switchToFrame = function* (cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let {id, element, focus} = cmd.parameters;
 
   const otherErrorsExpr = /about:.+(error)|(blocked)\?/;
   const checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
   let curWindow = this.getCurrentWindow();
 
   let checkLoad = function() {
@@ -1556,16 +1592,18 @@ GeckoDriver.prototype.setTimeouts = func
 
   // merge with existing timeouts
   let merged = Object.assign(this.timeouts.toJSON(), json);
   this.timeouts = session.Timeouts.fromJSON(merged);
 };
 
 /** Single tap. */
 GeckoDriver.prototype.singleTap = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let {id, x, y} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'singleTap' is not yet available in chrome context");
 
     case Context.CONTENT:
@@ -1580,30 +1618,34 @@ GeckoDriver.prototype.singleTap = functi
  *
  * @param {Array.<?>} actions
  *     Array of objects that each represent an action sequence.
  *
  * @throws {UnsupportedOperationError}
  *     If the command is made in chrome context.
  */
 GeckoDriver.prototype.performActions = function(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'performActions' is not yet available in chrome context");
 
     case Context.CONTENT:
       return this.listener.performActions({"actions": cmd.parameters.actions});
   }
 };
 
 /**
  * Release all the keys and pointer buttons that are currently depressed.
  */
 GeckoDriver.prototype.releaseActions = function(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'releaseActions' is not yet available in chrome context");
 
     case Context.CONTENT:
         return this.listener.releaseActions();
   }
@@ -1615,25 +1657,26 @@ GeckoDriver.prototype.releaseActions = f
  * @param {Object} value
  *     A nested array where the inner array represents each event,
  *     and the outer array represents a collection of events.
  *
  * @return {number}
  *     Last touch ID.
  */
 GeckoDriver.prototype.actionChain = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {chain, nextId} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       // be conservative until this has a use case and is established
       // to work as expected in Fennec
-      assert.firefox()
-
-      let win = this.getCurrentWindow();
+      assert.firefox();
+
       resp.body.value = yield this.legacyactions.dispatchActions(
           chain, nextId, {frame: win}, this.curBrowser.seenEls);
       break;
 
     case Context.CONTENT:
       this.addFrameCloseListener("action chain");
       resp.body.value = yield this.listener.actionChain(chain, nextId);
       break;
@@ -1644,16 +1687,18 @@ GeckoDriver.prototype.actionChain = func
  * A multi-action chain.
  *
  * @param {Object} value
  *     A nested array where the inner array represents eache vent,
  *     the middle array represents a collection of events for each
  *     finger, and the outer array represents all fingers.
  */
 GeckoDriver.prototype.multiAction = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'multiAction' is not yet available in chrome context");
 
     case Context.CONTENT:
       this.addFrameCloseListener("multi action chain");
       yield this.listener.multiAction(cmd.parameters.value, cmd.parameters.max_length);
@@ -1665,31 +1710,33 @@ GeckoDriver.prototype.multiAction = func
  * Find an element using the indicated search strategy.
  *
  * @param {string} using
  *     Indicates which search method to use.
  * @param {string} value
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElement = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let strategy = cmd.parameters.using;
   let expr = cmd.parameters.value;
   let opts = {
     startNode: cmd.parameters.element,
     timeout: this.timeouts.implicit,
     all: false,
   };
 
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
-      let container = {frame: this.getCurrentWindow()};
+      let container = {frame: win};
       if (opts.startNode) {
         opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
       }
       let el = yield element.find(container, strategy, expr, opts);
       let elRef = this.curBrowser.seenEls.add(el);
       let webEl = element.makeWebElement(elRef);
 
       resp.body.value = webEl;
@@ -1708,31 +1755,33 @@ GeckoDriver.prototype.findElement = func
  * Find elements using the indicated search strategy.
  *
  * @param {string} using
  *     Indicates which search method to use.
  * @param {string} value
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElements = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let strategy = cmd.parameters.using;
   let expr = cmd.parameters.value;
   let opts = {
     startNode: cmd.parameters.element,
     timeout: this.timeouts.implicit,
     all: true,
   };
 
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
-      let container = {frame: this.getCurrentWindow()};
+      let container = {frame: win};
       if (opts.startNode) {
         opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
       }
       let els = yield element.find(container, strategy, expr, opts);
 
       let elRefs = this.curBrowser.seenEls.addAll(els);
       let webEls = elRefs.map(element.makeWebElement);
       resp.body = webEls;
@@ -1744,16 +1793,18 @@ GeckoDriver.prototype.findElements = fun
           cmd.parameters.value,
           opts);
       break;
   }
 };
 
 /** Return the active element on the page. */
 GeckoDriver.prototype.getActiveElement = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'getActiveElement' is not yet available in chrome context");
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getActiveElement();
       break;
@@ -1762,21 +1813,22 @@ GeckoDriver.prototype.getActiveElement =
 
 /**
  * Send click event to element.
  *
  * @param {string} id
  *     Reference ID to the element that will be clicked.
  */
 GeckoDriver.prototype.clickElement = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       yield interaction.clickElement(el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       // We need to protect against the click causing an OOP frame to close.
       // This fires the mozbrowserclose event when it closes so we need to
       // listen for it and then just send an error back. The person making the
@@ -1794,21 +1846,22 @@ GeckoDriver.prototype.clickElement = fun
  *     Web element reference ID to the element that will be inspected.
  * @param {string} name
  *     Name of the attribute which value to retrieve.
  *
  * @return {string}
  *     Value of the attribute.
  */
 GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
 
       resp.body.value = el.getAttribute(name);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementAttribute(id, name);
       break;
@@ -1822,21 +1875,22 @@ GeckoDriver.prototype.getElementAttribut
  *     Web element reference ID to the element that will be inspected.
  * @param {string} name
  *     Name of the property which value to retrieve.
  *
  * @return {string}
  *     Value of the property.
  */
 GeckoDriver.prototype.getElementProperty = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       resp.body.value = el[name];
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementProperty(id, name);
       break;
   }
@@ -1845,22 +1899,23 @@ GeckoDriver.prototype.getElementProperty
 /**
  * Get the text of an element, if any.  Includes the text of all child
  * elements.
  *
  * @param {string} id
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.getElementText = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // for chrome, we look at text nodes, and any node with a "label" field
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       let lines = [];
       this.getVisibleText(el, lines);
       resp.body.value = lines.join("\n");
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementText(id);
@@ -1870,21 +1925,22 @@ GeckoDriver.prototype.getElementText = f
 
 /**
  * Get the tag name of the element.
  *
  * @param {string} id
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.getElementTagName = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       resp.body.value = el.tagName.toLowerCase();
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementTagName(id);
       break;
   }
@@ -1892,21 +1948,22 @@ GeckoDriver.prototype.getElementTagName 
 
 /**
  * Check if element is displayed.
  *
  * @param {string} id
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       resp.body.value = yield interaction.isElementDisplayed(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementDisplayed(id);
       break;
@@ -1917,21 +1974,22 @@ GeckoDriver.prototype.isElementDisplayed
  * Return the property of the computed style of an element.
  *
  * @param {string} id
  *     Reference ID to the element that will be checked.
  * @param {string} propertyName
  *     CSS rule that is being requested.
  */
 GeckoDriver.prototype.getElementValueOfCssProperty = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {id, propertyName: prop} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       let sty = win.document.defaultView.getComputedStyle(el);
       resp.body.value = sty.getPropertyValue(prop);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementValueOfCssProperty(id, prop);
       break;
@@ -1940,22 +1998,23 @@ GeckoDriver.prototype.getElementValueOfC
 
 /**
  * Check if element is enabled.
  *
  * @param {string} id
  *     Reference ID to the element that will be checked.
  */
 GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       resp.body.value = yield interaction.isElementEnabled(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementEnabled(id);
       break;
@@ -1964,39 +2023,41 @@ GeckoDriver.prototype.isElementEnabled =
 
 /**
  * Check if element is selected.
  *
  * @param {string} id
  *     Reference ID to the element that will be checked.
  */
 GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       resp.body.value = yield interaction.isElementSelected(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementSelected(id);
       break;
   }
 };
 
 GeckoDriver.prototype.getElementRect = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       let rect = el.getBoundingClientRect();
       resp.body = {
         x: rect.x + win.pageXOffset,
         y: rect.y + win.pageYOffset,
         width: rect.width,
         height: rect.height
       };
@@ -2012,53 +2073,57 @@ GeckoDriver.prototype.getElementRect = f
  * Send key presses to element after focusing on it.
  *
  * @param {string} id
  *     Reference ID to the element that will be checked.
  * @param {string} value
  *     Value to send to the element.
  */
 GeckoDriver.prototype.sendKeysToElement = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {id, value} = cmd.parameters;
   assert.defined(value, `Expected character sequence: ${value}`);
 
   switch (this.context) {
     case Context.CHROME:
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       yield interaction.sendKeysToElement(
           el, value, true, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       yield this.listener.sendKeysToElement(id, value);
       break;
   }
 };
 
 /** Sets the test name.  The test name is used for logging purposes. */
 GeckoDriver.prototype.setTestName = function*(cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   let val = cmd.parameters.value;
   this.testName = val;
   yield this.listener.setTestName({value: val});
 };
 
 /**
  * Clear the text of an element.
  *
  * @param {string} id
  *     Reference ID to the element that will be cleared.
  */
 GeckoDriver.prototype.clearElement = function*(cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // the selenium atom doesn't work here
-      let win = this.getCurrentWindow();
       let el = this.curBrowser.seenEls.get(id, {frame: win});
       if (el.nodeName == "textbox") {
         el.value = "";
       } else if (el.nodeName == "checkbox") {
         el.checked = false;
       }
       break;
 
@@ -2070,25 +2135,27 @@ GeckoDriver.prototype.clearElement = fun
 
 /**
  * Switch to shadow root of the given host element.
  *
  * @param {string} id element id.
  */
 GeckoDriver.prototype.switchToShadowRoot = function*(cmd, resp) {
   assert.content(this.context)
+  assert.window(this.getCurrentWindow());
 
   let id;
   if (cmd.parameters) { id = cmd.parameters.id; }
   yield this.listener.switchToShadowRoot(id);
 };
 
 /** Add a cookie to the document. */
 GeckoDriver.prototype.addCookie = function*(cmd, resp) {
   assert.content(this.context)
+  assert.window(this.getCurrentWindow());
 
   let cb = msg => {
     this.mm.removeMessageListener("Marionette:addCookie", cb);
     let cookie = msg.json;
     Services.cookies.add(
         cookie.domain,
         cookie.path,
         cookie.name,
@@ -2108,23 +2175,25 @@ GeckoDriver.prototype.addCookie = functi
 /**
  * Get all the cookies for the current domain.
  *
  * This is the equivalent of calling {@code document.cookie} and parsing
  * the result.
  */
 GeckoDriver.prototype.getCookies = function*(cmd, resp) {
   assert.content(this.context)
+  assert.window(this.getCurrentWindow());
 
   resp.body = yield this.listener.getCookies();
 };
 
 /** Delete all cookies that are visible to a document. */
 GeckoDriver.prototype.deleteAllCookies = function*(cmd, resp) {
   assert.content(this.context)
+  assert.window(this.getCurrentWindow());
 
   let cb = msg => {
     let cookie = msg.json;
     cookieManager.remove(
         cookie.host,
         cookie.name,
         cookie.path,
         false,
@@ -2135,16 +2204,17 @@ GeckoDriver.prototype.deleteAllCookies =
   this.mm.addMessageListener("Marionette:deleteCookie", cb);
   yield this.listener.deleteAllCookies();
   this.mm.removeMessageListener("Marionette:deleteCookie", cb);
 };
 
 /** Delete a cookie by name. */
 GeckoDriver.prototype.deleteCookie = function*(cmd, resp) {
   assert.content(this.context)
+  assert.window(this.getCurrentWindow());
 
   let cb = msg => {
     this.mm.removeMessageListener("Marionette:deleteCookie", cb);
     let cookie = msg.json;
     cookieManager.remove(
         cookie.host,
         cookie.name,
         cookie.path,
@@ -2164,19 +2234,21 @@ GeckoDriver.prototype.deleteCookie = fun
  * Otherwise the window itself will be closed. If it is the last window
  * currently open, the window will not be closed to prevent a shutdown of the
  * application. Instead the returned list of window handles is empty.
  *
  * @return {Array.<string>}
  *     Unique window handles of remaining windows.
  */
 GeckoDriver.prototype.close = function (cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   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.
     let tabbrowser = browser.getTabBrowser(win);
     if (tabbrowser) {
       nwins += tabbrowser.tabs.length;
     } else {
@@ -2205,21 +2277,21 @@ GeckoDriver.prototype.close = function (
  * closed to prevent a shutdown of the application. Instead the returned
  * list of chrome window handles is empty.
  *
  * @return {Array.<string>}
  *     Unique chrome window handles of remaining chrome windows.
  */
 GeckoDriver.prototype.closeChromeWindow = function (cmd, resp) {
   assert.firefox();
-
-  // Get the total number of windows
+  assert.window(this.getCurrentWindow(Context.CHROME));
+
   let nwins = 0;
+
   let winEn = Services.wm.getEnumerator(null);
-
   while (winEn.hasMoreElements()) {
     nwins++;
     winEn.getNext();
   }
 
   // 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.
@@ -2290,16 +2362,18 @@ GeckoDriver.prototype.deleteSession = fu
   cert.uninstallOverride();
 
   this.sessionId = null;
   this.capabilities = new session.Capabilities();
 };
 
 /** Returns the current status of the Application Cache. */
 GeckoDriver.prototype.getAppCacheStatus = function* (cmd, resp) {
+  assert.window(this.getCurrentWindow());
+
   switch (this.context) {
     case Context.CHROME:
       throw new UnsupportedOperationError(
           "Command 'getAppCacheStatus' is not yet available in chrome context");
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getAppCacheStatus();
       break;
@@ -2362,26 +2436,25 @@ GeckoDriver.prototype.clearImportedScrip
  *     scroll to the element.
  *
  * @return {string}
  *     If {@code hash} is false, PNG image encoded as base64 encoded string. If
  *     'hash' is True, hex digest of the SHA-256 hash of the base64 encoded
  *     string.
  */
 GeckoDriver.prototype.takeScreenshot = function (cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
+
   let {id, highlights, full, hash, scroll} = cmd.parameters;
   highlights = highlights || [];
   let format = hash ? capture.Format.Hash : capture.Format.Base64;
 
   switch (this.context) {
     case Context.CHROME:
-      let container = {frame: this.getCurrentWindow().document.defaultView};
-      if (!container.frame) {
-        throw new NoSuchWindowError("Unable to locate window");
-      }
+      let container = {frame: win.document.defaultView};
 
       let highlightEls = highlights.map(
           ref => this.curBrowser.seenEls.get(ref, container));
 
       // viewport
       let canvas;
       if (!id && !full) {
         canvas = capture.viewport(container.frame, highlightEls);
@@ -2416,62 +2489,63 @@ GeckoDriver.prototype.takeScreenshot = f
  * Get the current browser orientation.
  *
  * Will return one of the valid primary orientation values
  * portrait-primary, landscape-primary, portrait-secondary, or
  * landscape-secondary.
  */
 GeckoDriver.prototype.getScreenOrientation = function (cmd, resp) {
   assert.fennec();
-
-  resp.body.value = this.getCurrentWindow().screen.mozOrientation;
+  let win = assert.window(this.getCurrentWindow());
+
+  resp.body.value = win.screen.mozOrientation;
 };
 
 /**
  * Set the current browser orientation.
  *
  * The supplied orientation should be given as one of the valid
  * orientation values.  If the orientation is unknown, an error will
  * be raised.
  *
  * Valid orientations are "portrait" and "landscape", which fall
  * back to "portrait-primary" and "landscape-primary" respectively,
  * and "portrait-secondary" as well as "landscape-secondary".
  */
 GeckoDriver.prototype.setScreenOrientation = function (cmd, resp) {
   assert.fennec();
+  let win = assert.window(this.getCurrentWindow());
 
   const ors = [
     "portrait", "landscape",
     "portrait-primary", "landscape-primary",
     "portrait-secondary", "landscape-secondary",
   ];
 
   let or = String(cmd.parameters.orientation);
   assert.string(or);
   let mozOr = or.toLowerCase();
   if (!ors.includes(mozOr)) {
     throw new InvalidArgumentError(`Unknown screen orientation: ${or}`);
   }
 
-  let win = this.getCurrentWindow();
   if (!win.screen.mozLockOrientation(mozOr)) {
     throw new WebDriverError(`Unable to set screen orientation: ${or}`);
   }
 };
 
 /**
  * Get the size of the browser window currently in focus.
  *
  * Will return the current browser window size in pixels. Refers to
  * window outerWidth and outerHeight values, which include scroll bars,
  * title bars, etc.
  */
 GeckoDriver.prototype.getWindowSize = function (cmd, resp) {
-  let win = this.getCurrentWindow();
+  let win = assert.window(this.getCurrentWindow());
   return {
     width: win.outerWidth,
     height: win.outerHeight,
   };
 };
 
 /**
  * Set the size of the browser window currently in focus.
@@ -2485,19 +2559,19 @@ GeckoDriver.prototype.getWindowSize = fu
  * @param {number} height
  *     Requested window outer height.
  *
  * @return {Map.<string, number>}
  *     New outerWidth/outerHeight dimensions.
  */
 GeckoDriver.prototype.setWindowSize = function* (cmd, resp) {
   assert.firefox()
+  let win = assert.window(this.getCurrentWindow());
 
   const {width, height} = cmd.parameters;
-  const win = this.getCurrentWindow();
 
   yield new Promise(resolve => {
     // When the DOM resize event claims that it fires _after_ the document
     // view has been resized, it is lying.
     //
     // Because resize events fire at a high rate, DOM modifications
     // such as updates to outerWidth/outerHeight are not guaranteed to
     // have processed.  To overcome this... abomination... of the web
@@ -2519,77 +2593,80 @@ GeckoDriver.prototype.setWindowSize = fu
 /**
  * Maximizes the user agent window as if the user pressed the maximise
  * button.
  *
  * Not Supported on B2G or Fennec.
  */
 GeckoDriver.prototype.maximizeWindow = function (cmd, resp) {
   assert.firefox()
-
-  let win = this.getCurrentWindow();
+  let win = assert.window(this.getCurrentWindow());
+
   win.maximize()
 };
 
 /**
  * Dismisses a currently displayed tab modal, or returns no such alert if
  * no modal is displayed.
  */
 GeckoDriver.prototype.dismissDialog = function (cmd, resp) {
+  assert.window(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   let {button0, button1} = this.dialog.ui;
   (button1 ? button1 : button0).click();
   this.dialog = null;
 };
 
 /**
  * Accepts a currently displayed tab modal, or returns no such alert if
  * no modal is displayed.
  */
 GeckoDriver.prototype.acceptDialog = function (cmd, resp) {
+  assert.window(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   let {button0} = this.dialog.ui;
   button0.click();
   this.dialog = null;
 };
 
 /**
  * Returns the message shown in a currently displayed modal, or returns a no such
  * alert error if no modal is currently displayed.
  */
 GeckoDriver.prototype.getTextFromDialog = function (cmd, resp) {
+  assert.window(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   let {infoBody} = this.dialog.ui;
   resp.body.value = infoBody.textContent;
 };
 
 /**
  * Sends keys to the input field of a currently displayed modal, or
  * returns a no such alert error if no modal is currently displayed. If
  * a tab modal is currently displayed but has no means for text input,
  * an element not visible error is returned.
  */
 GeckoDriver.prototype.sendKeysToDialog = function (cmd, resp) {
+  let win = assert.window(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   // see toolkit/components/prompts/content/commonDialog.js
   let {loginContainer, loginTextbox} = this.dialog.ui;
   if (loginContainer.hidden) {
     throw new ElementNotVisibleError("This prompt does not accept text input");
   }
 
-  let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
   event.sendKeysToElement(
       cmd.parameters.value,
       loginTextbox,
       {ignoreVisibility: true},
-      win);
+      this.dialog.window ? this.dialog.window : win);
 };
 
 GeckoDriver.prototype._checkIfAlertIsPresent = function() {
   if (!this.dialog || !this.dialog.ui) {
     throw new NoAlertOpenError(
         "No tab modal was open when attempting to get the dialog text");
   }
 };
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py
@@ -1,34 +1,39 @@
 # 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_harness import MarionetteTestCase
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
-class TestClickChrome(MarionetteTestCase):
+class TestClickChrome(WindowManagerMixin, MarionetteTestCase):
+
     def setUp(self):
-        MarionetteTestCase.setUp(self)
-        self.root_window = self.marionette.current_window_handle
+        super(TestClickChrome, self).setUp()
+
         self.marionette.set_context("chrome")
-        self.marionette.execute_script(
-            "window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen')")
-        self.marionette.switch_to_window("foo")
-        self.assertNotEqual(self.root_window, self.marionette.current_window_handle)
 
     def tearDown(self):
-        self.assertNotEqual(self.root_window, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close()")
-        self.marionette.switch_to_window(self.root_window)
-        MarionetteTestCase.tearDown(self)
+        self.close_all_windows()
+
+        super(TestClickChrome, self).tearDown()
 
     def test_click(self):
+
+        def open_with_js():
+            self.marionette.execute_script("""
+              window.open('chrome://marionette/content/test.xul',
+                          'foo', 'chrome,centerscreen'); """)
+
+        win = self.open_window(open_with_js)
+        self.marionette.switch_to_window(win)
+
         def checked():
             return self.marionette.execute_script(
                 "return arguments[0].checked",
                 script_args=[box])
 
         box = self.marionette.find_element(By.ID, "testBox")
         self.assertFalse(checked())
         box.click()
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py
@@ -1,85 +1,58 @@
 # 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_harness import MarionetteTestCase, skip
+from marionette_harness import MarionetteTestCase, skip, WindowManagerMixin
 
 
-class TestIsElementEnabledChrome(MarionetteTestCase):
+class TestElementState(WindowManagerMixin, MarionetteTestCase):
+
     def setUp(self):
-        MarionetteTestCase.setUp(self)
+        super(TestElementState, self).setUp()
+
         self.marionette.set_context("chrome")
-        self.win = self.marionette.current_window_handle
-        self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
-        self.marionette.switch_to_window('foo')
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
+
+        def open_window_with_js():
+            self.marionette.execute_script("""
+              window.open('chrome://marionette/content/test.xul',
+                          'foo', 'chrome,centerscreen');
+            """)
+
+        self.win = self.open_window(open_window_with_js)
+        self.marionette.switch_to_window(self.win)
 
     def tearDown(self):
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close();")
-        self.marionette.switch_to_window(self.win)
-        MarionetteTestCase.tearDown(self)
+        self.close_all_windows()
+
+        super(TestElementState, self).tearDown()
+
+    @skip("Switched off in bug 896043, and to be turned on in bug 896046")
+    def test_is_displayed(self):
+        l = self.marionette.find_element(By.ID, "textInput")
+        self.assertTrue(l.is_displayed())
+        self.marionette.execute_script("arguments[0].hidden = true;", [l])
+        self.assertFalse(l.is_displayed())
+        self.marionette.execute_script("arguments[0].hidden = false;", [l])
 
     def test_enabled(self):
         l = self.marionette.find_element(By.ID, "textInput")
         self.assertTrue(l.is_enabled())
         self.marionette.execute_script("arguments[0].disabled = true;", [l])
         self.assertFalse(l.is_enabled())
         self.marionette.execute_script("arguments[0].disabled = false;", [l])
 
     def test_can_get_element_rect(self):
         l = self.marionette.find_element(By.ID, "textInput")
         rect = l.rect
         self.assertTrue(rect['x'] > 0)
         self.assertTrue(rect['y'] > 0)
 
-
-@skip("Switched off in bug 896043, and to be turned on in bug 896046")
-class TestIsElementDisplayed(MarionetteTestCase):
-    def test_isDisplayed(self):
-        l = self.marionette.find_element(By.ID, "textInput")
-        self.assertTrue(l.is_displayed())
-        self.marionette.execute_script("arguments[0].hidden = true;", [l])
-        self.assertFalse(l.is_displayed())
-        self.marionette.execute_script("arguments[0].hidden = false;", [l])
-
-
-class TestGetElementAttributeChrome(MarionetteTestCase):
-    def setUp(self):
-        MarionetteTestCase.setUp(self)
-        self.marionette.set_context("chrome")
-        self.win = self.marionette.current_window_handle
-        self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
-        self.marionette.switch_to_window('foo')
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-
-    def tearDown(self):
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close();")
-        self.marionette.switch_to_window(self.win)
-        MarionetteTestCase.tearDown(self)
-
-    def test_get(self):
+    def test_get_attribute(self):
         el = self.marionette.execute_script("return window.document.getElementById('textInput');")
         self.assertEqual(el.get_attribute("id"), "textInput")
 
-class TestGetElementProperty(MarionetteTestCase):
-    def setUp(self):
-        MarionetteTestCase.setUp(self)
-        self.marionette.set_context("chrome")
-        self.win = self.marionette.current_window_handle
-        self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
-        self.marionette.switch_to_window('foo')
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-
-    def tearDown(self):
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close();")
-        self.marionette.switch_to_window(self.win)
-        MarionetteTestCase.tearDown(self)
-
-    def test_get(self):
+    def test_get_property(self):
         el = self.marionette.execute_script("return window.document.getElementById('textInput');")
         self.assertEqual(el.get_property("id"), "textInput")
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
@@ -309,35 +309,16 @@ class TestExecuteChrome(WindowManagerMix
 
     def test_async_script_timeout(self):
         with self.assertRaises(errors.ScriptTimeoutException):
             self.marionette.execute_async_script("""
                 var cb = arguments[arguments.length - 1];
                 setTimeout(function() { cb() }, 250);
                 """, script_timeout=100)
 
-    @skip_if_mobile("New windows not supported in Fennec")
-    def test_invalid_chrome_handle(self):
-        try:
-            win = self.open_window()
-            self.marionette.switch_to_window(win)
-
-            # Close new window and don't switch back to the original one
-            self.marionette.close_chrome_window()
-            self.assertNotEqual(self.start_window, win)
-
-            # Call execute_script on an invalid chrome handle
-            with self.marionette.using_context('chrome'):
-                self.marionette.execute_script("""
-                    return true;
-                """)
-
-        finally:
-            self.close_all_windows()
-
     def test_lasting_side_effects(self):
         pass
 
     def test_return_web_element(self):
         pass
 
     def test_return_web_element_array(self):
         pass
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py
@@ -1,33 +1,39 @@
 # 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.errors import NoSuchElementException
 from marionette_driver.marionette import HTMLElement
 
-from marionette_harness import MarionetteTestCase
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
-class TestElementsChrome(MarionetteTestCase):
+class TestElementsChrome(WindowManagerMixin, MarionetteTestCase):
+
     def setUp(self):
-        MarionetteTestCase.setUp(self)
+        super(TestElementsChrome, self).setUp()
+
         self.marionette.set_context("chrome")
-        self.win = self.marionette.current_window_handle
-        self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
-        self.marionette.switch_to_window('foo')
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
+
+        def open_window_with_js():
+            self.marionette.execute_script("""
+              window.open('chrome://marionette/content/test.xul',
+                          'foo', 'chrome,centerscreen');
+            """)
+
+        win = self.open_window(open_window_with_js)
+        self.marionette.switch_to_window(win)
 
     def tearDown(self):
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close();")
-        self.marionette.switch_to_window(self.win)
-        MarionetteTestCase.tearDown(self)
+        self.close_all_windows()
+
+        super(TestElementsChrome, self).tearDown()
 
     def test_id(self):
         el = self.marionette.execute_script("return window.document.getElementById('textInput');")
         found_el = self.marionette.find_element(By.ID, "textInput")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
     def test_that_we_can_find_elements_from_css_selectors(self):
@@ -45,38 +51,49 @@ class TestElementsChrome(MarionetteTestC
 
     def test_child_elements(self):
         el = self.marionette.find_element(By.ID, "textInput3")
         parent = self.marionette.find_element(By.ID, "things")
         found_els = parent.find_elements(By.TAG_NAME, "textbox")
         self.assertTrue(el.id in [found_el.id for found_el in found_els])
 
     def test_tag_name(self):
-        el = self.marionette.execute_script("return window.document.getElementsByTagName('vbox')[0];")
+        el = self.marionette.execute_script(
+            "return window.document.getElementsByTagName('vbox')[0];")
         found_el = self.marionette.find_element(By.TAG_NAME, "vbox")
         self.assertEquals('vbox', found_el.tag_name)
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
     def test_class_name(self):
-        el = self.marionette.execute_script("return window.document.getElementsByClassName('asdf')[0];")
+        el = self.marionette.execute_script(
+            "return window.document.getElementsByClassName('asdf')[0];")
         found_el = self.marionette.find_element(By.CLASS_NAME, "asdf")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
     def test_xpath(self):
         el = self.marionette.execute_script("return window.document.getElementById('testBox');")
         found_el = self.marionette.find_element(By.XPATH, "id('testBox')")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
     def test_not_found(self):
         self.marionette.timeout.implicit = 1
-        self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page")
+        self.assertRaises(NoSuchElementException,
+                          self.marionette.find_element, By.ID, "I'm not on the page")
         self.marionette.timeout.implicit = 0
-        self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page")
+        self.assertRaises(NoSuchElementException,
+                          self.marionette.find_element, By.ID, "I'm not on the page")
 
     def test_timeout(self):
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "myid")
         self.marionette.timeout.implicit = 4
-        self.marionette.execute_script("window.setTimeout(function() {var b = window.document.createElement('button'); b.id = 'myid'; document.getElementById('things').appendChild(b);}, 1000)")
+        self.marionette.execute_script("""
+            window.setTimeout(function () {
+              var b = window.document.createElement('button');
+              b.id = 'myid';
+              document.getElementById('things').appendChild(b);
+            }, 1000); """)
         self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "myid")))
-        self.marionette.execute_script("window.document.getElementById('things').removeChild(window.document.getElementById('myid'));")
+        self.marionette.execute_script("""
+            var elem = window.document.getElementById('things');
+            elem.removeChild(window.document.getElementById('myid')); """)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_management.py
@@ -0,0 +1,168 @@
+# 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
+from marionette_driver.errors import NoSuchWindowException
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin, skip_if_mobile
+
+
+class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(TestNoSuchWindowContent, self).setUp()
+
+    def tearDown(self):
+        self.close_all_windows()
+        super(TestNoSuchWindowContent, self).tearDown()
+
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
+    def test_closed_chrome_window(self):
+
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                test_page = self.marionette.absolute_url("windowHandles.html")
+                self.marionette.navigate(test_page)
+                self.marionette.find_element(By.ID, "new-window").click()
+
+        win = self.open_window(open_with_link)
+        self.marionette.switch_to_window(win)
+        self.marionette.close_chrome_window()
+
+        # When closing a browser window both handles are not available
+        for context in ("chrome", "content"):
+            with self.marionette.using_context(context):
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_chrome_window_handle
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_window_handle
+
+        self.marionette.switch_to_window(self.start_window)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(win)
+
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
+    def test_closed_chrome_window_while_in_frame(self):
+
+        def open_window_with_js():
+            with self.marionette.using_context("chrome"):
+                self.marionette.execute_script("""
+                  window.open('chrome://marionette/content/test.xul',
+                              'foo', 'chrome,centerscreen');
+                """)
+
+        win = self.open_window(trigger=open_window_with_js)
+        self.marionette.switch_to_window(win)
+        with self.marionette.using_context("chrome"):
+            self.marionette.switch_to_frame("iframe")
+        self.marionette.close_chrome_window()
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_window_handle
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_window)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(win)
+
+    def test_closed_tab(self):
+        with self.marionette.using_context("content"):
+            tab = self.open_tab()
+            self.marionette.switch_to_window(tab)
+            self.marionette.close()
+
+        # Check that only the content window is not available in both contexts
+        for context in ("chrome", "content"):
+            with self.marionette.using_context(context):
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_window_handle
+                self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_tab)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(tab)
+
+    def test_closed_tab_while_in_frame(self):
+        with self.marionette.using_context("content"):
+            tab = self.open_tab()
+            self.marionette.switch_to_window(tab)
+            self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
+            frame = self.marionette.find_element(By.ID, "test_iframe")
+            self.marionette.switch_to_frame(frame)
+            self.marionette.close()
+
+            with self.assertRaises(NoSuchWindowException):
+                self.marionette.current_window_handle
+            self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_tab)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(tab)
+
+
+class TestNoSuchWindowChrome(TestNoSuchWindowContent):
+
+    def setUp(self):
+        super(TestNoSuchWindowChrome, self).setUp()
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.close_all_windows()
+        super(TestNoSuchWindowChrome, self).tearDown()
+
+
+class TestSwitchWindow(WindowManagerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(TestSwitchWindow, self).setUp()
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.close_all_windows()
+        super(TestSwitchWindow, self).tearDown()
+
+    def test_windows(self):
+        def open_browser_with_js():
+            self.marionette.execute_script(" window.open(); ")
+
+        new_window = self.open_window(trigger=open_browser_with_js)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+        # switch to the other window
+        self.marionette.switch_to_window(new_window)
+        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
+        self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+        # switch back and close original window
+        self.marionette.switch_to_window(self.start_window)
+        self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+        self.marionette.close_chrome_window()
+
+        self.assertNotIn(self.start_window, self.marionette.chrome_window_handles)
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+
+    def test_should_load_and_close_a_window(self):
+        def open_window_with_link():
+            test_html = self.marionette.absolute_url("test_windows.html")
+            with self.marionette.using_context("content"):
+                self.marionette.navigate(test_html)
+                self.marionette.find_element(By.LINK_TEXT, "Open new window").click()
+
+        new_window = self.open_window(trigger=open_window_with_link)
+        self.marionette.switch_to_window(new_window)
+        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
+        self.assertEqual(len(self.marionette.chrome_window_handles), 2)
+
+        with self.marionette.using_context('content'):
+            self.assertEqual(self.marionette.title, "We Arrive Here")
+
+        # Let's close and check
+        self.marionette.close_chrome_window()
+        self.marionette.switch_to_window(self.start_window)
+        self.assertEqual(len(self.marionette.chrome_window_handles), 1)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_status_chrome.py
@@ -0,0 +1,24 @@
+# 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/.
+
+import os
+import sys
+
+# add this directory to the path
+sys.path.append(os.path.dirname(__file__))
+
+from test_window_status_content import TestNoSuchWindowContent
+
+
+class TestNoSuchWindowChrome(TestNoSuchWindowContent):
+
+    def setUp(self):
+        super(TestNoSuchWindowChrome, self).setUp()
+
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.close_all_windows()
+
+        super(TestNoSuchWindowChrome, self).tearDown()
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_status_content.py
@@ -0,0 +1,115 @@
+# 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
+from marionette_driver.errors import NoSuchWindowException
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin, skip_if_mobile
+
+
+class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(TestNoSuchWindowContent, self).setUp()
+
+        self.test_page = self.marionette.absolute_url("windowHandles.html")
+        with self.marionette.using_context("content"):
+            self.marionette.navigate(self.test_page)
+
+    def tearDown(self):
+        self.close_all_windows()
+        super(TestNoSuchWindowContent, self).tearDown()
+
+    def open_tab_in_foreground(self):
+        with self.marionette.using_context("content"):
+            link = self.marionette.find_element(By.ID, "new-tab")
+            link.click()
+
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
+    def test_closed_chrome_window(self):
+
+        def open_with_link():
+            with self.marionette.using_context("content"):
+                test_page = self.marionette.absolute_url("windowHandles.html")
+                self.marionette.navigate(test_page)
+                self.marionette.find_element(By.ID, "new-window").click()
+
+        win = self.open_window(open_with_link)
+        self.marionette.switch_to_window(win)
+        self.marionette.close_chrome_window()
+
+        # When closing a browser window both handles are not available
+        for context in ("chrome", "content"):
+            with self.marionette.using_context(context):
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_chrome_window_handle
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_window_handle
+
+        self.marionette.switch_to_window(self.start_window)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(win)
+
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
+    def test_closed_chrome_window_while_in_frame(self):
+
+        def open_window_with_js():
+            with self.marionette.using_context("chrome"):
+                self.marionette.execute_script("""
+                  window.open('chrome://marionette/content/test.xul',
+                              'foo', 'chrome,centerscreen');
+                """)
+
+        win = self.open_window(trigger=open_window_with_js)
+        self.marionette.switch_to_window(win)
+        with self.marionette.using_context("chrome"):
+            self.marionette.switch_to_frame("iframe")
+        self.marionette.close_chrome_window()
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_window_handle
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_window)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(win)
+
+    def test_closed_tab(self):
+        with self.marionette.using_context("content"):
+            tab = self.open_tab(self.open_tab_in_foreground)
+            self.marionette.switch_to_window(tab)
+            self.marionette.close()
+
+        # Check that only the content window is not available in both contexts
+        for context in ("chrome", "content"):
+            with self.marionette.using_context(context):
+                with self.assertRaises(NoSuchWindowException):
+                    self.marionette.current_window_handle
+                self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_tab)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(tab)
+
+    def test_closed_tab_while_in_frame(self):
+        with self.marionette.using_context("content"):
+            tab = self.open_tab(self.open_tab_in_foreground)
+            self.marionette.switch_to_window(tab)
+            self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
+            frame = self.marionette.find_element(By.ID, "test_iframe")
+            self.marionette.switch_to_frame(frame)
+            self.marionette.close()
+
+            with self.assertRaises(NoSuchWindowException):
+                self.marionette.current_window_handle
+            self.marionette.current_chrome_window_handle
+
+        self.marionette.switch_to_window(self.start_tab)
+
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.switch_to_window(tab)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py
@@ -1,26 +1,33 @@
 # 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_harness import MarionetteTestCase
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
-class TestTitleChrome(MarionetteTestCase):
+class TestTitleChrome(WindowManagerMixin, MarionetteTestCase):
+
     def setUp(self):
-        MarionetteTestCase.setUp(self)
+        super(TestTitleChrome, self).setUp()
+
         self.marionette.set_context("chrome")
-        self.win = self.marionette.current_window_handle
-        self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
-        self.marionette.switch_to_window('foo')
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
 
     def tearDown(self):
-        self.assertNotEqual(self.win, self.marionette.current_window_handle)
-        self.marionette.execute_script("window.close();")
-        self.marionette.switch_to_window(self.win)
-        MarionetteTestCase.tearDown(self)
+        self.close_all_windows()
+
+        super(TestTitleChrome, self).tearDown()
 
     def test_get_chrome_title(self):
-        title = self.marionette.execute_script("return window.document.documentElement.getAttribute('title');")
+
+        def open_window_with_js():
+            self.marionette.execute_script("""
+              window.open('chrome://marionette/content/test.xul',
+                          'foo', 'chrome,centerscreen');
+            """)
+
+        win = self.open_window(open_window_with_js)
+        self.marionette.switch_to_window(win)
+
+        title = self.marionette.execute_script(
+            "return window.document.documentElement.getAttribute('title');")
         self.assertEqual(title, self.marionette.title)
-        self.assertEqual('Title Test', self.marionette.title)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_type_chrome.py
@@ -0,0 +1,33 @@
+# 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_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestWindowTypeChrome(WindowManagerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(TestWindowTypeChrome, self).setUp()
+
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.close_all_windows()
+
+        super(TestWindowTypeChrome, self).tearDown()
+
+    def test_get_window_type(self):
+
+        def open_window_with_js():
+            self.marionette.execute_script("""
+              window.open('chrome://marionette/content/test.xul',
+                          'foo', 'chrome,centerscreen');
+            """)
+
+        win = self.open_window(open_window_with_js)
+        self.marionette.switch_to_window(win)
+
+        window_type = self.marionette.execute_script(
+            "return window.document.documentElement.getAttribute('windowtype');")
+        self.assertEqual(window_type, self.marionette.get_window_type())
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -71,23 +71,25 @@ skip-if = appname == 'fennec'
 [test_window_handles_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_handles_content.py]
 [test_window_close_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_close_content.py]
 [test_window_position.py]
 skip-if = appname == 'fennec'
+[test_window_status_content.py]
+[test_window_status_chrome.py]
 
 [test_screenshot.py]
 [test_cookies.py]
 [test_window_title.py]
 [test_window_title_chrome.py]
 skip-if = appname == 'fennec'
-[test_window_type.py]
+[test_window_type_chrome.py]
 skip-if = appname == 'fennec'
 [test_implicit_waits.py]
 [test_wait.py]
 [test_expected.py]
 [test_date_time_value.py]
 [test_getactiveframe_oop.py]
 skip-if = true # Bug 925688
 [test_chrome_async_finish.js]