Bug 1266134 - Pull host management out of toolbox.xul. r=jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 25 Aug 2016 05:51:11 -0700
changeset 422064 ef039b97b009afffc3fe66f9af54bafa0bcf2fc7
parent 421656 da986c9f1f723af1e0c44f4ccd4cddd5fb6084e8
child 422065 52f8099ede88cf4ee950a20a93230845cfc5a6a9
push id31675
push userbmo:poirot.alex@gmail.com
push dateFri, 07 Oct 2016 09:37:23 +0000
reviewersjryans
bugs1266134
milestone52.0a1
Bug 1266134 - Pull host management out of toolbox.xul. r=jryans MozReview-Commit-ID: 71GNAFuVFgl
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/framework/devtools.js
devtools/client/framework/moz.build
devtools/client/framework/test/browser_devtools_api.js
devtools/client/framework/test/browser_toolbox_custom_host.js
devtools/client/framework/test/browser_toolbox_raise.js
devtools/client/framework/test/browser_toolbox_toggle.js
devtools/client/framework/test/browser_toolbox_window_title_changes.js
devtools/client/framework/test/browser_toolbox_window_title_frame_select.js
devtools/client/framework/test/shared-head.js
devtools/client/framework/toolbox-host-manager.js
devtools/client/framework/toolbox.js
devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
devtools/client/inspector/test/browser_inspector_portrait_mode.js
devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js
devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
@@ -32,11 +32,10 @@ var test = Task.async(function* () {
   callInTab(tab2, "testCase");
   const packet = yield paused;
 
   is(packet.why.type, "debuggerStatement",
      "Should have stopped at the debugger statement, not the other tab's breakpoint");
   is(packet.frame.where.line, 3,
      "Should have stopped at line 3 (debugger statement), not line 2 (other tab's breakpoint)");
 
-  yield teardown(panel1);
   yield resumeDebuggerThenCloseAndFinish(panel2);
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
@@ -79,34 +79,34 @@ function getHost(host) {
   if (host.indexOf("window") == 0) {
     return "window";
   }
   return host;
 }
 
 function resizeToolboxWindow(panel, host) {
   let sizeOption = host.split(":")[1];
-  let win = panel._toolbox._host._window;
+  let win = panel._toolbox.win.parent;
 
   // should be the same value as BREAKPOINT_SMALL_WINDOW_WIDTH in debugger-view.js
   let breakpoint = 850;
 
   if (sizeOption == "big" && win.outerWidth <= breakpoint) {
     yield resizeAndWaitForLayoutChange(panel, breakpoint + 300);
   } else if (sizeOption == "small" && win.outerWidth >= breakpoint) {
     yield resizeAndWaitForLayoutChange(panel, breakpoint - 300);
   } else {
     info("Window resize unnecessary for host " + host);
   }
 }
 
 function resizeAndWaitForLayoutChange(panel, width) {
     info("Updating toolbox window width to " + width);
 
-    let win = panel._toolbox._host._window;
+    let win = panel._toolbox.win.parent;
     let gDebugger = panel.panelWin;
 
     win.resizeTo(width, window.screen.availHeight);
     yield waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
 }
 
 function testHost(aPanel, aHostType, aLayoutType) {
   let gDebugger = aPanel.panelWin;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
@@ -29,18 +29,27 @@ add_task(function* () {
   let { workers } = yield listWorkers(tabClient);
   let [, workerClient] = yield attachWorker(tabClient,
                                              findWorker(workers, WORKER_URL));
 
   let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
                                             "jsdebugger",
                                             Toolbox.HostType.WINDOW);
 
-  is(toolbox._host.type, "window", "correct host");
-  ok(toolbox._host._window.document.title.includes(WORKER_URL),
+  is(toolbox.hostType, "window", "correct host");
+
+  yield new Promise(done => {
+    toolbox.win.parent.addEventListener("message", function onmessage(event) {
+      if (event.data.name == "set-host-title") {
+        toolbox.win.parent.removeEventListener("message", onmessage);
+        done();
+      }
+    });
+  });
+  ok(toolbox.win.parent.document.title.includes(WORKER_URL),
      "worker URL in host title");
 
   let toolTabs = toolbox.doc.querySelectorAll(".devtools-tab");
   let activeTools = [...toolTabs].map(tab=>tab.getAttribute("toolid"));
 
   is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
     "Correct set of tools supported by worker");
 
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -746,16 +746,19 @@ AddonDebugger.prototype = {
 
       groupmap.get(group).get(label).url = source.url.split(" -> ").pop();
     }
 
     return groups;
   }),
 
   _onMessage: function (event) {
+    if (typeof(event.data) !== "string") {
+      return;
+    }
     let json = JSON.parse(event.data);
     switch (json.name) {
       case "toolbox-title":
         this.title = json.data.value;
         break;
     }
   }
 };
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -5,24 +5,26 @@
 "use strict";
 
 const Services = require("Services");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 
 // Load gDevToolsBrowser toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
+loader.lazyRequireGetter(this, "ToolboxHostManager", "devtools/client/framework/toolbox-host-manager", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 
 const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
   require("devtools/client/definitions");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {JsonView} = require("devtools/client/jsonview/main");
 const AboutDevTools = require("devtools/client/framework/about-devtools-toolbox");
 const {when: unload} = require("sdk/system/unload");
+const {Task} = require("devtools/shared/task");
 
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
@@ -392,64 +394,52 @@ DevTools.prototype = {
    * @param {Toolbox.HostType} hostType
    *        The type of host (bottom, window, side)
    * @param {object} hostOptions
    *        Options for host specifically
    *
    * @return {Toolbox} toolbox
    *        The toolbox that was opened
    */
-  showToolbox: function (target, toolId, hostType, hostOptions) {
-    let deferred = defer();
-
+  showToolbox: Task.async(function* (target, toolId, hostType, hostOptions) {
     let toolbox = this._toolboxes.get(target);
     if (toolbox) {
 
-      let hostPromise = (hostType != null && toolbox.hostType != hostType) ?
-          toolbox.switchHost(hostType) :
-          promise.resolve(null);
+      if (hostType != null && toolbox.hostType != hostType) {
+        yield toolbox.switchHost(hostType);
+      }
 
       if (toolId != null && toolbox.currentToolId != toolId) {
-        hostPromise = hostPromise.then(function () {
-          return toolbox.selectTool(toolId);
-        });
+        yield toolbox.selectTool(toolId);
       }
 
-      return hostPromise.then(function () {
-        toolbox.raise();
-        return toolbox;
-      });
-    }
-    else {
-      // No toolbox for target, create one
-      toolbox = new Toolbox(target, toolId, hostType, hostOptions);
+      toolbox.raise();
+    } else {
+      let manager = new ToolboxHostManager(target, hostType, hostOptions);
+
+      toolbox = yield manager.create(toolId);
+      this._toolboxes.set(target, toolbox);
 
       this.emit("toolbox-created", toolbox);
 
-      this._toolboxes.set(target, toolbox);
-
       toolbox.once("destroy", () => {
         this.emit("toolbox-destroy", target);
       });
 
       toolbox.once("destroyed", () => {
         this._toolboxes.delete(target);
         this.emit("toolbox-destroyed", target);
       });
 
-      // If toolId was passed in, it will already be selected before the
-      // open promise resolves.
-      toolbox.open().then(() => {
-        deferred.resolve(toolbox);
-        this.emit("toolbox-ready", toolbox);
-      });
+      yield toolbox.open();
+      this.emit("toolbox-ready", toolbox);
     }
 
-    return deferred.promise;
-  },
+    return toolbox;
+  }),
 
   /**
    * Return the toolbox for a given target.
    *
    * @param  {object} target
    *         Target value e.g. the target that owns this toolbox
    *
    * @return {Toolbox} toolbox
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -20,13 +20,14 @@ DevToolsModules(
     'menu-item.js',
     'menu.js',
     'selection.js',
     'sidebar.js',
     'source-map-service.js',
     'target-from-url.js',
     'target.js',
     'toolbox-highlighter-utils.js',
+    'toolbox-host-manager.js',
     'toolbox-hosts.js',
     'toolbox-options.js',
     'toolbox.js',
     'ToolboxProcess.jsm',
 )
--- a/devtools/client/framework/test/browser_devtools_api.js
+++ b/devtools/client/framework/test/browser_devtools_api.js
@@ -66,17 +66,17 @@ function runTests1(aTab) {
     toolbox.once(toolId1 + "-ready", (event, panel) => {
       ok(panel, "panel argument available");
       events["ready"] = true;
     });
   });
 
   gDevTools.showToolbox(target, toolId1).then(function (toolbox) {
     is(toolbox.target, target, "toolbox target is correct");
-    is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
+    is(toolbox.target.tab, gBrowser.selectedTab, "targeted tab is correct");
 
     ok(events["init"], "init event fired");
     ok(events["ready"], "ready event fired");
 
     gDevTools.unregisterTool(toolId1);
 
     // Wait for unregisterTool to select the next tool before calling runTests2,
     // otherwise we will receive the wrong select event when waiting for
@@ -134,17 +134,17 @@ function runTests2() {
     toolbox.once(toolId2 + "-ready", (event, panel) => {
       ok(panel, "panel argument available");
       events["ready"] = true;
     });
   });
 
   gDevTools.showToolbox(target, toolId2).then(function (toolbox) {
     is(toolbox.target, target, "toolbox target is correct");
-    is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
+    is(toolbox.target.tab, gBrowser.selectedTab, "targeted tab is correct");
 
     ok(events["init"], "init event fired");
     ok(events["build"], "build event fired");
     ok(events["ready"], "ready event fired");
 
     continueTests(toolbox);
   });
 }
--- a/devtools/client/framework/test/browser_toolbox_custom_host.js
+++ b/devtools/client/framework/test/browser_toolbox_custom_host.js
@@ -19,16 +19,19 @@ function test() {
     target = TargetFactory.forTab(tab);
     let options = {customIframe: iframe};
     gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options)
              .then(testCustomHost, console.error)
              .then(null, console.error);
   });
 
   function onMessage(event) {
+    if (typeof(event.data) !== "string") {
+      return;
+    }
     info("onMessage: " + event.data);
     let json = JSON.parse(event.data);
     if (json.name == "toolbox-close") {
       ok("Got the `toolbox-close` message");
       window.removeEventListener("message", onMessage);
       cleanup();
     }
   }
--- a/devtools/client/framework/test/browser_toolbox_raise.js
+++ b/devtools/client/framework/test/browser_toolbox_raise.js
@@ -37,35 +37,35 @@ function testBottomHost(aToolbox) {
 
 function testWindowHost() {
   // Make sure toolbox is not focused.
   window.addEventListener("focus", onFocus, true);
 
   // Need to wait for focus  as otherwise window.focus() is overridden by
   // toolbox window getting focused first on Linux and Mac.
   let onToolboxFocus = () => {
-    toolbox._host._window.removeEventListener("focus", onToolboxFocus, true);
+    toolbox.win.parent.removeEventListener("focus", onToolboxFocus, true);
     info("focusing main window.");
     window.focus();
   };
   // Need to wait for toolbox window to get focus.
-  toolbox._host._window.addEventListener("focus", onToolboxFocus, true);
+  toolbox.win.parent.addEventListener("focus", onToolboxFocus, true);
 }
 
 function onFocus() {
   info("Main window is focused before calling toolbox.raise()");
   window.removeEventListener("focus", onFocus, true);
 
   // Check if toolbox window got focus.
   let onToolboxFocusAgain = () => {
-    toolbox._host._window.removeEventListener("focus", onToolboxFocusAgain, false);
+    toolbox.win.parent.removeEventListener("focus", onToolboxFocusAgain, false);
     ok(true, "Toolbox window is the focused window after calling toolbox.raise()");
     cleanup();
   };
-  toolbox._host._window.addEventListener("focus", onToolboxFocusAgain, false);
+  toolbox.win.parent.addEventListener("focus", onToolboxFocusAgain, false);
 
   // Now raise toolbox.
   toolbox.raise();
 }
 
 function cleanup() {
   Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
 
--- a/devtools/client/framework/test/browser_toolbox_toggle.js
+++ b/devtools/client/framework/test/browser_toolbox_toggle.js
@@ -77,17 +77,17 @@ function* testToggleDetachedToolbox(tab,
 
   let onMainWindowFocus = once(window, "focus");
   window.focus();
   yield onMainWindowFocus;
   ok(true, "Main window focused");
 
   info("Verify windowed toolbox is focused instead of closed when using " +
     "toggle key from the main window");
-  let toolboxWindow = toolbox._host._window;
+  let toolboxWindow = toolbox.win.top;
   let onToolboxWindowFocus = once(toolboxWindow, "focus", true);
   EventUtils.synthesizeKey(key, modifiers);
   yield onToolboxWindowFocus;
   ok(true, "Toolbox focused and not destroyed");
 
   info("Verify windowed toolbox is destroyed when using toggle key from its " +
     "own window");
 
--- a/devtools/client/framework/test/browser_toolbox_window_title_changes.js
+++ b/devtools/client/framework/test/browser_toolbox_window_title_changes.js
@@ -23,55 +23,66 @@ function test() {
 
   addTab(URL_1).then(function () {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM)
       .then(function (aToolbox) { toolbox = aToolbox; })
       .then(() => toolbox.selectTool(TOOL_ID_1))
 
     // undock toolbox and check title
-      .then(() => toolbox.switchHost(Toolbox.HostType.WINDOW))
+      .then(() => {
+        // We have to first switch the host in order to spawn the new top level window
+        // on which we are going to listen from title change event
+        return toolbox.switchHost(Toolbox.HostType.WINDOW)
+          .then(() => waitForTitleChange(toolbox));
+      })
       .then(checkTitle.bind(null, NAME_1, URL_1, "toolbox undocked"))
 
     // switch to different tool and check title
-      .then(() => toolbox.selectTool(TOOL_ID_2))
+      .then(() => {
+        let onTitleChanged = waitForTitleChange(toolbox);
+        toolbox.selectTool(TOOL_ID_2);
+        return onTitleChanged;
+      })
       .then(checkTitle.bind(null, NAME_1, URL_1, "tool changed"))
 
     // navigate to different local url and check title
       .then(function () {
-        let deferred = defer();
-        target.once("navigate", () => deferred.resolve());
+        let onTitleChanged = waitForTitleChange(toolbox);
         gBrowser.loadURI(URL_2);
-        return deferred.promise;
+        return onTitleChanged;
       })
       .then(checkTitle.bind(null, NAME_2, URL_2, "url changed"))
 
     // navigate to a real url and check title
       .then(() => {
-        let deferred = defer();
-        target.once("navigate", () => deferred.resolve());
+        let onTitleChanged = waitForTitleChange(toolbox);
         gBrowser.loadURI(URL_3);
-        return deferred.promise;
+        return onTitleChanged;
       })
       .then(checkTitle.bind(null, NAME_3, URL_3, "url changed"))
 
     // destroy toolbox, create new one hosted in a window (with a
     // different tool id), and check title
       .then(function () {
         // Give the tools a chance to handle the navigation event before
         // destroying the toolbox.
         executeSoon(function () {
           toolbox.destroy()
             .then(function () {
               // After destroying the toolbox, a fresh target is required.
               target = TargetFactory.forTab(gBrowser.selectedTab);
               return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
             })
             .then(function (aToolbox) { toolbox = aToolbox; })
-            .then(() => toolbox.selectTool(TOOL_ID_1))
+            .then(() => {
+              let onTitleChanged = waitForTitleChange(toolbox);
+              toolbox.selectTool(TOOL_ID_1);
+              return onTitleChanged;
+            })
             .then(checkTitle.bind(null, NAME_3, URL_3,
                                   "toolbox destroyed and recreated"))
 
             // clean up
             .then(() => toolbox.destroy())
             .then(function () {
               toolbox = null;
               gBrowser.removeCurrentTab();
--- a/devtools/client/framework/test/browser_toolbox_window_title_frame_select.js
+++ b/devtools/client/framework/test/browser_toolbox_window_title_frame_select.js
@@ -20,18 +20,24 @@ const IFRAME_URL = URL_ROOT + "browser_t
 add_task(function* () {
   Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
 
   yield addTab(URL);
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = yield gDevTools.showToolbox(target, null,
     Toolbox.HostType.BOTTOM);
 
+  let onTitleChanged = waitForTitleChange(toolbox);
   yield toolbox.selectTool("inspector");
+  yield onTitleChanged;
+
   yield toolbox.switchHost(Toolbox.HostType.WINDOW);
+  // Wait for title change event *after* switch host, in order to listen
+  // for the event on the WINDOW host window, which only exists after switchHost
+  yield waitForTitleChange(toolbox);
 
   is(getTitle(), `Developer Tools - Page title - ${URL}`,
     "Devtools title correct after switching to detached window host");
 
   // Wait for tick to avoid unexpected 'popuphidden' event, which
   // blocks the frame popup menu opened below. See also bug 1276873
   yield waitForTick();
 
@@ -51,24 +57,27 @@ add_task(function* () {
   let topFrameBtn = frames.filter(b => b.label == URL)[0];
   let iframeBtn = frames.filter(b => b.label == IFRAME_URL)[0];
   ok(topFrameBtn, "Got top level document in the list");
   ok(iframeBtn, "Got iframe document in the list");
 
   // Listen to will-navigate to check if the view is empty
   let willNavigate = toolbox.target.once("will-navigate");
 
+  onTitleChanged = waitForTitleChange(toolbox);
+
   // Only select the iframe after we are able to select an element from the top
   // level document.
   let newRoot = toolbox.getPanel("inspector").once("new-root");
   info("Select the iframe");
   iframeBtn.click();
 
   yield willNavigate;
   yield newRoot;
+  yield onTitleChanged;
 
   info("Navigation to the iframe is done, the inspector should be back up");
   is(getTitle(), `Developer Tools - Page title - ${URL}`,
     "Devtools title was not updated after changing inspected frame");
 
   info("Cleanup toolbox and test preferences.");
   yield toolbox.destroy();
   toolbox = null;
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -574,8 +574,22 @@ function emptyClipboard() {
 }
 
 /**
  * Check if the current operating system is Windows.
  */
 function isWindows() {
   return Services.appinfo.OS === "WINNT";
 }
+
+/**
+ * Wait for a given toolbox to get its title updated.
+ */
+function waitForTitleChange(toolbox) {
+  let deferred = defer();
+  toolbox.win.parent.addEventListener("message", function onmessage(event) {
+    if (event.data.name == "set-host-title") {
+      toolbox.win.parent.removeEventListener("message", onmessage);
+      deferred.resolve();
+    }
+  });
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/toolbox-host-manager.js
@@ -0,0 +1,258 @@
+const Services = require("Services");
+const {Ci} = require("chrome");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const {Task} = require("devtools/shared/task");
+
+loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
+loader.lazyRequireGetter(this, "Hosts", "devtools/client/framework/toolbox-hosts", true);
+
+/**
+ * Implement a wrapper on the chrome side to setup a Toolbox within Firefox UI.
+ *
+ * This component handles iframe creation within Firefox, in which we are loading
+ * the toolbox document. Then both the chrome and the toolbox document communicate
+ * via "message" events.
+ *
+ * Messages sent by the toolbox to the chrome:
+ * - switch-host:
+ *   Order to display the toolbox in another host (side, bottom or window)
+ * - switch-to-previous-host:
+ *   Order to display the toolbox in the previously used host
+ * - toggle-minimize-mode:
+ *   When using the bottom host, the toolbox can be miximized to only display
+ *   the tool titles
+ * - maximize-host:
+ *   When using the bottom host in minimized mode, revert back to regular mode
+ *   in order to see tool titles and the tools
+ * - raise-host:
+ *   Focus the tools
+ * - set-host-title:
+ *   When using the window host, update the window title
+ *
+ * Messages sent by the chrome to the toolbox:
+ * - host-minimized:
+ *   The bottom host is done minimizing (after animation end)
+ * - host-maximized:
+ *   The bottom host is done switching back to regular mode (after animation
+ *   end)
+ * - switched-host:
+ *   The `switch-host` command sent by the toolbox is done
+ */
+
+const LAST_HOST = "devtools.toolbox.host";
+const PREVIOUS_HOST = "devtools.toolbox.previousHost";
+let ID_COUNTER = 1;
+
+function ToolboxHostManager(target, hostType, hostOptions) {
+  this.target = target;
+
+  this.frameId = ID_COUNTER++;
+
+  if (!hostType) {
+    hostType = Services.prefs.getCharPref(LAST_HOST);
+  }
+  this.onHostMinimized = this.onHostMinimized.bind(this);
+  this.onHostMaximized = this.onHostMaximized.bind(this);
+  this.host = this.createHost(hostType, hostOptions);
+  this.hostType = hostType;
+}
+
+ToolboxHostManager.prototype = {
+  create: Task.async(function* (toolId) {
+    yield this.host.create();
+
+    this.host.frame.setAttribute("aria-label", L10N.getStr("toolbox.label"));
+    this.host.frame.ownerDocument.defaultView.addEventListener("message", this);
+    // We have to listen on capture as no event fires on bubble
+    this.host.frame.addEventListener("unload", this, true);
+
+    let toolbox = new Toolbox(this.target, toolId, this.host.type, this.host.frame.contentWindow, this.frameId);
+
+    // Prevent reloading the toolbox when loading the tools in a tab (e.g. from about:debugging)
+    if (!this.host.frame.contentWindow.location.href.startsWith("about:devtools-toolbox")) {
+      this.host.frame.setAttribute("src", "about:devtools-toolbox");
+    }
+
+    return toolbox;
+  }),
+
+  handleEvent(event) {
+    switch(event.type) {
+      case "message":
+        this.onMessage(event);
+        break;
+      case "unload":
+        // On unload, host iframe already lost its contentWindow attribute, so
+        // we can only compare against locations. Here we filter two very
+        // different cases: preliminary about:blank document as well as iframes
+        // like tool iframes.
+        if (!event.target.location.href.startsWith("about:devtools-toolbox")) {
+          break;
+        }
+        // Don't destroy the host during unload event (esp., don't remove the
+        // iframe from DOM!). Otherwise the unload event for the toolbox
+        // document doesn't fire within the toolbox *document*! This is
+        // the unload event that fires on the toolbox *iframe*.
+        DevToolsUtils.executeSoon(() => {
+          this.destroy();
+        });
+        break;
+    }
+  },
+
+  onMessage(event) {
+    if (!event.data) {
+      return;
+    }
+    // Toolbox document is still chrome and disallow identifying message
+    // origin via event.source as it is null. So use a custom id.
+    if (event.data.frameId != this.frameId) {
+      return;
+    }
+    switch (event.data.name) {
+      case "switch-host":
+        this.switchHost(event.data.hostType);
+        break;
+      case "switch-to-previous-host":
+        this.switchToPreviousHost();
+        break;
+      case "maximize-host":
+        this.host.maximize();
+        break;
+      case "raise-host":
+        this.host.raise();
+        break;
+      case "toggle-minimize-mode":
+        this.host.toggleMinimizeMode(event.data.toolbarHeight);
+        break;
+      case "set-host-title":
+        this.host.setTitle(event.data.title);
+        break;
+    }
+  },
+
+  postMessage(data) {
+    let window = this.host.frame.contentWindow;
+    window.postMessage(data, "*");
+  },
+
+  destroy() {
+    this.destroyHost();
+    this.host = null;
+    this.hostType = null;
+    this.target = null;
+  },
+
+  /**
+   * Create a host object based on the given host type.
+   *
+   * Warning: bottom and sidebar hosts require that the toolbox target provides
+   * a reference to the attached tab. Not all Targets have a tab property -
+   * make sure you correctly mix and match hosts and targets.
+   *
+   * @param {string} hostType
+   *        The host type of the new host object
+   *
+   * @return {Host} host
+   *        The created host object
+   */
+  createHost(hostType, options) {
+    if (!Hosts[hostType]) {
+      throw new Error("Unknown hostType: " + hostType);
+    }
+
+    let newHost = new Hosts[hostType](this.target.tab, options);
+    // Update the label and icon when the state changes.
+    newHost.on("minimized", this.onHostMinimized);
+    newHost.on("maximized", this.onHostMaximized);
+    return newHost;
+  },
+
+  onHostMinimized() {
+    this.postMessage({
+      name: "host-minimized"
+    });
+  },
+
+  onHostMaximized() {
+    this.postMessage({
+      name: "host-maximized"
+    });
+  },
+
+  /**
+   * Switch to the last used host for the toolbox UI.
+   * This is determined by the devtools.toolbox.previousHost pref.
+   */
+  switchToPreviousHost() {
+    let hostType = Services.prefs.getCharPref(PREVIOUS_HOST);
+
+    // Handle the case where the previous host happens to match the current
+    // host. If so, switch to bottom if it's not already used, and side if not.
+    if (hostType === this.hostType) {
+      if (hostType === Toolbox.HostType.BOTTOM) {
+        hostType = Toolbox.HostType.SIDE;
+      } else {
+        hostType = Toolbox.HostType.BOTTOM;
+      }
+    }
+
+    return this.switchHost(hostType);
+  },
+
+  switchHost: Task.async(function* (hostType) {
+    let iframe = this.host.frame;
+    let newHost = this.createHost(hostType);
+    let newIframe = yield newHost.create();
+    // change toolbox document's parent to the new host
+    newIframe.swapFrameLoaders(iframe);
+
+    // See bug 1022726, most probably because of swapFrameLoaders we need to
+    // first focus the window here, and then once again further from
+    // toolbox.js to make sure focus actually happens.
+    iframe.contentWindow.focus();
+
+    this.destroyHost();
+
+    if (this.hostType != Toolbox.HostType.CUSTOM) {
+      Services.prefs.setCharPref(PREVIOUS_HOST, this.hostType);
+    }
+
+    this.host = newHost;
+    this.hostType = hostType;
+    this.host.setTitle(this.host.frame.contentWindow.document.title);
+    this.host.frame.ownerDocument.defaultView.addEventListener("message", this);
+    this.host.frame.addEventListener("unload", this, true);
+
+    if (hostType != Toolbox.HostType.CUSTOM) {
+      Services.prefs.setCharPref(LAST_HOST, hostType);
+    }
+
+    // Tell the toolbox the host changed
+    this.postMessage({
+      name: "switched-host",
+      hostType
+    });
+  }),
+
+  /**
+   * Destroy the current host, and remove event listeners from its frame.
+   *
+   * @return {promise} to be resolved when the host is destroyed.
+   */
+  destroyHost() {
+    // When Firefox toplevel is closed, the frame may already be detached and
+    // the top level document gone
+    if (this.host.frame.ownerDocument.defaultView) {
+      this.host.frame.ownerDocument.defaultView.removeEventListener("message", this);
+    }
+    this.host.frame.removeEventListener("unload", this, true);
+
+    this.host.off("minimized", this.onHostMinimized);
+    this.host.off("maximized", this.onHostMaximized);
+    return this.host.destroy();
+  }
+};
+exports.ToolboxHostManager = ToolboxHostManager;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -35,18 +35,16 @@ const { BrowserLoader } =
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
 
 loader.lazyRequireGetter(this, "CommandUtils",
   "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "getHighlighterUtils",
   "devtools/client/framework/toolbox-highlighter-utils", true);
-loader.lazyRequireGetter(this, "Hosts",
-  "devtools/client/framework/toolbox-hosts", true);
 loader.lazyRequireGetter(this, "Selection",
   "devtools/client/framework/selection", true);
 loader.lazyRequireGetter(this, "InspectorFront",
   "devtools/shared/fronts/inspector", true);
 loader.lazyRequireGetter(this, "flags",
   "devtools/shared/flags");
 loader.lazyRequireGetter(this, "showDoorhanger",
   "devtools/client/shared/doorhanger", true);
@@ -98,22 +96,27 @@ const ToolboxButtons = exports.ToolboxBu
  * the iframes where the tool panels will be living in.
  *
  * @param {object} target
  *        The object the toolbox is debugging.
  * @param {string} selectedTool
  *        Tool to select initially
  * @param {Toolbox.HostType} hostType
  *        Type of host that will host the toolbox (e.g. sidebar, window)
- * @param {object} hostOptions
- *        Options for host specifically
+ * @param {DOMWindow} contentWindow
+ *        The window object of the toolbox document
+ * @param {string} frameId
+ *        A unique identifier to differentiate toolbox documents from the
+ *        chrome codebase when passing DOM messages
  */
-function Toolbox(target, selectedTool, hostType, hostOptions) {
+function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
   this._target = target;
-  this._win = null;
+  this._win = contentWindow;
+  this.frameId = frameId;
+
   this._toolPanels = new Map();
   this._telemetry = new Telemetry();
   if (Services.prefs.getBoolPref("devtools.sourcemap.locations.enabled")) {
     this._sourceMapService = new SourceMapService(this._target);
   }
 
   this._initInspector = null;
   this._inspector = null;
@@ -131,39 +134,36 @@ function Toolbox(target, selectedTool, h
   this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
   this.destroy = this.destroy.bind(this);
   this.highlighterUtils = getHighlighterUtils(this);
   this._highlighterReady = this._highlighterReady.bind(this);
   this._highlighterHidden = this._highlighterHidden.bind(this);
   this._prefChanged = this._prefChanged.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
+  this._onBrowserMessage = this._onBrowserMessage.bind(this);
   this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
   this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
   this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
   this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
   this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
   this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
   this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
   this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
   this._onTabbarFocus = this._onTabbarFocus.bind(this);
   this._onTabbarArrowKeypress = this._onTabbarArrowKeypress.bind(this);
 
   this._target.on("close", this.destroy);
 
-  if (!hostType) {
-    hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
-  }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   this._defaultToolId = selectedTool;
 
-  this._hostOptions = hostOptions;
-  this._host = this._createHost(hostType, hostOptions);
+  this._hostType = hostType;
 
   EventEmitter.decorate(this);
 
   this._target.on("navigate", this._refreshHostTitle);
   this._target.on("frame-update", this._updateFrames);
 
   this.on("host-changed", this._refreshHostTitle);
   this.on("select", this._refreshHostTitle);
@@ -185,20 +185,18 @@ Toolbox.HostType = {
   WINDOW: "window",
   CUSTOM: "custom"
 };
 
 Toolbox.prototype = {
   _URL: "about:devtools-toolbox",
 
   _prefs: {
-    LAST_HOST: "devtools.toolbox.host",
     LAST_TOOL: "devtools.toolbox.selectedTool",
     SIDE_ENABLED: "devtools.toolbox.sideEnabled",
-    PREVIOUS_HOST: "devtools.toolbox.previousHost"
   },
 
   currentToolId: null,
   lastUsedToolId: null,
 
   /**
    * Returns a *copy* of the _toolPanels collection.
    *
@@ -265,17 +263,17 @@ Toolbox.prototype = {
     return this._threadClient;
   },
 
   /**
    * Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
    * tab. See HostType for more details.
    */
   get hostType() {
-    return this._host.type;
+    return this._hostType;
   },
 
   /**
    * Shortcut to the window containing the toolbox UI
    */
   get win() {
     return this._win;
   },
@@ -348,37 +346,28 @@ Toolbox.prototype = {
       this.doc.querySelector("#toolbox-panel-iframe-webconsole").contentWindow;
   },
 
   /**
    * Open the toolbox
    */
   open: function () {
     return Task.spawn(function* () {
-      let iframe = yield this._host.create();
-      this._win = iframe.contentWindow;
-
-      let domReady = defer();
-
-      // Prevent reloading the document when the toolbox is opened in a tab
-      let location = iframe.contentWindow.location.href;
-      if (!location.startsWith(this._URL)) {
-        iframe.setAttribute("src", this._URL);
-      } else {
-        // Update the URL so that onceDOMReady watch for the right url.
-        this._URL = location;
-      }
-
       this.browserRequire = BrowserLoader({
         window: this.doc.defaultView,
         useOnlyShared: true
       }).require;
 
-      iframe.setAttribute("aria-label", L10N.getStr("toolbox.label"));
-      let domHelper = new DOMHelpers(iframe.contentWindow);
+      if (this.win.location.href.startsWith(this._URL)) {
+        // Update the URL so that onceDOMReady watch for the right url.
+        this._URL = this.win.location.href;
+      }
+
+      let domReady = defer();
+      let domHelper = new DOMHelpers(this.win);
       domHelper.onceDOMReady(() => {
         domReady.resolve();
       }, this._URL);
 
       // Optimization: fire up a few other things before waiting on
       // the iframe being ready (makes startup faster)
 
       // Load the toolbox-level actor fronts and utilities now
@@ -621,24 +610,48 @@ Toolbox.prototype = {
                  (name, event) => {
                    this.switchToPreviousHost();
                    event.preventDefault();
                  });
 
     this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
     this.doc.addEventListener("focus", this._onFocus, true);
     this.win.addEventListener("unload", this.destroy);
+    this.win.addEventListener("message", this._onBrowserMessage, true);
   },
 
   _removeHostListeners: function () {
     // The host iframe's contentDocument may already be gone.
     if (this.doc) {
       this.doc.removeEventListener("keypress", this._splitConsoleOnKeypress, false);
       this.doc.removeEventListener("focus", this._onFocus, true);
       this.win.removeEventListener("unload", this.destroy);
+      this.win.removeEventListener("message", this._onBrowserMessage, true);
+    }
+  },
+
+  // Called whenever the chrome send a message
+  _onBrowserMessage: function (event) {
+    if (!event.data) {
+      return;
+    }
+    switch (event.data.name) {
+      case "switched-host":
+        this._onSwitchedHost(event.data);
+        break;
+      case "host-minimized":
+        if (this.hostType == Toolbox.HostType.BOTTOM) {
+          this._onBottomHostMinimized();
+        }
+        break;
+      case "host-maximized":
+        if (this.hostType == Toolbox.HostType.BOTTOM) {
+          this._onBottomHostMaximized();
+        }
+        break;
     }
   },
 
   _registerOverlays: function () {
     registerHarOverlay(this);
   },
 
   _saveSplitConsoleHeight: function () {
@@ -792,19 +805,16 @@ Toolbox.prototype = {
          the UI for it, and until bug 1173849 is fixed too. */
       minimizeBtn.setAttribute("hidden", "true");
 
       minimizeBtn.addEventListener("click", this._toggleMinimizeMode);
       dockBox.appendChild(minimizeBtn);
       // Show the button in its maximized state.
       this._onBottomHostMaximized();
 
-      // Update the label and icon when the state changes.
-      this._host.on("minimized", this._onBottomHostMinimized);
-      this._host.on("maximized", this._onBottomHostMaximized);
       // Maximize again when a tool gets selected.
       this.on("before-select", this._onToolSelectWhileMinimized);
       // Maximize and stop listening before the host type changes.
       this.once("host-will-change", this._onBottomHostWillChange);
     }
 
     if (this.hostType == Toolbox.HostType.WINDOW) {
       this.closeButton.setAttribute("hidden", "true");
@@ -853,37 +863,53 @@ Toolbox.prototype = {
     btn.className = "maximized";
 
     btn.setAttribute("title",
       L10N.getStr("toolboxDockButtons.bottom.minimize") + " " +
       this._getMinimizeButtonShortcutTooltip());
   },
 
   _onToolSelectWhileMinimized: function () {
-    this._host.maximize();
+    this.postMessage({
+      name: "maximize-host"
+    });
+  },
+
+  postMessage: function (msg) {
+    // We sometime try to send messages in middle of destroy(), where the
+    // toolbox iframe may already be detached and no longer have a parent.
+    if (this.win.parent) {
+      // Toolbox document is still chrome and disallow identifying message
+      // origin via event.source as it is null. So use a custom id.
+      msg.frameId = this.frameId;
+      this.win.parent.postMessage(msg, "*");
+    }
   },
 
   _onBottomHostWillChange: function () {
-    this._host.maximize();
+    this.postMessage({
+      name: "maximize-host"
+    });
 
-    this._host.off("minimized", this._onBottomHostMinimized);
-    this._host.off("maximized", this._onBottomHostMaximized);
     this.off("before-select", this._onToolSelectWhileMinimized);
   },
 
   _toggleMinimizeMode: function () {
     if (this.hostType !== Toolbox.HostType.BOTTOM) {
       return;
     }
 
     // Calculate the height to which the host should be minimized so the
     // tabbar is still visible.
     let toolbarHeight = this.tabbar.getBoxQuads({box: "content"})[0].bounds
                                                                     .height;
-    this._host.toggleMinimizeMode(toolbarHeight);
+    this.postMessage({
+      name: "toggle-minimize-mode",
+      toolbarHeight
+    });
   },
 
   /**
    * Add tabs to the toolbox UI for registered tools
    */
   _buildTabs: function () {
     for (let definition of gDevTools.getToolDefinitionArray()) {
       this._buildTabForTool(definition);
@@ -1606,31 +1632,36 @@ Toolbox.prototype = {
     let tab = this.doc.getElementById("toolbox-tab-" + id);
     tab && tab.removeAttribute("highlighted");
   },
 
   /**
    * Raise the toolbox host.
    */
   raise: function () {
-    this._host.raise();
+    this.postMessage({
+      name: "raise-host"
+    });
   },
 
   /**
    * Refresh the host's title.
    */
   _refreshHostTitle: function () {
     let title;
     if (this.target.name && this.target.name != this.target.url) {
       title = L10N.getFormatStr("toolbox.titleTemplate2", this.target.name,
                                                           this.target.url);
     } else {
       title = L10N.getFormatStr("toolbox.titleTemplate1", this.target.url);
     }
-    this._host.setTitle(title);
+    this.postMessage({
+      name: "set-host-title",
+      title
+    });
   },
 
   // Returns an instance of the preference actor
   get _preferenceFront() {
     return this.target.root.then(rootForm => {
       return getPreferenceFront(this.target.client, rootForm);
     });
   },
@@ -1799,57 +1830,23 @@ Toolbox.prototype = {
     // If non-top level frame is selected the toolbar button is
     // marked as 'checked' indicating that a child frame is active.
     if (!topFrameSelected && this.selectedFrameId) {
       button.setAttribute("checked", "true");
     }
   },
 
   /**
-   * Create a host object based on the given host type.
-   *
-   * Warning: some hosts require that the toolbox target provides a reference to
-   * the attached tab. Not all Targets have a tab property - make sure you
-   * correctly mix and match hosts and targets.
-   *
-   * @param {string} hostType
-   *        The host type of the new host object
-   *
-   * @return {Host} host
-   *        The created host object
-   */
-  _createHost: function (hostType, options) {
-    if (!Hosts[hostType]) {
-      throw new Error("Unknown hostType: " + hostType);
-    }
-
-    // clean up the toolbox if its window is closed
-    let newHost = new Hosts[hostType](this.target.tab, options);
-    newHost.on("window-closed", this.destroy);
-    return newHost;
-  },
-
-  /**
    * Switch to the last used host for the toolbox UI.
-   * This is determined by the devtools.toolbox.previousHost pref.
    */
   switchToPreviousHost: function () {
-    let hostType = Services.prefs.getCharPref(this._prefs.PREVIOUS_HOST);
-
-    // Handle the case where the previous host happens to match the current
-    // host. If so, switch to bottom if it's not already used, and side if not.
-    if (hostType === this.hostType) {
-      if (hostType === Toolbox.HostType.BOTTOM) {
-        hostType = Toolbox.HostType.SIDE;
-      } else {
-        hostType = Toolbox.HostType.BOTTOM;
-      }
-    }
-
-    return this.switchHost(hostType);
+    this.postMessage({
+      name: "switch-to-previous-host"
+    });
+    return this.once("host-changed");
   },
 
   /**
    * Switch to a new host for the toolbox UI. E.g. bottom, sidebar, window,
    * and focus the window when done.
    *
    * @param {string} hostType
    *        The host type of the new host object
@@ -1862,43 +1859,37 @@ Toolbox.prototype = {
     this.emit("host-will-change", hostType);
 
     // If we call swapFrameLoaders() when a tool if focused it leaves the
     // browser in a state where it thinks that the tool is focused but in
     // reality the content area is focused. Blurring the tool before calling
     // swapFrameLoaders() works around this issue.
     this.focusTool(this.currentToolId, false);
 
-    let newHost = this._createHost(hostType);
-    return newHost.create().then(iframe => {
-      // change toolbox document's parent to the new host
-      iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
-      iframe.swapFrameLoaders(this._host.frame);
+    // Host code on the chrome side will send back a message once the host
+    // switched
+    this.postMessage({
+      name: "switch-host",
+      hostType
+    });
 
-      this._host.off("window-closed", this.destroy);
-      this.destroyHost();
-
-      let prevHostType = this._host.type;
-      this._host = newHost;
+    return this.once("host-changed");
+  },
 
-      if (this.hostType != Toolbox.HostType.CUSTOM) {
-        Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
-        Services.prefs.setCharPref(this._prefs.PREVIOUS_HOST, prevHostType);
-      }
+  _onSwitchedHost: function ({ hostType }) {
+    this._hostType = hostType;
 
-      this._buildDockButtons();
-      this._addKeysToWindow();
+    this._buildDockButtons();
+    this._addKeysToWindow();
 
-      // Focus the tool to make sure keyboard shortcuts work straight away.
-      this.focusTool(this.currentToolId, true);
+    // Focus the tool to make sure keyboard shortcuts work straight away.
+    this.focusTool(this.currentToolId, true);
 
-      this.emit("host-changed");
-
-      this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
-    });
+    this.emit("host-changed");
+    this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
   },
 
   /**
    * Return if the tool is available as a tab (i.e. if it's checked
    * in the options panel). This is different from Toolbox.getPanel -
    * a tool could be registered but not yet opened in which case
    * isToolRegistered would return true but getPanel would return false.
    */
@@ -2061,26 +2052,16 @@ Toolbox.prototype = {
    *
    * @return The notification box component.
    */
   getNotificationBox: function () {
     return this.notificationBox;
   },
 
   /**
-   * Destroy the current host, and remove event listeners from its frame.
-   *
-   * @return {promise} to be resolved when the host is destroyed.
-   */
-  destroyHost: function () {
-    this._removeHostListeners();
-    return this._host.destroy();
-  },
-
-  /**
    * Remove all UI elements, detach from target and clear up
    */
   destroy: function () {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
       return this._destroyer;
     }
@@ -2178,20 +2159,28 @@ Toolbox.prototype = {
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
     deferred.resolve(settleAll(outstanding)
         .catch(console.error)
-        .then(() => this.destroyHost())
-        .catch(console.error)
         .then(() => {
-          this._win = null;
+          this._removeHostListeners();
+
+          // `location` may already be null if the toolbox document is already
+          // in process of destruction. Otherwise if it is still around, ensure
+          // releasing toolbox document and triggering cleanup thanks to unload
+          // event. We do that precisely here, before nullifying the target as
+          // various cleanup code depends on the target attribute to be still
+          // defined.
+          if (win.location) {
+            win.location.replace("about:blank");
+          }
 
           // Targets need to be notified that the toolbox is being torn down.
           // This is done after other destruction tasks since it may tear down
           // fronts and the debugger transport which earlier destroy methods may
           // require to complete.
           if (!this._target) {
             return null;
           }
@@ -2201,16 +2190,17 @@ Toolbox.prototype = {
           target.off("close", this.destroy);
           return target.destroy();
         }, console.error).then(() => {
           this.emit("destroyed");
 
           // Free _host after the call to destroyed in order to let a chance
           // to destroyed listeners to still query toolbox attributes
           this._host = null;
+          this._win = null;
           this._toolPanels.clear();
 
           // Force GC to prevent long GC pauses when running tests and to free up
           // memory in general when the toolbox is closed.
           if (flags.testing) {
             win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils)
               .garbageCollect();
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
@@ -38,17 +38,17 @@ add_task(function* () {
 
   let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
 
   // No way to wait for scrolling to end (Bug 1172171)
   // Rather than wait a max time; limit test to instant scroll behavior
   inspector.breadcrumbs.arrowScrollBox.scrollBehavior = "instant";
 
   yield toolbox.switchHost(Toolbox.HostType.WINDOW);
-  let hostWindow = toolbox._host._window;
+  let hostWindow = toolbox.win.parent;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
   hostWindow.resizeTo(640, 300);
 
   info("Testing transitions ltr");
   yield pushPref("intl.uidirection.en-US", "ltr");
   yield testBreadcrumbTransitions(hostWindow, inspector);
 
--- a/devtools/client/inspector/test/browser_inspector_portrait_mode.js
+++ b/devtools/client/inspector/test/browser_inspector_portrait_mode.js
@@ -6,17 +6,17 @@
 
 // Test that the inspector splitter is properly initialized in horizontal mode if the
 // inspector starts in portrait mode.
 
 add_task(function* () {
   let { inspector, toolbox } = yield openInspectorForURL(
     "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>", "window");
 
-  let hostWindow = toolbox._host._window;
+  let hostWindow = toolbox.win.parent;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
 
   let splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter");
 
   // If the inspector is not already in landscape mode.
   if (!splitter.classList.contains("vert")) {
     info("Resize toolbox window to force inspector to landscape mode");
@@ -41,17 +41,17 @@ add_task(function* () {
   info("Reopen inspector");
   ({ inspector, toolbox } = yield openInspector("window"));
 
   // Devtools window should still be 500px * 500px, inspector should still be in portrait.
   splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter");
   ok(splitter.classList.contains("horz"), "Splitter is in horizontal mode");
 
   info("Restore original window size");
-  toolbox._host._window.resizeTo(originalWidth, originalHeight);
+  toolbox.win.parent.resizeTo(originalWidth, originalHeight);
 });
 
 /**
  * Helper waiting for a class attribute mutation on the provided target. Returns a
  * promise.
  *
  * @param {Node} target
  *        Node to observe
--- a/devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -16,17 +16,17 @@ add_task(function* () {
   is(ui.editors.length, 2, "There are 2 style sheets initially");
 
   info("Changing toolbox host to a window.");
   yield toolbox.switchHost(Toolbox.HostType.WINDOW);
 
   let editor = yield ui.editors[0].getSourceEditor();
   let originalSourceEditor = editor.sourceEditor;
 
-  let hostWindow = toolbox._host._window;
+  let hostWindow = toolbox.win.parent;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
 
   // to check the caret is preserved
   originalSourceEditor.setCursor(originalSourceEditor.getPosition(4));
 
   info("Resizing window.");
   hostWindow.resizeTo(120, 480);
--- a/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
+++ b/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
@@ -91,10 +91,10 @@ function onGetNameFetch(evt, view) {
 }
 
 function onExpandClosure(results) {
   let prop = results[0].matchedProp;
   ok(prop, "matched the name property in the variables view");
 
   gVariablesView.window.focus();
   gJSTerm.once("sidebar-closed", finishTest);
-  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, gVariablesView.window);
 }