Bug 813031 - gDevTools needs an API review [toolbox]; r=paul
authorJoe Walker <jwalker@mozilla.com>
Thu, 13 Dec 2012 13:03:55 +0000
changeset 116049 fea63da9e80c53b8e6b9912da6a4a6fdc0df1874
parent 116048 a5de4b6885f49d7d88e716aa8063527466bb5064
child 116050 051762d4eced6a85c2bc1c5589b2e21d793736bc
push id19703
push useremorley@mozilla.com
push dateFri, 14 Dec 2012 16:52:46 +0000
treeherdermozilla-inbound@9d1bb565ab3a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs813031
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 813031 - gDevTools needs an API review [toolbox]; r=paul
browser/base/content/browser-sets.inc
browser/base/content/browser.js
browser/base/content/nsContextMenu.js
browser/devtools/commandline/CmdBreak.jsm
browser/devtools/commandline/CmdCalllog.jsm
browser/devtools/commandline/CmdCalllogChrome.jsm
browser/devtools/commandline/CmdConsole.jsm
browser/devtools/commandline/CmdDbg.jsm
browser/devtools/commandline/test/browser_cmd_calllog.js
browser/devtools/commandline/test/browser_cmd_calllog_chrome.js
browser/devtools/commandline/test/browser_cmd_commands.js
browser/devtools/commandline/test/browser_dbg_cmd.js
browser/devtools/commandline/test/browser_dbg_cmd_break.js
browser/devtools/commandline/test/head.js
browser/devtools/commandline/test/helpers.js
browser/devtools/debugger/DebuggerPanel.jsm
browser/devtools/debugger/test/browser_dbg_panesize-inner.js
browser/devtools/debugger/test/head.js
browser/devtools/framework/Sidebar.jsm
browser/devtools/framework/Target.jsm
browser/devtools/framework/ToolDefinitions.jsm
browser/devtools/framework/Toolbox.jsm
browser/devtools/framework/ToolboxHosts.jsm
browser/devtools/framework/connect/connect.js
browser/devtools/framework/gDevTools.jsm
browser/devtools/framework/test/browser_devtools_api.js
browser/devtools/framework/test/browser_new_activation_workflow.js
browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
browser/devtools/framework/test/browser_toolbox_hosts.js
browser/devtools/framework/test/browser_toolbox_ready.js
browser/devtools/framework/test/browser_toolbox_select_event.js
browser/devtools/framework/test/browser_toolbox_sidebar.js
browser/devtools/framework/test/browser_toolbox_tool_ready.js
browser/devtools/framework/test/head.js
browser/devtools/inspector/CmdInspect.jsm
browser/devtools/inspector/InspectorPanel.jsm
browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
browser/devtools/inspector/test/browser_inspector_bug_566084_location_changed.js
browser/devtools/inspector/test/browser_inspector_initialization.js
browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
browser/devtools/inspector/test/browser_inspector_sidebarstate.js
browser/devtools/inspector/test/head.js
browser/devtools/layoutview/test/browser_layoutview.js
browser/devtools/markupview/test/browser_inspector_markup_edit.js
browser/devtools/markupview/test/browser_inspector_markup_mutation.js
browser/devtools/markupview/test/browser_inspector_markup_navigation.js
browser/devtools/responsivedesign/responsivedesign.jsm
browser/devtools/responsivedesign/test/head.js
browser/devtools/scratchpad/scratchpad.js
browser/devtools/shared/EventEmitter.jsm
browser/devtools/shared/test/browser_toolbar_basic.js
browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
browser/devtools/styleeditor/StyleEditorPanel.jsm
browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
browser/devtools/styleeditor/test/head.js
browser/devtools/styleinspector/CssHtmlTree.jsm
browser/devtools/styleinspector/StyleInspector.jsm
browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
browser/devtools/styleinspector/test/browser_ruleview_focus.js
browser/devtools/styleinspector/test/head.js
browser/devtools/tilt/TiltVisualizer.jsm
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/WebConsolePanel.jsm
browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js
browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
browser/devtools/webconsole/test/browser_webconsole_view_source.js
browser/devtools/webconsole/test/head.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
toolkit/devtools/Console.jsm
toolkit/devtools/webconsole/WebConsoleUtils.jsm
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -83,25 +83,25 @@
     <command id="cmd_fullZoomReduce"  oncommand="FullZoom.reduce()"/>
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
-    <command id="Tools:DevToolbox" oncommand="gDevTools.toggleToolboxCommand(gBrowser);"/>
+    <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
-    <command id="Tools:DevToolsConnect" oncommand="DevToolsXULCommands.openConnectScreen(gBrowser)"/>
+    <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing"
 #ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
       oncommand="OpenBrowserWindow({private: true});"/>
 #else
       oncommand="gPrivateBrowsingUI.toggleMode();"/>
 #endif
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1456,17 +1456,17 @@ var gBrowserInit = {
     let responsiveUIEnabled = gPrefService.getBoolPref("devtools.responsiveUI.enabled");
     if (responsiveUIEnabled) {
       let cmd = document.getElementById("Tools:ResponsiveUI");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
     }
 
     // Add Devtools menuitems and listeners
-    gDevTools.registerBrowserWindow(window);
+    gDevToolsBrowser.registerBrowserWindow(window);
 
     let appMenuButton = document.getElementById("appmenu-button");
     let appMenuPopup = document.getElementById("appmenu-popup");
     if (appMenuButton && appMenuPopup) {
       let appMenuOpening = null;
       appMenuButton.addEventListener("mousedown", function(event) {
         if (event.button == 0)
           appMenuOpening = new Date();
@@ -1500,17 +1500,17 @@ var gBrowserInit = {
 
   onUnload: function() {
     // In certain scenarios it's possible for unload to be fired before onload,
     // (e.g. if the window is being closed after browser.js loads but before the
     // load completes). In that case, there's nothing to do here.
     if (!gStartupRan)
       return;
 
-    gDevTools.forgetBrowserWindow(window);
+    gDevToolsBrowser.forgetBrowserWindow(window);
 
     // First clean up services initialized in gBrowserInit.onLoad (or those whose
     // uninit methods don't depend on the services having been initialized).
     allTabs.uninit();
 
     CombinedStopReload.uninit();
 
     gGestureSupport.init(false);
@@ -7421,17 +7421,17 @@ var TabContextMenu = {
     document.getElementById("context_tabViewMenu").hidden =
       (this.contextTab.pinned || !TabView.firstUseExperienced);
   }
 };
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DevToolsXULCommands",
+XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser",
                                   "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () {
   let tempScope = {};
   Cu.import("resource:///modules/HUDService.jsm", tempScope);
   try {
     return tempScope.HUDService.consoleUI;
   }
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -416,27 +416,21 @@ nsContextMenu.prototype = {
     }
     this.showItem("context-media-sep-commands",  onMedia);
   },
 
   inspectNode: function CM_inspectNode() {
     let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
     let imported = {};
     Cu.import("resource:///modules/devtools/Target.jsm", imported);
-    var target = imported.TargetFactory.forTab(gBrowser.selectedTab);
-    let inspector = gDevTools.getPanelForTarget("inspector", target);
-    if (inspector && inspector.isReady) {
-      inspector.selection.setNode(this.target);
-    } else {
-      let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-      toolbox.once("inspector-ready", function(event, panel) {
-        let inspector = gDevTools.getPanelForTarget("inspector", target);
-        inspector.selection.setNode(this.target, "browser-context-menu");
-      }.bind(this));
-    }
+    let tt = imported.TargetFactory.forTab(gBrowser.selectedTab);
+    return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
+      let inspector = toolbox.getCurrentPanel();
+      inspector.selection.setNode(this.target, "browser-context-menu");
+    }.bind(this));
   },
 
   // Set various context menu attributes based on the state of the world.
   setTarget: function (aNode, aRangeParent, aRangeOffset) {
     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     if (aNode.namespaceURI == xulNS ||
         aNode.nodeType == Node.DOCUMENT_NODE ||
         this.isTargetAFormControl(aNode)) {
--- a/browser/devtools/commandline/CmdBreak.jsm
+++ b/browser/devtools/commandline/CmdBreak.jsm
@@ -22,31 +22,27 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  * 'break' command
  */
 gcli.addCommand({
   name: "break",
   description: gcli.lookup("breakDesc"),
   manual: gcli.lookup("breakManual")
 });
 
-
 /**
  * 'break list' command
  */
 gcli.addCommand({
   name: "break list",
   description: gcli.lookup("breaklistDesc"),
   returnType: "html",
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
+    let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
+      return gcli.lookup("debuggerStopped");
     }
 
     let breakpoints = dbg.getAllBreakpoints();
 
     if (Object.keys(breakpoints).length === 0) {
       return gcli.lookup("breaklistNone");
     }
 
@@ -58,17 +54,16 @@ gcli.addCommand({
                                     breakpoint.location.line]);
       reply += "<li>" + text + "</li>";
     };
     reply += "</ol>";
     return reply;
   }
 });
 
-
 /**
  * 'break add' command
  */
 gcli.addCommand({
   name: "break add",
   description: gcli.lookup("breakaddDesc"),
   manual: gcli.lookup("breakaddManual")
 });
@@ -79,22 +74,19 @@ gcli.addCommand({
 gcli.addCommand({
   name: "break add line",
   description: gcli.lookup("breakaddlineDesc"),
   params: [
     {
       name: "file",
       type: {
         name: "selection",
-        data: function() {
-          let gBrowser = HUDService.currentContext().gBrowser;
-          let target = TargetFactory.forTab(gBrowser.selectedTab);
-          let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
+        data: function(args, context) {
           let files = [];
+          let dbg = getPanel(context, "jsdebugger");
           if (dbg) {
             let sourcesView = dbg.panelWin.DebuggerView.Sources;
             for (let item in sourcesView) {
               files.push(item.value);
             }
           }
           return files;
         }
@@ -106,83 +98,87 @@ gcli.addCommand({
       type: { name: "number", min: 1, step: 10 },
       description: gcli.lookup("breakaddlineLineDesc")
     }
   ],
   returnType: "html",
   exec: function(args, context) {
     args.type = "line";
 
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
+    let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
+      return gcli.lookup("debuggerStopped");
     }
-    var promise = context.createPromise();
+    var deferred = context.defer();
     let position = { url: args.file, line: args.line };
     dbg.addBreakpoint(position, function(aBreakpoint, aError) {
       if (aError) {
-        promise.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
+        deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
         return;
       }
-      promise.resolve(gcli.lookup("breakaddAdded"));
+      deferred.resolve(gcli.lookup("breakaddAdded"));
     });
-    return promise;
+    return deferred.promise;
   }
 });
 
 
 /**
  * 'break del' command
  */
 gcli.addCommand({
   name: "break del",
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
       name: "breakid",
       type: {
         name: "number",
         min: 0,
-        max: function() {
-          let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-          let target = TargetFactory.forTab(gBrowser.selectedTab);
-          let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
-          if (!dbg) {
-            return gcli.lookup("breakaddDebuggerStopped");
-          }
-          return Object.keys(dbg.getAllBreakpoints()).length - 1;
+        max: function(args, context) {
+          let dbg = getPanel(context, "jsdebugger");
+          return dbg == null ?
+              null :
+              Object.keys(dbg.getAllBreakpoints()).length - 1;
         },
       },
       description: gcli.lookup("breakdelBreakidDesc")
     }
   ],
   returnType: "html",
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
+    let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
+      return gcli.lookup("debuggerStopped");
     }
 
     let breakpoints = dbg.getAllBreakpoints();
     let id = Object.keys(breakpoints)[args.breakid];
     if (!id || !(id in breakpoints)) {
       return gcli.lookup("breakNotFound");
     }
 
-    let promise = context.createPromise();
+    let deferred = context.defer();
     try {
       dbg.removeBreakpoint(breakpoints[id], function() {
-        promise.resolve(gcli.lookup("breakdelRemoved"));
+        deferred.resolve(gcli.lookup("breakdelRemoved"));
       });
     } catch (ex) {
       // If the debugger has been closed already, don't scare the user.
-      promise.resolve(gcli.lookup("breakdelRemoved"));
+      deferred.resolve(gcli.lookup("breakdelRemoved"));
     }
-    return promise;
+    return deferred.promise;
   }
 });
+
+/**
+ * A helper to go from a command context to a debugger panel
+ */
+function getPanel(context, id) {
+  if (context == null) {
+    return undefined;
+  }
+
+  let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = gDevTools.getToolbox(target);
+  return toolbox == null ? undefined : toolbox.getPanel(id);
+}
--- a/browser/devtools/commandline/CmdCalllog.jsm
+++ b/browser/devtools/commandline/CmdCalllog.jsm
@@ -50,17 +50,17 @@ gcli.addCommand({
       // BUG 773652 -  Make the output from the GCLI calllog command nicer
       contentWindow.console.log("Method call: " + this.callDescription(frame));
     }.bind(this);
 
     debuggers.push(dbg);
 
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.openToolboxForTab(target, "webconsole");
+    gDevTools.showToolbox(target, "webconsole");
 
     return gcli.lookup("calllogStartReply");
   },
 
   callDescription: function(frame) {
     let name = "<anonymous>";
     if (frame.callee.name) {
       name = frame.callee.name;
--- a/browser/devtools/commandline/CmdCalllogChrome.jsm
+++ b/browser/devtools/commandline/CmdCalllogChrome.jsm
@@ -107,17 +107,17 @@ gcli.addCommand({
     dbg.onEnterFrame = function(frame) {
       // BUG 773652 -  Make the output from the GCLI calllog command nicer
       contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") +
                                 ": " + this.callDescription(frame));
     }.bind(this);
 
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.openToolboxForTab(target, "webconsole");
+    gDevTools.showToolbox(target, "webconsole");
 
     return gcli.lookup("calllogChromeStartReply");
   },
 
   valueToString: function(value) {
     if (typeof value !== "object" || value === null)
       return uneval(value);
     return "[object " + value.class + "]";
--- a/browser/devtools/commandline/CmdConsole.jsm
+++ b/browser/devtools/commandline/CmdConsole.jsm
@@ -45,24 +45,24 @@ gcli.addCommand({
  * 'console close' command
  */
 gcli.addCommand({
   name: "console close",
   description: gcli.lookup("consolecloseDesc"),
   exec: function Command_consoleClose(args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.closeToolbox(target);
+    return gDevTools.closeToolbox(target);
   }
 });
 
 /**
  * 'console open' command
  */
 gcli.addCommand({
   name: "console open",
   description: gcli.lookup("consoleopenDesc"),
   exec: function Command_consoleOpen(args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.openToolboxForTab(target, "webconsole");
+    return gDevTools.showToolbox(target, "webconsole");
   }
 });
--- a/browser/devtools/commandline/CmdDbg.jsm
+++ b/browser/devtools/commandline/CmdDbg.jsm
@@ -28,150 +28,149 @@ gcli.addCommand({
  */
 gcli.addCommand({
   name: "dbg open",
   description: gcli.lookup("dbgOpen"),
   params: [],
   exec: function (args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.openToolboxForTab(target, "jsdebugger");
+    return gDevTools.showToolbox(target, "jsdebugger");
   }
 });
 
 /**
  * 'dbg close' command
  */
 gcli.addCommand({
   name: "dbg close",
   description: gcli.lookup("dbgClose"),
   params: [],
   exec: function (args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-
-    if (dbg /* FIXME: and debugger panel is currently active */) {
-      gDevTools.closeToolbox(target);
-    }
+    return gDevTools.closeToolbox(target);
   }
 });
 
 /**
  * 'dbg interrupt' command
  */
 gcli.addCommand({
   name: "dbg interrupt",
   description: gcli.lookup("dbgInterrupt"),
   params: [],
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+    let dbg = getPanel(context, "jsdebugger");
+    if (!dbg) {
+      return gcli.lookup("debuggerStopped");
+    }
 
-    if (dbg) {
-      let controller = dbg._controller;
-      let thread = controller.activeThread;
-      if (!thread.paused) {
-        thread.interrupt();
-      }
+    let controller = dbg._controller;
+    let thread = controller.activeThread;
+    if (!thread.paused) {
+      thread.interrupt();
     }
   }
 });
 
 /**
  * 'dbg continue' command
  */
 gcli.addCommand({
   name: "dbg continue",
   description: gcli.lookup("dbgContinue"),
   params: [],
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+    let dbg = getPanel(context, "jsdebugger");
+    if (!dbg) {
+      return gcli.lookup("debuggerStopped");
+    }
 
-    if (dbg) {
-      let controller = dbg._controller;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.resume();
-      }
+    let controller = dbg._controller;
+    let thread = controller.activeThread;
+    if (thread.paused) {
+      thread.resume();
     }
   }
 });
 
-
 /**
  * 'dbg step' command
  */
 gcli.addCommand({
   name: "dbg step",
   description: gcli.lookup("dbgStepDesc"),
   manual: gcli.lookup("dbgStepManual")
 });
 
-
 /**
  * 'dbg step over' command
  */
 gcli.addCommand({
   name: "dbg step over",
   description: gcli.lookup("dbgStepOverDesc"),
   params: [],
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+    let dbg = getPanel(context, "jsdebugger");
+    if (!dbg) {
+      return gcli.lookup("debuggerStopped");
+    }
 
-    if (dbg) {
-      let controller = dbg._controller;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepOver();
-      }
+    let controller = dbg._controller;
+    let thread = controller.activeThread;
+    if (thread.paused) {
+      thread.stepOver();
     }
   }
 });
 
 /**
  * 'dbg step in' command
  */
 gcli.addCommand({
   name: 'dbg step in',
   description: gcli.lookup("dbgStepInDesc"),
   params: [],
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+    let dbg = getPanel(context, "jsdebugger");
+    if (!dbg) {
+      return gcli.lookup("debuggerStopped");
+    }
 
-    if (dbg) {
-      let controller = dbg._controller;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepIn();
-      }
+    let controller = dbg._controller;
+    let thread = controller.activeThread;
+    if (thread.paused) {
+      thread.stepIn();
     }
   }
 });
 
 /**
  * 'dbg step over' command
  */
 gcli.addCommand({
   name: 'dbg step out',
   description: gcli.lookup("dbgStepOutDesc"),
   params: [],
   exec: function(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+    let dbg = getPanel(context, "jsdebugger");
+    if (!dbg) {
+      return gcli.lookup("debuggerStopped");
+    }
 
-    if (dbg) {
-      let controller = dbg._controller;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepOut();
-      }
+    let controller = dbg._controller;
+    let thread = controller.activeThread;
+    if (thread.paused) {
+      thread.stepOut();
     }
   }
 });
+
+/**
+ * A helper to go from a command context to a debugger panel
+ */
+function getPanel(context, id) {
+  let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = gDevTools.getToolbox(target);
+  return toolbox == null ? undefined : toolbox.getPanel(id);
+}
--- a/browser/devtools/commandline/test/browser_cmd_calllog.js
+++ b/browser/devtools/commandline/test/browser_cmd_calllog.js
@@ -67,16 +67,17 @@ function testCallLogExec() {
 
     let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
     is(labels.length, 0, "no output in console");
 
     DeveloperToolbarTest.exec({
       typed: "console close",
       args: {},
       blankOutput: true,
+      completed: false,
     });
   });
 
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
   DeveloperToolbarTest.exec({
     typed: "calllog start",
     args: { },
--- a/browser/devtools/commandline/test/browser_cmd_calllog_chrome.js
+++ b/browser/devtools/commandline/test/browser_cmd_calllog_chrome.js
@@ -93,19 +93,21 @@ function testCallLogExec() {
 
     let labels = hud.jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
     is(labels.length, 0, "no output in console");
 
     DeveloperToolbarTest.exec({
       typed: "console close",
       args: {},
       blankOutput: true,
+      completed: false,
     });
 
     executeSoon(finish);
   }
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
   DeveloperToolbarTest.exec({
     typed: "calllog chromestart javascript \"({a1: function() {this.a2()},a2: function() {}});\"",
     outputMatch: /Call logging started/,
+    completed: false,
   });
 }
--- a/browser/devtools/commandline/test/browser_cmd_commands.js
+++ b/browser/devtools/commandline/test/browser_cmd_commands.js
@@ -33,16 +33,17 @@ function testConsole(browser, tab) {
   }
 
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
   DeveloperToolbarTest.exec({
     typed: "console open",
     args: {},
     blankOutput: true,
+    completed: false,
   });
 
   function onExecute() {
     let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
     ok(labels.length > 0, "output for pprint(window)");
 
     DeveloperToolbarTest.exec({
       typed: "console clear",
--- a/browser/devtools/commandline/test/browser_dbg_cmd.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -41,17 +41,17 @@ function testCommands(dbg, cmd) {
                       cmd("dbg continue", function() {
                         is(output.value, "dbg continue", "debugger continued");
                         DeveloperToolbarTest.exec({
                           typed: "dbg close",
                           blankOutput: true
                         });
 
                         let target = TargetFactory.forTab(gBrowser.selectedTab);
-                        ok(!gDevTools.getToolboxForTarget(target),
+                        ok(!gDevTools.getToolbox(target),
                           "Debugger was closed.");
                         finish();
                       });
                     });
                   });
                 });
               });
             });
@@ -63,26 +63,32 @@ function testCommands(dbg, cmd) {
         typed: "dbg continue",
         blankOutput: true
       });
     });
   });
 }
 
 function testDbgCmd() {
-  DeveloperToolbarTest.exec({
+  let output = DeveloperToolbarTest.exec({
     typed: "dbg open",
-    blankOutput: true
+    blankOutput: true,
+    completed: false,
   });
 
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.getToolboxForTarget(target);
+  output.onChange.add(onOpenComplete);
+}
 
-  toolbox.once("jsdebugger-ready", function dbgReady() {
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+function onOpenComplete(ev) {
+  let output = ev.output;
+  output.onChange.remove(onOpenComplete);
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
+    let dbg = toolbox.getCurrentPanel();
     ok(dbg, "DebuggerPanel exists");
 
     function cmd(aTyped, aCallback) {
       dbg._controller.activeThread.addOneTimeListener("paused", aCallback);
       DeveloperToolbarTest.exec({
         typed: aTyped,
         blankOutput: true
       });
--- a/browser/devtools/commandline/test/browser_dbg_cmd_break.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd_break.js
@@ -35,19 +35,18 @@ function testBreakCommands() {
   helpers.check({
     input:  'break add line',
     hints:                ' <file> <line>',
     markup: 'VVVVVVVVVVVVVV',
     status: 'ERROR'
   });
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.openToolboxForTab(target, "jsdebugger");
-  toolbox.once("jsdebugger-ready", function dbgReady() {
-    let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+  gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
+    let dbg = toolbox.getCurrentPanel();
     ok(dbg, "DebuggerPanel exists");
     dbg.once("connected", function() {
       // Wait for the initial resume...
       dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
         dbg._view.Variables.lazyEmpty = false;
 
         var client = dbg.panelWin.gClient;
         var framesAdded = DeveloperToolbarTest.checkCalled(function() {
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -6,42 +6,65 @@ const TEST_BASE_HTTP = "http://example.c
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/commandline/test/";
 
 let console = (function() {
   let tempScope = {};
   Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
   return tempScope.console;
 })();
 
+let TargetFactory = (function() {
+  let tempScope = {};
+  Components.utils.import("resource:///modules/devtools/Target.jsm", tempScope);
+  return tempScope.TargetFactory;
+})();
+
+let Promise = (function() {
+  let tempScope = {};
+  Components.utils.import("resource://gre/modules/commonjs/promise/core.js", tempScope);
+  return tempScope.Promise;
+})();
+
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this);
 Services.scriptloader.loadSubScript(testDir + "/helpers_perwindowpb.js", this);
 
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  content.location = aURL;
+  if (aURL != null) {
+    content.location = aURL;
+  }
+
+  let deferred = Promise.defer();
 
   let tab = gBrowser.selectedTab;
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
   let browser = gBrowser.getBrowserForTab(tab);
 
   function onTabLoad() {
     browser.removeEventListener("load", onTabLoad, true);
-    aCallback(browser, tab, browser.contentDocument);
+
+    if (aCallback != null) {
+      aCallback(browser, tab, browser.contentDocument);
+    }
+
+    deferred.resolve({ browser: browser, tab: tab, target: target });
   }
 
   browser.addEventListener("load", onTabLoad, true);
+  return deferred.promise;
 }
 
 registerCleanupFunction(function tearDown() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 
   console = undefined;
--- a/browser/devtools/commandline/test/helpers.js
+++ b/browser/devtools/commandline/test/helpers.js
@@ -330,39 +330,39 @@ DeveloperToolbarTest.exec = function DTT
     });
   }
 
   let displayed = DeveloperToolbar.outputPanel._div.textContent;
 
   if (tests.outputMatch) {
     var doTest = function(match, against) {
       if (!match.test(against)) {
-        ok(false, "html output for " + typed + " against " + match.source +
+        info("Actual textContent: '" + against + "'");
+        ok(false, "mismatched output for " + typed + " against " + match.source +
                 " (textContent sent to info)");
-        info("Actual textContent");
-        info(against);
       }
     }
     if (Array.isArray(tests.outputMatch)) {
       tests.outputMatch.forEach(function(match) {
         doTest(match, displayed);
       });
     }
     else {
       doTest(tests.outputMatch, displayed);
     }
   }
 
   if (tests.blankOutput != null) {
     if (!/^$/.test(displayed)) {
-      ok(false, "html output for " + typed + " (textContent sent to info)");
-      info("Actual textContent");
-      info(displayed);
+      info("Actual textContent: '" + displayed + "'");
+      ok(false, "non-blank output for " + typed + " (textContent sent to info)");
     }
   }
+
+  return output;
 };
 
 /**
  * Quick wrapper around the things you need to do to run DeveloperToolbar
  * command tests:
  * - Set the pref 'devtools.toolbar.enabled' to true
  * - Add a tab pointing at |uri|
  * - Open the DeveloperToolbar
@@ -441,16 +441,17 @@ DeveloperToolbarTest.test = function DTT
           DeveloperToolbarTest._checkFinish();
         }
       }
       else {
         try {
           target(browser, tab);
         }
         catch (ex) {
+console.error(ex.stack);
           ok(false, "" + ex);
           DeveloperToolbarTest._finish();
           throw ex;
         }
       }
     });
   }
 
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -1,71 +1,82 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 "use strict";
 
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 function DebuggerPanel(iframeWindow, toolbox) {
+  this.panelWin = iframeWindow;
   this._toolbox = toolbox;
-  this._controller = iframeWindow.DebuggerController;
-  this._view = iframeWindow.DebuggerView;
+
+  this._controller = this.panelWin.DebuggerController;
+  this._view = this.panelWin.DebuggerView;
   this._controller._target = this.target;
   this._bkp = this._controller.Breakpoints;
-  this.panelWin = iframeWindow;
-
-  this._ensureOnlyOneRunningDebugger();
-  if (!this.target.isRemote) {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-  }
-
-  let onDebuggerLoaded = function () {
-    iframeWindow.removeEventListener("Debugger:Loaded", onDebuggerLoaded, true);
-    this.setReady();
-  }.bind(this);
-
-  let onDebuggerConnected = function () {
-    iframeWindow.removeEventListener("Debugger:Connected",
-      onDebuggerConnected, true);
-    this.emit("connected");
-  }.bind(this);
-
-  iframeWindow.addEventListener("Debugger:Loaded", onDebuggerLoaded, true);
-  iframeWindow.addEventListener("Debugger:Connected",
-    onDebuggerConnected, true);
 
   new EventEmitter(this);
 }
 
 DebuggerPanel.prototype = {
+  /**
+   * open is effectively an asynchronous constructor
+   */
+  open: function DebuggerPanel_open() {
+    let deferred = Promise.defer();
+
+    this._ensureOnlyOneRunningDebugger();
+
+    if (!this.target.isRemote) {
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+    }
+
+    let onDebuggerLoaded = function () {
+      this.panelWin.removeEventListener("Debugger:Loaded",
+                                        onDebuggerLoaded, true);
+      this._isReady = true;
+      this.emit("ready");
+      deferred.resolve(this);
+    }.bind(this);
+
+    let onDebuggerConnected = function () {
+      this.panelWin.removeEventListener("Debugger:Connected",
+                                        onDebuggerConnected, true);
+      this.emit("connected");
+    }.bind(this);
+
+    this.panelWin.addEventListener("Debugger:Loaded", onDebuggerLoaded, true);
+    this.panelWin.addEventListener("Debugger:Connected",
+                                   onDebuggerConnected, true);
+
+    return deferred.promise;
+  },
+
   // DevToolPanel API
   get target() this._toolbox.target,
 
   get isReady() this._isReady,
 
-  setReady: function() {
-    this._isReady = true;
-    this.emit("ready");
-  },
-
   destroy: function() {
+    return Promise.resolve(null);
   },
 
   // DebuggerPanel API
 
   addBreakpoint: function() {
     this._bkp.addBreakpoint.apply(this._bkp, arguments);
   },
 
--- a/browser/devtools/debugger/test/browser_dbg_panesize-inner.js
+++ b/browser/devtools/debugger/test/browser_dbg_panesize-inner.js
@@ -4,22 +4,21 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 function test() {
   var tab1 = addTab(TAB1_URL, function() {
     gBrowser.selectedTab = tab1;
     let target1 = TargetFactory.forTab(tab1);
 
-    ok(!gDevTools.getPanelForTarget("jsdebugger", target1),
+    ok(!gDevTools.getToolbox(target1),
       "Shouldn't have a debugger panel for this tab yet.");
 
-    let toolbox = gDevTools.openToolboxForTab(target1, "jsdebugger");
-    toolbox.once("jsdebugger-ready", function dbgReady() {
-      let dbg = gDevTools.getPanelForTarget("jsdebugger", target1);
+    gDevTools.showToolbox(target1, "jsdebugger").then(function(toolbox) {
+      let dbg = toolbox.getCurrentPanel();
       ok(dbg, "We should have a debugger panel.");
 
       let preferredSfw = Services.prefs.getIntPref("devtools.debugger.ui.stackframes-width");
       let preferredBpw = Services.prefs.getIntPref("devtools.debugger.ui.variables-width");
       let someWidth1, someWidth2;
 
       do {
         someWidth1 = parseInt(Math.random() * 200) + 100;
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -87,33 +87,30 @@ function removeTab(aTab, aWindow) {
   let targetBrowser = targetWindow.gBrowser;
 
   targetBrowser.removeTab(aTab);
 }
 
 function closeDebuggerAndFinish(aRemoteFlag, aCallback, aWindow) {
   let debuggerClosed = false;
   let debuggerDisconnected = false;
-  // let targetWindow = aWindow || window;
   ok(gTab, "There is a gTab to use for getting a toolbox reference");
   let target = TargetFactory.forTab(gTab);
 
-  // let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
   window.addEventListener("Debugger:Shutdown", function cleanup() {
     window.removeEventListener("Debugger:Shutdown", cleanup, false);
     debuggerDisconnected = true;
     _maybeFinish();
   }, false);
 
-  let toolbox = gDevTools.getToolboxForTarget(target);
-  toolbox.once("destroyed", function() {
+  let toolbox = gDevTools.getToolbox(target);
+  toolbox.destroy().then(function() {
     debuggerClosed = true;
     _maybeFinish();
   });
-  toolbox.destroy();
 
   function _maybeFinish() {
     if (debuggerClosed && debuggerDisconnected) {
       if (!aCallback)
         aCallback = finish;
       aCallback();
     }
   }
@@ -157,34 +154,36 @@ function attach_thread_actor_for_url(aCl
         aCallback(actor);
       });
     });
   });
 }
 
 function wait_for_connect_and_resume(aOnDebugging, aTab) {
   let target = TargetFactory.forTab(aTab);
-  let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
-  dbg.once("connected", function dbgConnected() {
-    // Wait for the initial resume...
-    dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
-      aOnDebugging();
+  gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
+    let dbg = toolbox.getCurrentPanel();
+    dbg.once("connected", function dbgConnected() {
+      // Wait for the initial resume...
+      dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
+        aOnDebugging();
+      });
     });
   });
 }
 
 function debug_tab_pane(aURL, aOnDebugging) {
   let tab = addTab(aURL, function() {
     gBrowser.selectedTab = gTab;
     let debuggee = gBrowser.selectedTab.linkedBrowser.contentWindow.wrappedJSObject;
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolboxForTab(target, "jsdebugger");
-    toolbox.once("jsdebugger-ready", function dbgReady() {
-      let dbg = gDevTools.getPanelForTarget("jsdebugger", target);
+
+    gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
+      let dbg = toolbox.getCurrentPanel();
       dbg.once("connected", function() {
         // Wait for the initial resume...
         dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
           dbg._view.Variables.lazyEmpty = false;
           aOnDebugging(tab, debuggee, dbg);
         });
       });
     });
--- a/browser/devtools/framework/Sidebar.jsm
+++ b/browser/devtools/framework/Sidebar.jsm
@@ -1,20 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
-const Cc = Components.classes;
-const Cu = Components.utils;
-const Ci = Components.interfaces;
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["ToolSidebar"];
 
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /**
  * ToolSidebar provides methods to register tabs in the sidebar.
  * It's assumed that the sidebar contains a xul:tabbox.
  *
@@ -126,17 +125,16 @@ ToolSidebar.prototype = {
         this.emit(previousTool + "-unselected");
       }
 
       this.emit(this._currentTool + "-selected");
       this.emit("select", this._currentTool);
     }
   },
 
-
   /**
    * Toggle sidebar's visibility state.
    */
   toggle: function ToolSidebar_toggle() {
     if (this._tabbox.hasAttribute("hidden")) {
       this.show();
     } else {
       this.hide();
@@ -168,24 +166,31 @@ ToolSidebar.prototype = {
     let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
     return panel.firstChild.contentWindow;
   },
 
   /**
    * Clean-up.
    */
   destroy: function ToolSidebar_destroy() {
+    if (this._destroyed) {
+      return Promise.resolve(null);
+    }
+    this._destroyed = true;
+
     this._tabbox.removeEventListener("select", this, true);
 
     while (this._tabbox.tabpanels.hasChildNodes()) {
       this._tabbox.tabpanels.removeChild(this._tabbox.tabpanels.firstChild);
     }
 
     while (this._tabbox.tabs.hasChildNodes()) {
       this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
     }
 
     this._tabs = null;
     this._tabbox = null;
     this._panelDoc = null;
     this._toolPanel = null;
+
+    return Promise.resolve(null);
   },
 }
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -1,20 +1,21 @@
 /* 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/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "TargetFactory" ];
 
-const Cu = Components.utils;
-const Ci = Components.interfaces;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 
 const targets = new WeakMap();
 
 /**
  * Functions for creating Targets
  */
 this.TargetFactory = {
@@ -223,34 +224,35 @@ TabTarget.prototype = {
           this.emit("visible", event);
         } else {
           this.emit("hidden", event);
         }
         break;
     }
   },
 
-
   /**
    * Target is not alive anymore.
    */
   destroy: function() {
-    if (this._destroyed) {
-      return;
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
+      this._webProgressListener.target = null;
+      this._webProgressListener = null;
+      this.tab.removeEventListener("TabClose", this);
+      this.tab.parentNode.removeEventListener("TabSelect", this);
+      this.emit("close");
+
+      targets.delete(this._tab);
+      this._tab = null;
     }
-    this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
-    this._webProgressListener.target = null;
-    this._webProgressListener = null;
-    this.tab.removeEventListener("TabClose", this);
-    this.tab.parentNode.removeEventListener("TabSelect", this);
-    this._destroyed = true;
-    this.emit("close");
 
-    targets.delete(this._tab);
-    this._tab = null;
+    return Promise.resolve(null);
   },
 
   toString: function() {
     return 'TabTarget:' + this.tab;
   },
 };
 
 
@@ -322,16 +324,32 @@ WindowTarget.prototype = {
   get url() {
     return this._window.document.location.href;
   },
 
   get isRemote() {
     return false;
   },
 
+  /**
+   * Target is not alive anymore.
+   */
+  destroy: function() {
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      this.emit("close");
+
+      targets.delete(this._window);
+      this._window = null;
+    }
+
+    return Promise.resolve(null);
+  },
+
   toString: function() {
     return 'WindowTarget:' + this.window;
   },
 };
 
 /**
  * A RemoteTarget represents a page living in a remote Firefox instance.
  */
@@ -365,25 +383,33 @@ RemoteTarget.prototype = {
   get client() this._client,
 
   get form() this._form,
 
   /**
    * Target is not alive anymore.
    */
   destroy: function RT_destroy() {
-    if (this._destroyed) {
-      return;
+    // 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.promise;
     }
+
+    this._destroyer = Promise.defer();
+
     this.client.removeListener("tabNavigated", this._onTabNavigated);
     this.client.removeListener("tabDetached", this.destroy);
 
     this._client.close(function onClosed() {
       this._client = null;
-      this._destroyed = true;
       this.emit("close");
+
+      this._destroyer.resolve(null);
     }.bind(this));
+
+    return this._destroyer.promise;
   },
 
   toString: function() {
     return 'RemoteTarget:' + this.form.actor;
   },
 };
--- a/browser/devtools/framework/ToolDefinitions.jsm
+++ b/browser/devtools/framework/ToolDefinitions.jsm
@@ -61,17 +61,18 @@ let webConsoleDefinition = {
   ordinal: 0,
   icon: "chrome://browser/skin/devtools/webconsole-tool-icon.png",
   url: "chrome://browser/content/devtools/webconsole.xul",
   label: l10n("ToolboxWebconsole.label", webConsoleStrings),
   isTargetSupported: function(target) {
     return true;
   },
   build: function(iframeWindow, toolbox) {
-    return new WebConsolePanel(iframeWindow, toolbox);
+    let panel = new WebConsolePanel(iframeWindow, toolbox);
+    return panel.open();
   }
 };
 
 let debuggerDefinition = {
   id: "jsdebugger",
   key: l10n("open.commandkey", debuggerStrings),
   accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
@@ -81,17 +82,18 @@ let debuggerDefinition = {
   url: "chrome://browser/content/debugger.xul",
   label: l10n("ToolboxDebugger.label", debuggerStrings),
 
   isTargetSupported: function(target) {
     return true;
   },
 
   build: function(iframeWindow, toolbox) {
-    return new DebuggerPanel(iframeWindow, toolbox);
+    let panel = new DebuggerPanel(iframeWindow, toolbox);
+    return panel.open();
   }
 };
 
 let inspectorDefinition = {
   id: "inspector",
   accesskey: l10n("inspector.accesskey", inspectorStrings),
   key: l10n("inspector.commandkey", inspectorStrings),
   ordinal: 2,
@@ -100,17 +102,18 @@ let inspectorDefinition = {
   url: "chrome://browser/content/devtools/inspector/inspector.xul",
   label: l10n("inspector.label", inspectorStrings),
 
   isTargetSupported: function(target) {
     return !target.isRemote;
   },
 
   build: function(iframeWindow, toolbox) {
-    return new InspectorPanel(iframeWindow, toolbox);
+    let panel = new InspectorPanel(iframeWindow, toolbox);
+    return panel.open();
   }
 };
 
 let styleEditorDefinition = {
   id: "styleeditor",
   key: l10n("open.commandkey", styleEditorStrings),
   ordinal: 3,
   accesskey: l10n("open.accesskey", styleEditorStrings),
@@ -118,17 +121,18 @@ let styleEditorDefinition = {
   label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
   url: "chrome://browser/content/styleeditor.xul",
 
   isTargetSupported: function(target) {
     return !target.isRemote && !target.isChrome;
   },
 
   build: function(iframeWindow, toolbox) {
-    return new StyleEditorPanel(iframeWindow, toolbox);
+    let panel = new StyleEditorPanel(iframeWindow, toolbox);
+    return panel.open();
   }
 };
 
 this.defaultTools = [
   styleEditorDefinition,
   webConsoleDefinition,
   debuggerDefinition,
   inspectorDefinition,
--- a/browser/devtools/framework/Toolbox.jsm
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Hosts",
                                   "resource:///modules/devtools/ToolboxHosts.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommandUtils",
                                   "resource:///modules/devtools/DeveloperToolbar.jsm");
 
@@ -21,33 +22,96 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 Cu.import("resource://gre/modules/devtools/gcli.jsm");
 Cu.import("resource://gre/modules/devtools/Require.jsm");
 
 let Requisition = require('gcli/cli').Requisition;
 let CommandOutputManager = require('gcli/canon').CommandOutputManager;
 
 this.EXPORTED_SYMBOLS = [ "Toolbox" ];
 
+// This isn't the best place for this, but I don't know what is right now
+
+/**
+ * Implementation of 'promised', while we wait for bug 790195 to be fixed.
+ * @see Consuming promises in https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/promise.html
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=790195
+ * @see https://github.com/mozilla/addon-sdk/blob/master/packages/api-utils/lib/promise.js#L179
+ */
+Promise.promised = (function() {
+  // Note: Define shortcuts and utility functions here in order to avoid
+  // slower property accesses and unnecessary closure creations on each
+  // call of this popular function.
+
+  var call = Function.call;
+  var concat = Array.prototype.concat;
+
+  // Utility function that does following:
+  // execute([ f, self, args...]) => f.apply(self, args)
+  function execute(args) { return call.apply(call, args); }
+
+  // Utility function that takes promise of `a` array and maybe promise `b`
+  // as arguments and returns promise for `a.concat(b)`.
+  function promisedConcat(promises, unknown) {
+    return promises.then(function(values) {
+      return Promise.resolve(unknown).then(function(value) {
+        return values.concat([ value ]);
+      });
+    });
+  }
+
+  return function promised(f, prototype) {
+    /**
+    Returns a wrapped `f`, which when called returns a promise that resolves to
+    `f(...)` passing all the given arguments to it, which by the way may be
+    promises. Optionally second `prototype` argument may be provided to be used
+    a prototype for a returned promise.
+
+    ## Example
+
+    var promise = promised(Array)(1, promise(2), promise(3))
+    promise.then(console.log) // => [ 1, 2, 3 ]
+    **/
+
+    return function promised() {
+      // create array of [ f, this, args... ]
+      return concat.apply([ f, this ], arguments).
+          // reduce it via `promisedConcat` to get promised array of fulfillments
+          reduce(promisedConcat, Promise.resolve([], prototype)).
+          // finally map that to promise of `f.apply(this, args...)`
+          then(execute);
+    };
+  };
+})();
+
+/**
+ * Convert an array of promises to a single promise, which is resolved (with an
+ * array containing resolved values) only when all the component promises are
+ * resolved.
+ */
+Promise.all = Promise.promised(Array);
+
+
+
+
 /**
  * A "Toolbox" is the component that holds all the tools for one specific
  * target. Visually, it's a document that includes the tools tabs and all
  * 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 {string} selectedTool
- *        Tool to select initially
  */
-this.Toolbox = function Toolbox(target, hostType, selectedTool) {
+this.Toolbox = function Toolbox(target, selectedTool, hostType) {
   this._target = target;
   this._toolPanels = new Map();
 
-  this._onLoad = this._onLoad.bind(this);
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this.destroy = this.destroy.bind(this);
 
   this._target.once("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
@@ -101,40 +165,48 @@ Toolbox.prototype = {
 
     for (let [key, value] of this._toolPanels) {
       panels.set(key, value);
     }
     return panels;
   },
 
   /**
+   * Access the panel for a given tool
+   */
+  getPanel: function TBOX_getPanel(id) {
+    return this.getToolPanels().get(id);
+  },
+
+  /**
+   * This is a shortcut for getPanel(currentToolId) because it is much more
+   * likely that we're going to want to get the panel that we've just made
+   * visible
+   */
+  getCurrentPanel: function TBOX_getCurrentPanel() {
+    return this.getToolPanels().get(this.currentToolId);
+  },
+
+  /**
    * Get/alter the target of a Toolbox so we're debugging something different.
    * See Target.jsm for more details.
    * TODO: Do we allow |toolbox.target = null;| ?
    */
   get target() {
     return this._target;
   },
 
-  set target(value) {
-    this._target = value;
-  },
-
   /**
    * 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;
   },
 
-  set hostType(value) {
-    this._switchToHost(value);
-  },
-
   /**
    * Get/alter the currently displayed tool.
    */
   get currentToolId() {
     return this._currentToolId;
   },
 
   set currentToolId(value) {
@@ -154,22 +226,42 @@ Toolbox.prototype = {
   get doc() {
     return this.frame.contentDocument;
   },
 
   /**
    * Open the toolbox
    */
   open: function TBOX_open() {
-    this._host.once("ready", function(event, iframe) {
-      iframe.addEventListener("DOMContentLoaded", this._onLoad, true);
+    let deferred = Promise.defer();
+
+    this._host.open().then(function(iframe) {
+      let onload = function() {
+        iframe.removeEventListener("DOMContentLoaded", onload, true);
+
+        this.isReady = true;
+
+        let closeButton = this.doc.getElementById("toolbox-close");
+        closeButton.addEventListener("command", this.destroy, true);
+
+        this._buildDockButtons();
+        this._buildTabs();
+        this._buildButtons(this.frame);
+
+        this.selectTool(this._defaultToolId).then(function(panel) {
+          this.emit("ready");
+          deferred.resolve();
+        }.bind(this));
+      }.bind(this);
+
+      iframe.addEventListener("DOMContentLoaded", onload, true);
       iframe.setAttribute("src", this._URL);
     }.bind(this));
 
-    this._host.open();
+    return deferred.promise;
   },
 
   /**
    * Build the buttons for changing hosts. Called every time
    * the host changes.
    */
   _buildDockButtons: function TBOX_createDockButtons() {
     let dockBox = this.doc.getElementById("toolbox-dock-buttons");
@@ -185,44 +277,24 @@ Toolbox.prototype = {
          (!sideEnabled && position == this.HostType.SIDE)) {
         continue;
       }
 
       let button = this.doc.createElement("toolbarbutton");
       button.id = "toolbox-dock-" + position;
       button.className = "toolbox-dock-button";
       button.addEventListener("command", function(position) {
-        this.hostType = position;
+        this.switchHost(position);
       }.bind(this, position));
 
       dockBox.appendChild(button);
     }
   },
 
   /**
-   * Onload handler for the toolbox's iframe
-   */
-  _onLoad: function TBOX_onLoad() {
-    this.frame.removeEventListener("DOMContentLoaded", this._onLoad, true);
-    this.isReady = true;
-
-    let closeButton = this.doc.getElementById("toolbox-close");
-    closeButton.addEventListener("command", this.destroy, true);
-
-    this._buildDockButtons();
-
-    this._buildTabs();
-    this._buildButtons(this.frame);
-
-    this.selectTool(this._defaultToolId);
-
-    this.emit("ready");
-  },
-
-  /**
    * Add tabs to the toolbox UI for registered tools
    */
   _buildTabs: function TBOX_buildTabs() {
     for (let [id, definition] of gDevTools.getToolDefinitions()) {
       this._buildTabForTool(definition);
     }
   },
 
@@ -291,16 +363,18 @@ Toolbox.prototype = {
 
   /**
    * Switch to the tool with the given id
    *
    * @param {string} id
    *        The id of the tool to switch to
    */
   selectTool: function TBOX_selectTool(id) {
+    let deferred = Promise.defer();
+
     if (!this.isReady) {
       throw new Error("Can't select tool, wait for toolbox 'ready' event");
     }
     let tab = this.doc.getElementById("toolbox-tab-" + id);
 
     if (!tab) {
       throw new Error("No tool found");
     }
@@ -331,47 +405,46 @@ Toolbox.prototype = {
       iframe.id = "toolbox-panel-iframe-" + id;
       iframe.setAttribute("flex", 1);
 
       let vbox = this.doc.getElementById("toolbox-panel-" + id);
       vbox.appendChild(iframe);
 
       let boundLoad = function() {
         iframe.removeEventListener("DOMContentLoaded", boundLoad, true);
-        let panel = definition.build(iframe.contentWindow, this);
-        this._toolPanels.set(id, panel);
 
-        let panelReady = function() {
+        definition.build(iframe.contentWindow, this).then(function(panel) {
+          this._toolPanels.set(id, panel);
+
           this.emit(id + "-ready", panel);
           this.emit("select", id);
           this.emit(id + "-selected", panel);
           gDevTools.emit(id + "-ready", this, panel);
-        }.bind(this);
 
-        if (panel.isReady) {
-          panelReady();
-        } else {
-          panel.once("ready", panelReady);
-        }
+          deferred.resolve(panel);
+        }.bind(this));
       }.bind(this);
 
       iframe.addEventListener("DOMContentLoaded", boundLoad, true);
       iframe.setAttribute("src", definition.url);
     } else {
       let panel = this._toolPanels.get(id);
       // only emit 'select' event if the iframe has been loaded
       if (panel) {
         this.emit("select", id);
         this.emit(id + "-selected", panel);
+        deferred.resolve(panel);
       }
     }
 
     Services.prefs.setCharPref(this._prefs.LAST_TOOL, id);
 
     this._currentToolId = id;
+
+    return deferred.promise;
   },
 
   /**
    * Create a host object based on the given host type.
    *
    * @param {string} hostType
    *        The host type of the new host object
    *
@@ -393,41 +466,38 @@ Toolbox.prototype = {
 
   /**
    * Switch to a new host for the toolbox UI. E.g.
    * bottom, sidebar, separate window.
    *
    * @param {string} hostType
    *        The host type of the new host object
    */
-  _switchToHost: function TBOX_switchToHost(hostType) {
+  switchHost: function TBOX_switchHost(hostType) {
     if (hostType == this._host.type) {
       return;
     }
 
     let newHost = this._createHost(hostType);
-
-    newHost.once("ready", function(event, iframe) {
+    return newHost.open().then(function(iframe) {
       // change toolbox document's parent to the new host
-      iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+      iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
       iframe.swapFrameLoaders(this.frame);
 
       this._host.off("window-closed", this.destroy);
       this._host.destroy();
 
       this._host = newHost;
 
       Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
 
       this._buildDockButtons();
 
       this.emit("host-changed");
     }.bind(this));
-
-    newHost.open();
   },
 
   /**
    * Get the most appropriate host tab, either the target or the current tab
    */
   _getHostTab: function TBOX_getHostTab() {
     if (!this._target.isRemote && !this._target.isChrome) {
       return this._target.tab;
@@ -499,31 +569,43 @@ Toolbox.prototype = {
   getNotificationBox: function TBOX_getNotificationBox() {
     return this.doc.getElementById("toolbox-notificationbox");
   },
 
   /**
    * Remove all UI elements, detach from target and clear up
    */
   destroy: function TBOX_destroy() {
-    if (this._destroyed) {
-      return;
+    // 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;
     }
 
+    let outstanding = [];
+
     // Remote targets need to be notified that the toolbox is being torn down.
     if (this._target && this._target.isRemote) {
-      this._target.destroy();
+      outstanding.push(this._target.destroy());
     }
     this._target = null;
 
     for (let [id, panel] of this._toolPanels) {
-      panel.destroy();
+      outstanding.push(panel.destroy());
     }
 
-    this._host.destroy();
+    outstanding.push(this._host.destroy());
 
     gDevTools.off("tool-registered", this._toolRegistered);
     gDevTools.off("tool-unregistered", this._toolUnregistered);
 
-    this._destroyed = true;
-    this.emit("destroyed");
+    this._destroyer = Promise.all(outstanding);
+    this._destroyer.then(function() {
+      this.emit("destroyed");
+    }.bind(this));
+
+    return this._destroyer.then(function() {
+      // Ensure that the promise resolves to nothing, rather than an array of
+      // several nothings, which is what we get from Promise.all
+      return undefined;
+    });
   }
 };
--- a/browser/devtools/framework/ToolboxHosts.jsm
+++ b/browser/devtools/framework/ToolboxHosts.jsm
@@ -2,16 +2,17 @@
  * 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/. */
 
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 this.EXPORTED_SYMBOLS = [ "Hosts" ];
 
 /**
  * A toolbox host represents an object that contains a toolbox (e.g. the
  * sidebar or a separate window). Any host object should implement the
  * following functions:
@@ -39,16 +40,18 @@ BottomHost.prototype = {
   type: "bottom",
 
   heightPref: "devtools.toolbox.footer.height",
 
   /**
    * Create a box at the bottom of the host tab.
    */
   open: function BH_open() {
+    let deferred = Promise.defer();
+
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.id = "devtools-toolbox-bottom-iframe";
@@ -56,38 +59,43 @@ BottomHost.prototype = {
 
     this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this.frame);
 
     let frameLoad = function() {
       this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
       this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
     }.bind(this);
 
     this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
 
     // we have to load something so we can switch documents if we have to
     this.frame.setAttribute("src", "about:blank");
 
     focusTab(this.hostTab);
+
+    return deferred.promise;
   },
 
   /**
    * Destroy the bottom dock.
    */
   destroy: function BH_destroy() {
-    if (this._destroyed) {
-      return;
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      Services.prefs.setIntPref(this.heightPref, this.frame.height);
+      this._nbox.removeChild(this._splitter);
+      this._nbox.removeChild(this.frame);
     }
-    this._destroyed = true;
-    Services.prefs.setIntPref(this.heightPref, this.frame.height);
 
-    this._nbox.removeChild(this._splitter);
-    this._nbox.removeChild(this.frame);
+    return Promise.resolve(null);
   }
 }
 
 
 /**
  * Host object for the in-browser sidebar
  */
 function SidebarHost(hostTab) {
@@ -100,16 +108,18 @@ SidebarHost.prototype = {
   type: "side",
 
   widthPref: "devtools.toolbox.sidebar.width",
 
   /**
    * Create a box in the sidebar of the host tab.
    */
   open: function RH_open() {
+    let deferred = Promise.defer();
+
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-side-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.id = "devtools-toolbox-side-iframe";
@@ -117,32 +127,41 @@ SidebarHost.prototype = {
 
     this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
     this._sidebar.appendChild(this._splitter);
     this._sidebar.appendChild(this.frame);
 
     let frameLoad = function() {
       this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
       this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
     }.bind(this);
 
     this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
     this.frame.setAttribute("src", "about:blank");
 
     focusTab(this.hostTab);
+
+    return deferred.promise;
   },
 
   /**
    * Destroy the sidebar.
    */
   destroy: function RH_destroy() {
-    Services.prefs.setIntPref(this.widthPref, this.frame.width);
+    if (!this._destroyed) {
+      this._destroyed = true;
 
-    this._sidebar.removeChild(this._splitter);
-    this._sidebar.removeChild(this.frame);
+      Services.prefs.setIntPref(this.widthPref, this.frame.width);
+      this._sidebar.removeChild(this._splitter);
+      this._sidebar.removeChild(this.frame);
+    }
+
+    return Promise.resolve(null);
   }
 }
 
 /**
  * Host object for the toolbox in a separate window
  */
 function WindowHost() {
   this._boundUnload = this._boundUnload.bind(this);
@@ -154,32 +173,38 @@ WindowHost.prototype = {
   type: "window",
 
   WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul",
 
   /**
    * Create a new xul window to contain the toolbox.
    */
   open: function WH_open() {
+    let deferred = Promise.defer();
+
     let flags = "chrome,centerscreen,resizable,dialog=no";
     let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
                                      flags, null);
 
     let frameLoad = function(event) {
       win.removeEventListener("load", frameLoad, true);
       this.frame = win.document.getElementById("toolbox-iframe");
       this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
     }.bind(this);
 
     win.addEventListener("load", frameLoad, true);
     win.addEventListener("unload", this._boundUnload);
 
     win.focus();
 
     this._window = win;
+
+    return deferred.promise;
   },
 
   /**
    * Catch the user closing the window.
    */
   _boundUnload: function(event) {
     if (event.target.location != this.WINDOW_URL) {
       return;
@@ -188,18 +213,24 @@ WindowHost.prototype = {
 
     this.emit("window-closed");
   },
 
   /**
    * Destroy the window.
    */
   destroy: function WH_destroy() {
-    this._window.removeEventListener("unload", this._boundUnload);
-    this._window.close();
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      this._window.removeEventListener("unload", this._boundUnload);
+      this._window.close();
+    }
+
+    return Promise.resolve(null);
   }
 }
 
 /**
  *  Switch to the given tab in a browser and focus the browser window
  */
 function focusTab(tab) {
   let browserWindow = tab.ownerDocument.defaultView;
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -90,11 +90,11 @@ function submit() {
         }
       }
     });
   });
 }
 
 function connect(form, chrome=false) {
   let target = TargetFactory.forRemote(form, gClient, chrome);
-  gDevTools.openToolbox(target, Toolbox.HostType.WINDOW, "webconsole");
+  gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
   window.close();
 }
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -1,21 +1,21 @@
 /* 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/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "DevToolsXULCommands" ];
+this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser" ];
 
-const Cu = Components.utils;
-const Ci = Components.interfaces;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource:///modules/devtools/ToolDefinitions.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Toolbox",
   "resource:///modules/devtools/Toolbox.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
   "resource:///modules/devtools/Target.jsm");
 
 const FORBIDDEN_IDS = new Set("toolbox", "");
@@ -26,28 +26,21 @@ const FORBIDDEN_IDS = new Set("toolbox",
  */
 this.DevTools = function DevTools() {
   this._tools = new Map();
   this._toolboxes = new Map();
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
 
-  this._trackedBrowserWindows = new Set();
-
-  // Bind _updateMenuCheckbox() to preserve context.
-  this._updateMenuCheckbox = this._updateMenuCheckbox.bind(this);
-
   new EventEmitter(this);
 
   Services.obs.addObserver(this.destroy, "quit-application", false);
 
-  /**
-   * Register the set of default tools
-   */
+  // Register the set of default tools
   for (let definition of defaultTools) {
     this.registerTool(definition);
   }
 }
 
 DevTools.prototype = {
   /**
    * Register a new developer tool.
@@ -75,36 +68,32 @@ DevTools.prototype = {
   registerTool: function DT_registerTool(toolDefinition) {
     let toolId = toolDefinition.id;
 
     if (!toolId || FORBIDDEN_IDS.has(toolId)) {
       throw new Error("Invalid definition.id");
     }
 
     toolDefinition.killswitch = toolDefinition.killswitch ||
-      "devtools." + toolId + ".enabled";
+        "devtools." + toolId + ".enabled";
     this._tools.set(toolId, toolDefinition);
 
-    this._addToolToWindows(toolDefinition);
-
     this.emit("tool-registered", toolId);
   },
 
   /**
    * Removes all tools that match the given |toolId|
    * Needed so that add-ons can remove themselves when they are deactivated
    *
    * @param {string} toolId
    *        id of the tool to unregister
    */
   unregisterTool: function DT_unregisterTool(toolId) {
     this._tools.delete(toolId);
 
-    this._removeToolFromWindows(toolId);
-
     this.emit("tool-unregistered", toolId);
   },
 
   /**
    * Allow ToolBoxes to get at the list of tools that they should populate
    * themselves with.
    *
    * @return {Map} tools
@@ -125,189 +114,202 @@ DevTools.prototype = {
       if (enabled) {
         tools.set(key, value);
       }
     }
     return tools;
   },
 
   /**
-   * Create a toolbox to debug |target| using a window displayed in |hostType|
-   * (optionally with |defaultToolId| opened)
+   * Show a Toolbox for a target (either by creating a new one, or if a toolbox
+   * already exists for the target, by bring to the front the existing one)
+   * If |toolId| is specified then the displayed toolbox will have the
+   * specified tool selected.
+   * If |hostType| is specified then the toolbox will be displayed using the
+   * specified HostType.
    *
    * @param {Target} target
    *         The target the toolbox will debug
+   * @param {string} toolId
+   *        The id of the tool to show
    * @param {Toolbox.HostType} hostType
-   *        The type of host (bottom, top, side)
-   * @param {string} defaultToolId
-   *        The id of the initial tool to show
+   *        The type of host (bottom, window, side)
    *
    * @return {Toolbox} toolbox
    *        The toolbox that was opened
    */
-  openToolbox: function DT_openToolbox(target, hostType, defaultToolId) {
-    if (this._toolboxes.has(target)) {
-      // only allow one toolbox per target
-      return this._toolboxes.get(target);
+  showToolbox: function(target, toolId, hostType) {
+    let deferred = Promise.defer();
+
+    let toolbox = this._toolboxes.get(target);
+    if (toolbox) {
+
+      let promise = (hostType != null && toolbox.hostType != hostType) ?
+          toolbox.switchHost(hostType) :
+          Promise.resolve(null);
+
+      if (toolId != null && toolbox.currentToolId != toolId) {
+        promise = promise.then(function() {
+          return toolbox.selectTool(toolId);
+        });
+      }
+
+      return promise.then(function() {
+        return toolbox;
+      });
+    }
+    else {
+      // No toolbox for target, create one
+      toolbox = new Toolbox(target, toolId, hostType);
+
+      this._toolboxes.set(target, toolbox);
+
+      toolbox.once("destroyed", function() {
+        this._toolboxes.delete(target);
+        this.emit("toolbox-destroyed", target);
+      }.bind(this));
+
+      // If we were asked for a specific tool then we need to wait for the
+      // tool to be ready, otherwise we can just wait for toolbox open
+      if (toolId != null) {
+        toolbox.once(toolId + "-ready", function(event, panel) {
+          this.emit("toolbox-ready", toolbox);
+          deferred.resolve(toolbox);
+        }.bind(this));
+        toolbox.open();
+      }
+      else {
+        toolbox.open().then(function() {
+          deferred.resolve(toolbox);
+          this.emit("toolbox-ready", toolbox);
+        }.bind(this));
+      }
     }
 
-    let tb = new Toolbox(target, hostType, defaultToolId);
-
-    this._toolboxes.set(target, tb);
-    tb.once("destroyed", function() {
-      this._toolboxes.delete(target);
-      this._updateMenuCheckbox();
-      this.emit("toolbox-destroyed", target);
-    }.bind(this));
-
-    tb.once("ready", function() {
-      this.emit("toolbox-ready", tb);
-      this._updateMenuCheckbox();
-    }.bind(this));
-
-    tb.open();
-
-    return tb;
-  },
-
-  /**
-   * Close the toolbox for a given target
-   */
-  closeToolbox: function DT_closeToolbox(target) {
-    let toolbox = this._toolboxes.get(target);
-    if (toolbox == null) {
-      return;
-    }
-    toolbox.destroy();
-  },
-
-  /**
-   * Open the toolbox for a specific target (not tab).
-   * FIXME: We should probably merge this function and openToolbox
-   *
-   * @param  {Target} target
-   *         The target that the toolbox should be debugging
-   * @param  {String} toolId
-   *         The id of the tool to open
-   *
-   * @return {Toolbox} toolbox
-   *         The toolbox that has been opened
-   */
-  openToolboxForTab: function DT_openToolboxForTab(target, toolId) {
-    let tb = this.getToolboxForTarget(target);
-
-    if (tb) {
-      tb.selectTool(toolId);
-    } else {
-      tb = this.openToolbox(target, null, toolId);
-    }
-    return tb;
-  },
-
-  /**
-   * This function is for the benefit of command#Tools:DevToolbox in
-   * browser/base/content/browser-sets.inc and should not be used outside
-   * of there
-   */
-  toggleToolboxCommand: function(gBrowser, toolId=null) {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    this.toggleToolboxForTarget(target, toolId);
-  },
-
-  /**
-   * Toggle a toolbox for the given target.
-   *
-   * @param  {Target} target
-   *         The target the toolbox is debugging
-   * @param  {string} toolId
-   *         The id of the tool to show in the toolbox, if it's to be opened.
-   */
-  toggleToolboxForTarget: function DT_toggleToolboxForTarget(target, toolId) {
-    let tb = this.getToolboxForTarget(target);
-
-    if (tb /* FIXME: && tool is showing */ ) {
-      tb.destroy();
-    } else {
-      this.openToolboxForTab(target, toolId);
-    }
+    return deferred.promise;
   },
 
   /**
    * Return the toolbox for a given target.
    *
    * @param  {object} target
    *         Target value e.g. the target that owns this toolbox
    *
    * @return {Toolbox} toolbox
    *         The toobox that is debugging the given target
    */
-  getToolboxForTarget: function DT_getToolboxForTarget(target) {
+  getToolbox: function DT_getToolbox(target) {
     return this._toolboxes.get(target);
   },
 
   /**
-   * Return a tool panel for a given tool and target.
-   *
-   * @param  {String} toolId
-   *         The id of the tool to open.
-   * @param  {object} target
-   *         The toolbox's target.
-   *
-   * @return {ToolPanel} panel
-   *         Panel for the tool with the toolid
+   * Close the toolbox for a given target
+   */
+  closeToolbox: function DT_closeToolbox(target) {
+    let toolbox = this._toolboxes.get(target);
+    if (toolbox == null) {
+      return;
+    }
+    return toolbox.destroy();
+  },
+
+  /**
+   * All browser windows have been closed, tidy up remaining objects.
    */
-  getPanelForTarget: function DT_getPanelForTarget(toolId, target) {
-    let toolbox = this.getToolboxForTarget(target);
-    if (!toolbox) {
-      return undefined;
-    }
-    return toolbox.getToolPanels().get(toolId);
+  destroy: function() {
+    Services.obs.removeObserver(this.destroy, "quit-application");
+
+    delete this._trackedBrowserWindows;
+    delete this._toolboxes;
+  },
+};
+
+/**
+ * gDevTools is a singleton that controls the Firefox Developer Tools.
+ *
+ * It is an instance of a DevTools class that holds a set of tools. It has the
+ * same lifetime as the browser.
+ */
+let gDevTools = new DevTools();
+this.gDevTools = gDevTools;
+
+/**
+ * gDevToolsBrowser exposes functions to connect the gDevTools instance with a
+ * Firefox instance.
+ */
+let gDevToolsBrowser = {
+  /**
+   * A record of the windows whose menus we altered, so we can undo the changes
+   * as the window is closed
+   */
+  _trackedBrowserWindows: new Set(),
+
+  /**
+   * This function is for the benefit of command#Tools:DevToolbox in
+   * browser/base/content/browser-sets.inc and should not be used outside
+   * of there
+   */
+  toggleToolboxCommand: function(gBrowser, toolId=null) {
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    let toolbox = gDevTools.getToolbox(target);
+
+    return toolbox && (toolId == null || toolId == toolbox.currentToolId) ?
+        toolbox.destroy() :
+        gDevTools.showToolbox(target, toolId);
+  },
+
+  /**
+   * Open a tab to allow connects to a remote browser
+   */
+  openConnectScreen: function(gBrowser) {
+    gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml");
   },
 
   /**
    * Add this DevTools's presence to a browser window's document
    *
-   * @param  {XULDocument} doc
-   *         The document to which menuitems and handlers are to be added
+   * @param {XULDocument} doc
+   *        The document to which menuitems and handlers are to be added
    */
   registerBrowserWindow: function DT_registerBrowserWindow(win) {
-    this._trackedBrowserWindows.add(win);
-    this._addAllToolsToMenu(win.document);
+    gDevToolsBrowser._trackedBrowserWindows.add(win);
+    gDevToolsBrowser._addAllToolsToMenu(win.document);
 
     let tabContainer = win.document.getElementById("tabbrowser-tabs")
-    tabContainer.addEventListener("TabSelect", this._updateMenuCheckbox, false);
+    tabContainer.addEventListener("TabSelect",
+                                  gDevToolsBrowser._updateMenuCheckbox, false);
   },
 
   /**
    * Add the menuitem for a tool to all open browser windows.
    *
    * @param {object} toolDefinition
    *        properties of the tool to add
    */
   _addToolToWindows: function DT_addToolToWindows(toolDefinition) {
-    for (let win of this._trackedBrowserWindows) {
-      this._addToolToMenu(toolDefinition, win.document);
+    for (let win of gDevToolsBrowser._trackedBrowserWindows) {
+      gDevToolsBrowser._addToolToMenu(toolDefinition, win.document);
     }
   },
 
   /**
    * Add all tools to the developer tools menu of a window.
    *
    * @param {XULDocument} doc
    *        The document to which the tool items are to be added.
    */
   _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) {
     let fragCommands = doc.createDocumentFragment();
     let fragKeys = doc.createDocumentFragment();
     let fragBroadcasters = doc.createDocumentFragment();
     let fragAppMenuItems = doc.createDocumentFragment();
     let fragMenuItems = doc.createDocumentFragment();
 
-    for (let [key, toolDefinition] of this._tools) {
-      let frags = this._addToolToMenu(toolDefinition, doc, true);
+    for (let [key, toolDefinition] of gDevTools._tools) {
+      let frags = gDevToolsBrowser._addToolToMenu(toolDefinition, doc, true);
 
       if (!frags) {
         return;
       }
 
       let [cmd, key, bc, appmenuitem, menuitem] = frags;
 
       fragCommands.appendChild(cmd);
@@ -356,31 +358,31 @@ DevTools.prototype = {
     // Prevent multiple entries for the same tool.
     if (doc.getElementById("Tools:" + id)) {
       return;
     }
 
     let cmd = doc.createElement("command");
     cmd.id = "Tools:" + id;
     cmd.setAttribute("oncommand",
-        'gDevTools.toggleToolboxCommand(gBrowser, "' + id + '");');
+        'gDevToolsBrowser.toggleToolboxCommand(gBrowser, "' + id + '");');
 
     let key = null;
     if (toolDefinition.key) {
       key = doc.createElement("key");
       key.id = "key_" + id;
 
       if (toolDefinition.key.startsWith("VK_")) {
         key.setAttribute("keycode", toolDefinition.key);
       } else {
         key.setAttribute("key", toolDefinition.key);
       }
 
       key.setAttribute("oncommand",
-          'gDevTools.toggleToolboxCommand(gBrowser, "' + id + '");');
+          'gDevToolsBrowser.toggleToolboxCommand(gBrowser, "' + id + '");');
       key.setAttribute("modifiers", toolDefinition.modifiers);
     }
 
     let bc = doc.createElement("broadcaster");
     bc.id = "devtoolsMenuBroadcaster_" + id;
     bc.setAttribute("label", toolDefinition.label);
     bc.setAttribute("command", "Tools:" + id);
 
@@ -426,22 +428,22 @@ DevTools.prototype = {
     }
   },
 
   /**
    * Update the "Toggle Toolbox" checkbox in the developer tools menu. This is
    * called when a toolbox is created or destroyed.
    */
   _updateMenuCheckbox: function DT_updateMenuCheckbox() {
-    for (let win of this._trackedBrowserWindows) {
+    for (let win of gDevToolsBrowser._trackedBrowserWindows) {
 
       let hasToolbox = false;
       if (TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
         let target = TargetFactory.forTab(win.gBrowser.selectedTab);
-        if (this._toolboxes.has(target)) {
+        if (gDevTools._toolboxes.has(target)) {
           hasToolbox = true;
         }
       }
 
       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
       if (hasToolbox) {
         broadcaster.setAttribute("checked", "true");
       } else {
@@ -452,18 +454,18 @@ DevTools.prototype = {
 
   /**
    * Remove the menuitem for a tool to all open browser windows.
    *
    * @param {object} toolId
    *        id of the tool to remove
    */
   _removeToolFromWindows: function DT_removeToolFromWindows(toolId) {
-    for (let win of this._trackedBrowserWindows) {
-      this._removeToolFromMenu(toolId, win.document);
+    for (let win of gDevToolsBrowser._trackedBrowserWindows) {
+      gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
     }
   },
 
   /**
    * Remove a tool's menuitem from a window
    *
    * @param {string} toolId
    *        Id of the tool to add a menu entry for
@@ -476,71 +478,59 @@ DevTools.prototype = {
 
     let key = doc.getElementById("key_" + toolId);
     if (key) {
       key.parentNode.removeChild(key);
     }
 
     let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
     bc.parentNode.removeChild(bc);
-
-    /*
-    // FIXME: item is null in testing. This is the only place to use
-    // "appmenu_devToolbar" + toolId, so it seems clear that this is wrong
-    let item = doc.getElementById("appmenu_devToolbar" + toolId);
-    item.parentNode.removeChild(item);
-    */
   },
 
   /**
    * Called on browser unload to remove menu entries, toolboxes and event
    * listeners from the closed browser window.
    *
    * @param  {XULWindow} win
    *         The window containing the menu entry
    */
   forgetBrowserWindow: function DT_forgetBrowserWindow(win) {
-    if (!this._tools) {
+    if (!gDevToolsBrowser._trackedBrowserWindows) {
       return;
     }
 
-    this._trackedBrowserWindows.delete(win);
+    gDevToolsBrowser._trackedBrowserWindows.delete(win);
 
     // Destroy toolboxes for closed window
-    for (let [target, toolbox] of this._toolboxes) {
+    for (let [target, toolbox] of gDevTools._toolboxes) {
       if (toolbox.frame.ownerDocument.defaultView == win) {
         toolbox.destroy();
       }
     }
 
     let tabContainer = win.document.getElementById("tabbrowser-tabs")
     tabContainer.removeEventListener("TabSelect",
-                                     this._updateMenuCheckbox, false);
+                                     gDevToolsBrowser._updateMenuCheckbox, false);
   },
 
   /**
    * All browser windows have been closed, tidy up remaining objects.
    */
   destroy: function() {
-    Services.obs.removeObserver(this.destroy, "quit-application");
-
-    delete this._trackedBrowserWindows;
-    delete this._tools;
-    delete this._toolboxes;
-  },
-};
-
-/**
- * gDevTools is a singleton that controls the Firefox Developer Tools.
- *
- * It is an instance of a DevTools class that holds a set of tools. It has the
- * same lifetime as the browser.
- */
-this.gDevTools = new DevTools();
-
-/**
- * DevToolsXULCommands exposes methods used by browser's <command>s.
- */
-this.DevToolsXULCommands = {
-  openConnectScreen: function(gBrowser) {
-    gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml");
+    Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
+    delete gDevToolsBrowser._trackedBrowserWindows;
   },
 }
+this.gDevToolsBrowser = gDevToolsBrowser;
+
+gDevTools.on("tool-registered", function(ev, toolId) {
+  let toolDefinition = gDevTools._tools.get(toolId);
+  gDevToolsBrowser._addToolToWindows(toolDefinition);
+});
+
+gDevTools.on("tool-unregistered", function(ev, toolId) {
+  gDevToolsBrowser._removeToolFromWindows(toolId);
+});
+
+gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
+gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
+
+Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
--- a/browser/devtools/framework/test/browser_devtools_api.js
+++ b/browser/devtools/framework/test/browser_devtools_api.js
@@ -22,72 +22,57 @@ function runTests(aTab) {
   let toolDefinition = {
     id: toolId,
     isTargetSupported: function() true,
     killswitch: "devtools.test-tool.enabled",
     url: "about:blank",
     label: "someLabel",
     build: function(iframeWindow, toolbox) {
       let panel = new DevToolPanel(iframeWindow, toolbox);
-      return panel;
+      return panel.open();
     },
   };
 
   ok(gDevTools, "gDevTools exists");
   is(gDevTools.getToolDefinitions().has(toolId), false,
     "The tool is not registered");
 
   gDevTools.registerTool(toolDefinition);
   is(gDevTools.getToolDefinitions().has(toolId), true,
     "The tool is registered");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  function onNewToolbox(event, toolboxFromEvent) {
-    let toolBoxes = gDevTools._toolboxes;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let tb = toolBoxes.get(target);
-    is(toolboxFromEvent, tb, "'toolbox-ready' event fired. Correct toolbox value.");
-    is(tb.target, target, "toolbox target is correct");
-    is(tb._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
-    gDevTools.once(toolId + "-ready", continueTests);
-  }
-
-  function onToolboxClosed(event, targetFromEvent) {
-    is(targetFromEvent, target, "'toolbox-destroyed' event fired. Correct tab value.");
-    finishUp();
-  }
-
-
-  gDevTools.once("toolbox-ready", onNewToolbox);
-  gDevTools.once("toolbox-destroyed", onToolboxClosed);
-
-  executeSoon(function() {
-    gDevTools.openToolbox(target, "bottom", toolId);
-  });
+  gDevTools.showToolbox(target, toolId).then(function(toolbox) {
+    is(toolbox.target, target, "toolbox target is correct");
+    is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
+    continueTests(toolbox);
+  }).then(null, console.error);
 }
 
-function continueTests(event, toolbox, panel) {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  is(toolbox, gDevTools._toolboxes.get(target), "{toolId}-ready event received, with correct toolbox value");
-  is(panel, toolbox.getToolPanels().get(toolId), "panel value is correct");
-
+function continueTests(toolbox, panel) {
+  ok(toolbox.getCurrentPanel(), "panel value is correct");
   is(toolbox.currentToolId, toolId, "toolbox _currentToolId is correct");
 
   let toolDefinitions = gDevTools.getToolDefinitions();
   is(toolDefinitions.has(toolId), true, "The tool is in gDevTools");
 
   let toolDefinition = toolDefinitions.get(toolId);
   is(toolDefinition.id, toolId, "toolDefinition id is correct");
 
   gDevTools.unregisterTool(toolId);
   is(gDevTools.getToolDefinitions().has(toolId), false,
     "The tool is no longer registered");
 
-  toolbox.destroy();
+  toolbox.destroy().then(function() {
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    ok(gDevTools._toolboxes.get(target) == null, "gDevTools doesn't know about target");
+    ok(toolbox._target == null, "toolbox doesn't know about target.");
+
+    finishUp();
+  }).then(null, console.error);
 }
 
 function finishUp() {
   tempScope = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
@@ -105,33 +90,35 @@ function DevToolPanel(iframeWindow, tool
   this._toolbox = toolbox;
 
   /*let doc = iframeWindow.document
   let label = doc.createElement("label");
   let textNode = doc.createTextNode("Some Tool");
 
   label.appendChild(textNode);
   doc.body.appendChild(label);*/
-
-  executeSoon(function() {
-    this.setReady();
-  }.bind(this));
 }
 
 DevToolPanel.prototype = {
+  open: function() {
+    let deferred = Promise.defer();
+
+    executeSoon(function() {
+      this._isReady = true;
+      this.emit("ready");
+      deferred.resolve(this);
+    }.bind(this));
+
+    return deferred.promise;
+  },
+
   get target() this._toolbox.target,
 
   get toolbox() this._toolbox,
 
   get isReady() this._isReady,
 
   _isReady: false,
 
-  setReady: function() {
-    this._isReady = true;
-    this.emit("ready");
-  },
-
-  destroy: function DTI_destroy()
-  {
-
+  destroy: function DTI_destroy() {
+    return Promise.defer(null);
   },
 };
--- a/browser/devtools/framework/test/browser_new_activation_workflow.js
+++ b/browser/devtools/framework/test/browser_new_activation_workflow.js
@@ -1,64 +1,69 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests devtools API
 
 const Cu = Components.utils;
 
-let toolbox;
+let toolbox, target;
 
 let tempScope = {};
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
 
 function test() {
   addTab("about:blank", function(aBrowser, aTab) {
-    loadWebConsole(aTab);
+    target = TargetFactory.forTab(gBrowser.selectedTab);
+    loadWebConsole(aTab).then(function() {
+      console.log('loaded');
+    }, console.error);
   });
 }
 
 function loadWebConsole(aTab) {
   ok(gDevTools, "gDevTools exists");
 
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  toolbox = gDevTools.openToolbox(target, "bottom", "webconsole");
-  toolbox.once("webconsole-ready", checkToolLoading);
+  return gDevTools.showToolbox(target, "webconsole").then(function(aToolbox) {
+    toolbox = aToolbox;
+    checkToolLoading();
+  }, console.error);
 }
 
 function checkToolLoading() {
   is(toolbox.currentToolId, "webconsole", "The web console is selected");
-  selectAndCheckById("jsdebugger");
-  selectAndCheckById("styleeditor");
-  testToggle();
+  ok(toolbox.isReady, "toolbox is ready")
+
+  selectAndCheckById("jsdebugger").then(function() {
+    selectAndCheckById("styleeditor").then(function() {
+      testToggle();
+    });
+  }, console.error);
 }
 
 function selectAndCheckById(id) {
   let doc = toolbox.frame.contentDocument;
 
-  toolbox.selectTool(id);
-  let tab = doc.getElementById("toolbox-tab-" + id);
-  is(tab.selected, true, "The " + id + " tab is selected");
+  return toolbox.selectTool(id).then(function() {
+    let tab = doc.getElementById("toolbox-tab-" + id);
+    is(tab.selected, true, "The " + id + " tab is selected");
+  });
 }
 
 function testToggle() {
   toolbox.once("destroyed", function() {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    toolbox = gDevTools.openToolbox(target, "bottom", "styleeditor");
-    toolbox.once("styleeditor-ready", checkStyleEditorLoaded);
+    gDevTools.showToolbox(target, "styleeditor").then(function() {
+      is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
+      finishUp();
+    });
   }.bind(this));
 
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.toggleToolboxForTarget(target);
-}
-
-function checkStyleEditorLoaded() {
-  is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
-  finishUp();
+  toolbox.destroy();
 }
 
 function finishUp() {
   toolbox.destroy();
   toolbox = null;
+  target = null;
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
+++ b/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
@@ -8,35 +8,29 @@ let temp = {};
 Cu.import("resource:///modules/devtools/Target.jsm", temp);
 let TargetFactory = temp.TargetFactory;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    openToolbox();
+    gDevTools.showToolbox(target).then(testRegister);
   }, true);
 
   content.location = "data:text/html,test for dynamically registering and unregistering tools";
 }
 
-function openToolbox()
+function testRegister(aToolbox)
 {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  toolbox = gDevTools.openToolbox(target);
-
-  toolbox.once("ready", testRegister);
-}
-
-
-function testRegister()
-{
+  toolbox = aToolbox
   gDevTools.once("tool-registered", toolRegistered);
 
   gDevTools.registerTool({
     id: "test-tool",
     label: "Test Tool",
     isTargetSupported: function() true,
     build: function() {}
   });
--- a/browser/devtools/framework/test/browser_toolbox_hosts.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts.js
@@ -7,69 +7,64 @@ Cu.import("resource:///modules/devtools/
 let DevTools = temp.DevTools;
 
 Cu.import("resource:///modules/devtools/Toolbox.jsm", temp);
 let Toolbox = temp.Toolbox;
 
 Cu.import("resource:///modules/devtools/Target.jsm", temp);
 let TargetFactory = temp.TargetFactory;
 
-let toolbox;
+let toolbox, target;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
+  target = TargetFactory.forTab(gBrowser.selectedTab);
+
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    openToolbox(testBottomHost);
+    gDevTools.showToolbox(target)
+             .then(testBottomHost, console.error)
+             .then(null, console.error);
   }, true);
 
   content.location = "data:text/html,test for opening toolbox in different hosts";
 }
 
-function openToolbox(callback)
+function testBottomHost(aToolbox)
 {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.toggleToolboxForTarget(target);
+  toolbox = aToolbox;
 
-  toolbox = gDevTools.getToolboxForTarget(target);
-  toolbox.once("ready", callback);
-}
-
-function testBottomHost()
-{
   checkHostType(Toolbox.HostType.BOTTOM);
 
   // test UI presence
   let iframe = document.getElementById("devtools-toolbox-bottom-iframe");
   ok(iframe, "toolbox bottom iframe exists");
 
   checkToolboxLoaded(iframe);
 
-  toolbox.once("host-changed", testSidebarHost);
-  toolbox.hostType = Toolbox.HostType.SIDE;
+  toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost);
 }
 
 function testSidebarHost()
 {
   checkHostType(Toolbox.HostType.SIDE);
 
   // test UI presence
   let bottom = document.getElementById("devtools-toolbox-bottom-iframe");
   ok(!bottom, "toolbox bottom iframe doesn't exist");
 
   let iframe = document.getElementById("devtools-toolbox-side-iframe");
   ok(iframe, "toolbox side iframe exists");
 
   checkToolboxLoaded(iframe);
 
-  toolbox.once("host-changed", testWindowHost);
-  toolbox.hostType = Toolbox.HostType.WINDOW;
+  toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost);
 }
 
 function testWindowHost()
 {
   checkHostType(Toolbox.HostType.WINDOW);
 
   let sidebar = document.getElementById("devtools-toolbox-side-iframe");
   ok(!sidebar, "toolbox sidebar iframe doesn't exist");
@@ -81,27 +76,24 @@ function testWindowHost()
   checkToolboxLoaded(iframe);
 
   testToolSelect();
 }
 
 function testToolSelect()
 {
   // make sure we can load a tool after switching hosts
-  toolbox.once("inspector-ready", testDestroy);
-  toolbox.selectTool("inspector");
+  toolbox.selectTool("inspector").then(testDestroy);
 }
 
 function testDestroy()
 {
-  toolbox.once("destroyed", function() {
-    openToolbox(testRememberHost);
+  toolbox.destroy().then(function() {
+    gDevTools.showToolbox(target).then(testRememberHost);
   });
-
-  toolbox.destroy();
 }
 
 function testRememberHost()
 {
   // last host was the window - make sure it's the same when re-opening
   is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered");
 
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
@@ -124,12 +116,12 @@ function checkToolboxLoaded(iframe)
   ok(tabs, "toolbox UI has been loaded into iframe");
 }
 
 function cleanup()
 {
   Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
 
   toolbox.destroy();
-  DevTools = Toolbox = toolbox = null;
+  DevTools = Toolbox = toolbox = target = null;
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/framework/test/browser_toolbox_ready.js
+++ b/browser/devtools/framework/test/browser_toolbox_ready.js
@@ -1,56 +1,47 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tempScope = {};
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
 
-let toolbox;
-
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    openToolbox();
+    gDevTools.showToolbox(target).then(testReady);
   }, true);
 
   content.location = "data:text/html,test for dynamically registering and unregistering tools";
 }
 
-function openToolbox()
+function testReady(toolbox)
 {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.toggleToolboxForTarget(target);
-
-  toolbox = gDevTools.getToolboxForTarget(target);
-
-  ok(!toolbox.isReady, "toolbox isReady isn't set yet");
-
-  try {
-    toolbox.selectTool("webconsole");
-    ok(false, "Should throw when selectTool() called before toolbox is ready");
-  }
-  catch(error) {
-    is(error.message, "Can't select tool, wait for toolbox 'ready' event")
-  }
-
-  toolbox.once("ready", testReady);
+  ok(toolbox.isReady, "toolbox isReady is set");
+  testDouble(toolbox);
 }
 
-function testReady()
+function testDouble(toolbox)
 {
-  ok(toolbox.isReady, "toolbox isReady is set");
-  cleanup();
+  let target = toolbox.target;
+  let toolId = toolbox.currentToolId;
+
+  gDevTools.showToolbox(target, toolId).then(function(toolbox2) {
+    is(toolbox2, toolbox, "same toolbox");
+    cleanup(toolbox);
+  });
 }
 
-function cleanup()
+function cleanup(toolbox)
 {
-  toolbox.destroy();
-  toolbox = null;
-  gBrowser.removeCurrentTab();
-  finish();
+  toolbox.destroy().then(function() {
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
--- a/browser/devtools/framework/test/browser_toolbox_select_event.js
+++ b/browser/devtools/framework/test/browser_toolbox_select_event.js
@@ -1,29 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let toolbox;
 
 function test() {
   addTab("about:blank", function() {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    toolbox = gDevTools.openToolbox(target, "bottom", "webconsole");
-    toolbox.once("ready", testSelect);
+    gDevTools.showToolbox(target, "webconsole").then(testSelect);
   });
 }
 
 let called = {
   inspector: false,
   webconsole: false,
   styleeditor: false,
   //jsdebugger: false,
 }
 
-function testSelect() {
+function testSelect(aToolbox) {
+  toolbox = aToolbox;
+
   info("Toolbox fired a `ready` event");
 
   toolbox.on("select", selectCB);
 
   toolbox.selectTool("inspector");
   toolbox.selectTool("webconsole");
   toolbox.selectTool("styleeditor");
   //toolbox.selectTool("jsdebugger");
--- a/browser/devtools/framework/test/browser_toolbox_sidebar.js
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js
@@ -28,33 +28,36 @@ function test() {
 
   let toolDefinition = {
     id: "fakeTool4242",
     killswitch: "devtools.fakeTool4242.enabled",
     url: toolURL,
     label: "FAKE TOOL!!!",
     isTargetSupported: function() true,
     build: function(iframeWindow, toolbox) {
-      let panel = {
-        target: toolbox.target,
-        toolbox: toolbox,
-        isReady: true,
-        destroy: function(){},
-        panelDoc: iframeWindow.document,
-      }
-      return panel;
+      let deferred = Promise.defer();
+      executeSoon(function() {
+        deferred.resolve({
+          target: toolbox.target,
+          toolbox: toolbox,
+          isReady: true,
+          destroy: function(){},
+          panelDoc: iframeWindow.document,
+        });
+      }.bind(this));
+      return deferred.promise;
     },
   };
 
   gDevTools.registerTool(toolDefinition);
 
   addTab("about:blank", function(aBrowser, aTab) {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolbox(target, "bottom", "fakeTool4242");
-    toolbox.once("fakeTool4242-ready", function(event, panel) {
+    gDevTools.showToolbox(target, toolDefinition.id).then(function(toolbox) {
+      let panel = toolbox.getPanel(toolDefinition.id);
       ok(true, "Tool open");
 
       let tabbox = panel.panelDoc.getElementById("sidebar");
       panel.sidebar = new ToolSidebar(tabbox, panel, true);
 
       panel.sidebar.on("new-tab-registered", function(event, id) {
         registeredTabs[id] = true;
       });
@@ -80,17 +83,17 @@ function test() {
         }
       });
 
       panel.sidebar.addTab("tab1", tab1URL, true);
       panel.sidebar.addTab("tab2", tab2URL);
       panel.sidebar.addTab("tab3", tab3URL);
 
       panel.sidebar.show();
-    });
+    }).then(null, console.error);
   });
 
   function allTabsReady(panel) {
     ok(registeredTabs.tab1, "tab1 registered");
     ok(registeredTabs.tab2, "tab2 registered");
     ok(registeredTabs.tab3, "tab3 registered");
     ok(readyTabs.tab1, "tab1 ready");
     ok(readyTabs.tab2, "tab2 ready");
--- a/browser/devtools/framework/test/browser_toolbox_tool_ready.js
+++ b/browser/devtools/framework/test/browser_toolbox_tool_ready.js
@@ -1,72 +1,29 @@
-/* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let temp = [];
-Cu.import("resource:///modules/devtools/Target.jsm", temp);
-let TargetFactory = temp.TargetFactory;
-
 function test() {
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
-    gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    openAllTools();
-  }, true);
+  addTab().then(function(data) {
+    let toolIds = [ "jsdebugger", "styleeditor", "webconsole", "inspector" ];
 
-  function openAllTools() {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-    let tools = gDevTools.getToolDefinitions();
-    let expectedCallbacksCount = tools.size;
+    let open = function(index) {
+      let toolId = toolIds[index];
 
-    let firstTool = null;
-    // we transform the map to a [id, eventHasBeenFiredYet] map
-    for (let [id] of tools) {
-      if (!firstTool)
-        firstTool = id;
-      tools.set(id, false);
-    }
-
-    let toolbox = gDevTools.openToolbox(target, undefined, firstTool);
+      info("About to open " + index + "/" + toolId);
+      gDevTools.showToolbox(data.target, toolId).then(function(toolbox) {
+        ok(toolbox, "toolbox exists for " + toolId);
+        is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
 
-    // We add the event listeners
-    for (let [toolId] of tools) {
-      let id = toolId;
-      info("Registering listener for " + id);
-      tools.set(id, false);
-      toolbox.on(id + "-ready", function(event, panel) {
-        expectedCallbacksCount--;
-        info("Got event "  + event);
-        is(toolbox.getToolPanels().get(id), panel, "Got the right tool panel for " + id);
-        tools.set(id, true);
-        if (expectedCallbacksCount == 0) {
-          // "executeSoon" because we want to let a chance
-          // to falsy code to fire unexpected ready events.
-          executeSoon(theEnd);
-        }
-        if (expectedCallbacksCount < 0) {
-          ok(false, "we are receiving too many events");
+        let nextIndex = index + 1;
+        if (nextIndex >= toolIds.length) {
+          toolbox.destroy();
+          finish();
         }
-      });
-    }
-
-    toolbox.once("ready", function() {
-      // We open all the 
-      for (let [id] of tools) {
-        if (id != firstTool) {
-          toolbox.selectTool(id);
+        else {
+          open(nextIndex);
         }
-      }
-    });
+      }, console.error);
+    };
 
-    function theEnd() {
-      for (let [id, called] of tools) {
-        ok(called, "Tool " + id + " has fired its ready event");
-      }
-      toolbox.destroy();
-      gBrowser.removeCurrentTab();
-      finish();
-    }
-  }
+    open(0);
+  }).then(null, console.error);
 }
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -2,35 +2,48 @@
  * 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/. */
 
 let tempScope = {};
 Components.utils.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
 Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
+Components.utils.import("resource://gre/modules/commonjs/promise/core.js", tempScope);
+let Promise = tempScope.Promise;
 
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  content.location = aURL;
+  if (aURL != null) {
+    content.location = aURL;
+  }
+
+  let deferred = Promise.defer();
 
   let tab = gBrowser.selectedTab;
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
   let browser = gBrowser.getBrowserForTab(tab);
 
   function onTabLoad() {
     browser.removeEventListener("load", onTabLoad, true);
-    aCallback(browser, tab, browser.contentDocument);
+
+    if (aCallback != null) {
+      aCallback(browser, tab, browser.contentDocument);
+    }
+
+    deferred.resolve({ browser: browser, tab: tab, target: target });
   }
 
   browser.addEventListener("load", onTabLoad, true);
+  return deferred.promise;
 }
 
 registerCleanupFunction(function tearDown() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
--- a/browser/devtools/inspector/CmdInspect.jsm
+++ b/browser/devtools/inspector/CmdInspect.jsm
@@ -27,22 +27,13 @@ gcli.addCommand({
       description: gcli.lookup("inspectNodeDesc"),
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
   exec: function Command_inspect(args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
 
-    let node = args.selector;
-
-    let inspector = gDevTools.getPanelForTarget("inspector", target);
-    if (inspector && inspector.isReady) {
-      inspector.selection.setNode(node, "gcli");
-    } else {
-      let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-      toolbox.once("inspector-ready", function(event, panel) {
-        let inspector = gDevTools.getPanelForTarget("inspector", target);
-        inspector.selection.setNode(node, "gcli");
-      }.bind(this));
-    }
+    return gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
+    }.bind(this));
   }
 });
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["InspectorPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "MarkupView",
   "resource://gre/modules/devtools/MarkupView.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Selection",
   "resource:///modules/devtools/Selection.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HTMLBreadcrumbs",
   "resource:///modules/devtools/Breadcrumbs.jsm");
@@ -29,96 +30,101 @@ const LAYOUT_CHANGE_TIMER = 250;
  * Represents an open instance of the Inspector for a tab.
  * The inspector controls the highlighter, the breadcrumbs,
  * the markup view, and the sidebar (computed view, rule view
  * and layout view).
  */
 this.InspectorPanel = function InspectorPanel(iframeWindow, toolbox) {
   this._toolbox = toolbox;
   this._target = toolbox._target;
-
-  if (this.target.isRemote) {
-    throw "Unsupported target";
-  }
+  this.panelDoc = iframeWindow.document;
+  this.panelWin = iframeWindow;
+  this.panelWin.inspector = this;
 
   this.tabTarget = (this.target.tab != null);
   this.winTarget = (this.target.window != null);
 
   new EventEmitter(this);
-
-  this.preventNavigateAway = this.preventNavigateAway.bind(this);
-  this.onNavigatedAway = this.onNavigatedAway.bind(this);
-  this.target.on("will-navigate", this.preventNavigateAway);
-  this.target.on("navigate", this.onNavigatedAway);
-
-  this.panelDoc = iframeWindow.document;
-  this.panelWin = iframeWindow;
-  this.panelWin.inspector = this;
-
-  this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
-  this.lastNodemenuItem = this.nodemenu.lastChild;
-  this._setupNodeMenu = this._setupNodeMenu.bind(this);
-  this._resetNodeMenu = this._resetNodeMenu.bind(this);
-  this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
-  this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
-
-  // Create an empty selection
-  this._selection = new Selection();
-  this.onNewSelection = this.onNewSelection.bind(this);
-  this.selection.on("new-node", this.onNewSelection);
-
-  this.breadcrumbs = new HTMLBreadcrumbs(this);
-
-  if (this.tabTarget) {
-    this.browser = this.target.tab.linkedBrowser;
-    this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
-    this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
-
-    this.highlighter = new Highlighter(this.target, this, this._toolbox);
-    let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
-    button.hidden = false;
-    this.updateInspectorButton = function() {
-      if (this.highlighter.locked) {
-        button.removeAttribute("checked");
-      } else {
-        button.setAttribute("checked", "true");
-      }
-    }.bind(this);
-    this.highlighter.on("locked", this.updateInspectorButton);
-    this.highlighter.on("unlocked", this.updateInspectorButton);
-  }
-
-  this._initMarkup();
-  this.isReady = false;
-
-  this.once("markuploaded", function() {
-    this.isReady = true;
-
-    // All the components are initialized. Let's select a node.
-    if (this.tabTarget) {
-      let root = this.browser.contentDocument.documentElement;
-      this._selection.setNode(root);
-    }
-    if (this.winTarget) {
-      let root = this.target.window.document.documentElement;
-      this._selection.setNode(root);
-    }
-
-    if (this.highlighter) {
-      this.highlighter.unlock();
-    }
-
-    this.emit("ready");
-  }.bind(this));
-
-  this.setupSidebar();
 }
 
 InspectorPanel.prototype = {
   /**
+   * open is effectively an asynchronous constructor
+   */
+  open: function InspectorPanel_open() {
+    let deferred = Promise.defer();
+
+    this.preventNavigateAway = this.preventNavigateAway.bind(this);
+    this.onNavigatedAway = this.onNavigatedAway.bind(this);
+    this.target.on("will-navigate", this.preventNavigateAway);
+    this.target.on("navigate", this.onNavigatedAway);
+
+    this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
+    this.lastNodemenuItem = this.nodemenu.lastChild;
+    this._setupNodeMenu = this._setupNodeMenu.bind(this);
+    this._resetNodeMenu = this._resetNodeMenu.bind(this);
+    this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
+    this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
+
+    // Create an empty selection
+    this._selection = new Selection();
+    this.onNewSelection = this.onNewSelection.bind(this);
+    this.selection.on("new-node", this.onNewSelection);
+
+    this.breadcrumbs = new HTMLBreadcrumbs(this);
+
+    if (this.tabTarget) {
+      this.browser = this.target.tab.linkedBrowser;
+      this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
+      this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
+
+      this.highlighter = new Highlighter(this.target, this, this._toolbox);
+      let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
+      button.hidden = false;
+      this.updateInspectorButton = function() {
+        if (this.highlighter.locked) {
+          button.removeAttribute("checked");
+        } else {
+          button.setAttribute("checked", "true");
+        }
+      }.bind(this);
+      this.highlighter.on("locked", this.updateInspectorButton);
+      this.highlighter.on("unlocked", this.updateInspectorButton);
+    }
+
+    this._initMarkup();
+    this.isReady = false;
+
+    this.once("markuploaded", function() {
+      this.isReady = true;
+
+      // All the components are initialized. Let's select a node.
+      if (this.tabTarget) {
+        let root = this.browser.contentDocument.documentElement;
+        this._selection.setNode(root);
+      }
+      if (this.winTarget) {
+        let root = this.target.window.document.documentElement;
+        this._selection.setNode(root);
+      }
+
+      if (this.highlighter) {
+        this.highlighter.unlock();
+      }
+
+      this.emit("ready");
+      deferred.resolve(this);
+    }.bind(this));
+
+    this.setupSidebar();
+
+    return deferred.promise;
+  },
+
+  /**
    * Selection object (read only)
    */
   get selection() {
     return this._selection;
   },
 
   /**
    * Target getter.
@@ -269,20 +275,21 @@ InspectorPanel.prototype = {
     this.cancelLayoutChange();
   },
 
   /**
    * Destroy the inspector.
    */
   destroy: function InspectorPanel__destroy() {
     if (this._destroyed) {
-      return;
+      return Promise.resolve(null);
     }
+    this._destroyed = true;
+
     this.cancelLayoutChange();
-    this._destroyed = true;
 
     this._toolbox = null;
 
     if (this.browser) {
       this.browser.removeEventListener("resize", this.scheduleLayoutChange, true);
       this.browser = null;
     }
 
@@ -309,16 +316,18 @@ InspectorPanel.prototype = {
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
     this.lastNodemenuItem = null;
     this.nodemenu = null;
     this.highlighter = null;
+
+    return Promise.resolve(null);
   },
 
   /**
    * Show the node menu.
    */
   showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) {
     if (aExtraItems) {
       for (let item of aExtraItems) {
--- a/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
+++ b/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
@@ -63,17 +63,17 @@ function test()
         inspector.selection.setNode(node);
       }
     });
   }
 
   function performTest()
   {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let panel = gDevTools.getPanelForTarget("inspector", target);
+    let panel = gDevTools.getToolbox(target).getPanel("inspector");
     let container = panel.panelDoc.getElementById("inspector-breadcrumbs");
     let buttonsLabelIds = nodes[cursor].result.split(" ");
 
     // html > body > …
     is(container.childNodes.length, buttonsLabelIds.length + 2, "Node " + cursor + ": Items count");
 
     for (let i = 2; i < container.childNodes.length; i++) {
       let expectedId = "#" + buttonsLabelIds[i - 2];
--- a/browser/devtools/inspector/test/browser_inspector_bug_566084_location_changed.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_566084_location_changed.js
@@ -19,17 +19,17 @@ function test() {
 
     let para = content.document.querySelector("p");
     ok(para, "found the paragraph element");
     is(para.textContent, "init", "paragraph content is correct");
 
     inspector.markDirty();
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.getToolboxForTarget(target);
+    let toolbox = gDevTools.getToolbox(target);
     notificationBox = toolbox.getNotificationBox();
     notificationBox.addEventListener("AlertActive", alertActive1, false);
 
     ok(toolbox, "We have access to the notificationBox");
 
     gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
 
     content.location = "data:text/html,<div>location change test 1 for " +
@@ -54,17 +54,17 @@ function test() {
 
   function locationTest2() {
     // Location did not change.
     let para = content.document.querySelector("p");
     ok(para, "found the paragraph element, second time");
     is(para.textContent, "init", "paragraph content is correct");
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let inspector = gDevTools.getPanelForTarget("inspector", target);
+    let inspector = gDevTools.getToolbox(target).getPanel("inspector");
     ok(inspector, "Inspector still alive");
 
     notificationBox.addEventListener("AlertActive", alertActive2, false);
 
     content.location = "data:text/html,<div>location change test 2 for " +
       "inspector</div><p>test2</p>";
   }
 
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js
+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
@@ -20,27 +20,27 @@ function createDocument()
     'solely to provide some things to test the inspector initialization.</p>\n' +
     'If you are reading this, you should go do something else instead. Maybe ' +
     'read a book. Or better yet, write some test-cases for another bit of code. ' +
     '<span style="{font-style: italic}">Maybe more inspector test-cases!</span></p>\n' +
     '<p id="closing">end transmission</p>\n' +
     '</div>';
   doc.title = "Inspector Initialization Test";
 
-  openInspector(startInspectorTests);
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    startInspectorTests(toolbox);
+  }).then(null, console.error);
 }
 
-function startInspectorTests()
+function startInspectorTests(toolbox)
 {
+  let inspector = toolbox.getCurrentPanel();
   ok(true, "Inspector started, and notification received.");
 
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
-
   ok(inspector, "Inspector instance is accessible");
   ok(inspector.isReady, "Inspector instance is ready");
   is(inspector.target.tab, gBrowser.selectedTab, "Valid target");
   ok(inspector.highlighter, "Highlighter is up");
 
   let p = doc.querySelector("p");
 
   inspector.selection.setNode(p);
@@ -53,21 +53,20 @@ function startInspectorTests()
   span.scrollIntoView();
 
   inspector.selection.setNode(span);
 
   testHighlighter(span);
   testMarkupView(span);
   testBreadcrumbs(span);
 
-  let toolbox = gDevTools.getToolboxForTarget(target);
   toolbox.once("destroyed", function() {
     ok("true", "'destroyed' notification received.");
-    let toolbox = gDevTools.getToolboxForTarget(target);
-    ok(!toolbox, "Toolbox destroyed.");
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    ok(!gDevTools.getToolbox(target), "Toolbox destroyed.");
     executeSoon(runContextMenuTest);
   });
   toolbox.destroy();
 }
 
 
 function testHighlighter(node)
 {
@@ -89,24 +88,23 @@ function testBreadcrumbs(node)
   ok(button, "A crumbs is checked=true");
   is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node");
 }
 
 function _clickOnInspectMenuItem(node) {
   document.popupNode = node;
   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
-  contextMenu.inspectNode();
+  return contextMenu.inspectNode();
 }
 
 function runContextMenuTest()
 {
   salutation = doc.getElementById("salutation");
-  _clickOnInspectMenuItem(salutation);
-  gDevTools.once("inspector-ready", testInitialNodeIsSelected);
+  _clickOnInspectMenuItem(salutation).then(testInitialNodeIsSelected);
 }
 
 function testInitialNodeIsSelected() {
   testHighlighter(salutation);
   testMarkupView(salutation);
   testBreadcrumbs(salutation);
   inspectNodesFromContextTestWhileOpen();
 }
--- a/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
+++ b/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
@@ -125,11 +125,11 @@ function finishUp()
     testRemoved();
     inspector = ruleview = null;
     doc = div = null;
     gBrowser.removeCurrentTab();
     finish();
   });
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.getToolboxForTarget(target);
+  let toolbox = gDevTools.getToolbox(target);
   toolbox.destroy();
 }
--- a/browser/devtools/inspector/test/browser_inspector_sidebarstate.js
+++ b/browser/devtools/inspector/test/browser_inspector_sidebarstate.js
@@ -24,17 +24,17 @@ function inspectorRuleViewOpened()
 {
   is(inspector.sidebar.getCurrentTabID(), "ruleview", "Rule View is selected by default");
 
   // Select the computed view and turn off the inspector.
   inspector.sidebar.select("computedview");
 
   gDevTools.once("toolbox-destroyed", inspectorClosed);
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.getToolboxForTarget(target);
+  let toolbox = gDevTools.getToolbox(target);
   executeSoon(function() {
     toolbox.destroy();
   });
 }
 
 function inspectorClosed()
 {
   openInspector(function(panel) {
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -3,41 +3,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cu = Components.utils;
 let tempScope = {};
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
 let LayoutHelpers = tempScope.LayoutHelpers;
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
+Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope);
+let console = tempScope.console;
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 
 function openInspector(callback)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
-  if (inspector && inspector.isReady) {
-    callback(inspector);
-  } else {
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-ready", function(event, panel) {
-      let inspector = gDevTools.getPanelForTarget("inspector", target);
-      callback(inspector);
-    });
-  }
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    callback(toolbox.getCurrentPanel());
+  }).then(null, console.error);
 }
 
 function getActiveInspector()
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  return gDevTools.getPanelForTarget("inspector", target);
+  return gDevTools.getToolbox(target).getPanel("inspector");
 }
 
 function isHighlighting()
 {
   let outline = getActiveInspector().highlighter.outline;
   return !(outline.getAttribute("hidden") == "true");
 }
 
--- a/browser/devtools/layoutview/test/browser_layoutview.js
+++ b/browser/devtools/layoutview/test/browser_layoutview.js
@@ -62,20 +62,18 @@ function test() {
   let html = "<style>" + style + "</style><div></div>"
   content.location = "data:text/html," + encodeURIComponent(html);
 
   function setupTest() {
     node = doc.querySelector("div");
     ok(node, "node found");
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-ready", function(event, panel) {
-      let inspector = gDevTools.getPanelForTarget("inspector", target);
-      openLayoutView(inspector);
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      openLayoutView(toolbox.getCurrentPanel());
     });
   }
 
   function openLayoutView(aInspector) {
     inspector = aInspector;
 
     info("Inspector open");
 
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
@@ -200,19 +200,18 @@ function test() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
   content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_edit.html";
 
   function setupTest() {
     var target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
-      inspector = aInspector;
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      inspector = toolbox.getCurrentPanel();
       runTests();
     });
   }
 
   function runTests() {
     inspector.selection.once("new-node", startTests);
     executeSoon(function() {
       inspector.selection.setNode(doc.body);
--- a/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
@@ -141,19 +141,18 @@ function test() {
     }, true);
     content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_mutation.html";
   }, true);
 
   content.location = "data:text/html,<html></html>";
 
   function setupTest() {
     var target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-selected", function BIMMT_selected(id, aInspector) {
-      inspector = aInspector;
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      inspector = toolbox.getCurrentPanel();
       startTests();
     });
   }
 
   function startTests() {
     markup = inspector.markup;
     checkMarkup();
     nextStep(0);
--- a/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
@@ -69,19 +69,18 @@ function test() {
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
   content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_navigation.html";
 
   function setupTest() {
     var target = TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-selected", function BIMNT_selected(id, aInspector) {
-      inspector = aInspector;
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      inspector = toolbox.getCurrentPanel();
       startNavigation();
     });
   }
 
   function startNavigation() {
     nextStep(0);
   }
 
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -159,17 +159,17 @@ function ResponsiveUI(aWindow, aTab)
   this.tab.addEventListener("TabClose", this);
   this.tabContainer.addEventListener("TabSelect", this);
   this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
 
   this.buildUI();
   this.checkMenus();
 
   let target = TargetFactory.forTab(this.tab);
-  this.toolboxWasOpen = !!gDevTools.getToolboxForTarget(target);
+  this.toolboxWasOpen = !!gDevTools.getToolbox(target);
 
   try {
     if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
       this.rotate();
     }
   } catch(e) {}
 
   if (this._floatingScrollbars)
@@ -244,17 +244,17 @@ ResponsiveUI.prototype = {
     if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE &&
         this.mainWindow.gBrowser.selectedBrowser == this.browser) {
 
       // If the toolbox wasn't open at first but is open now,
       // we don't want to close the Responsive Mode on Escape.
       // We let the toolbox close first.
 
       let target = TargetFactory.forTab(this.tab);
-      let isToolboxOpen =  !!gDevTools.getToolboxForTarget(target);
+      let isToolboxOpen =  !!gDevTools.getToolbox(target);
       if (this.toolboxWasOpen || !isToolboxOpen) {
         aEvent.preventDefault();
         aEvent.stopPropagation();
         this.close();
       }
     }
   },
 
--- a/browser/devtools/responsivedesign/test/head.js
+++ b/browser/devtools/responsivedesign/test/head.js
@@ -9,21 +9,13 @@ let TargetFactory = tempScope.TargetFact
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 
 function openInspector(callback)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
-  if (inspector && inspector.isReady) {
-    callback(inspector);
-  } else {
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-ready", function(event, panel) {
-      let inspector = gDevTools.getPanelForTarget("inspector", target);
-      callback(inspector);
-    });
-  }
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    callback(toolbox.getCurrentPanel());
+  });
 }
 
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -1051,17 +1051,17 @@ var Scratchpad = {
   },
 
   /**
    * Open the Web Console.
    */
   openWebConsole: function SP_openWebConsole()
   {
     let target = TargetFactory.forTab(this.gBrowser.selectedTab);
-    gDevTools.openToolboxForTab(target, "webconsole");
+    gDevTools.showToolbox(target, "webconsole");
     this.browserWindow.focus();
   },
 
   /**
    * Set the current execution context to be the active tab content window.
    */
   setContentContext: function SP_setContentContext()
   {
--- a/browser/devtools/shared/EventEmitter.jsm
+++ b/browser/devtools/shared/EventEmitter.jsm
@@ -87,13 +87,21 @@ EventEmitter.prototype = {
       if (!this._eventEmitterListeners) {
         break;
       }
 
       // If listeners were removed during emission, make sure the
       // event handler we're going to fire wasn't removed.
       if (originalListeners === this._eventEmitterListeners.get(aEvent) ||
           this._eventEmitterListeners.get(aEvent).some(function(l) l === listener)) {
-        listener.apply(null, arguments);
+        try {
+          listener.apply(null, arguments);
+        }
+        catch (ex) {
+          // Prevent a bad listener from interfering with the others.
+          let msg = ex + ": " + ex.stack;
+          Components.utils.reportError(msg);
+          dump(msg + "\n");
+        }
       }
     }
   },
 }
--- a/browser/devtools/shared/test/browser_toolbar_basic.js
+++ b/browser/devtools/shared/test/browser_toolbar_basic.js
@@ -34,18 +34,17 @@ function checkOpen() {
   let close = document.getElementById("developer-toolbar-closebutton");
   ok(close, "Close button exists");
 
   let toggleToolbox =
     document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
   ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.openToolboxForTab(target, "webconsole");
-  toolbox.once("webconsole-selected", function BTBT_selected(id, aInspector) {
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     ok(isChecked(toggleToolbox), "toggle toolbox button is checked");
 
     addTab("about:blank", function(browser, tab) {
       info("Opened a new tab");
 
       ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked");
 
       gBrowser.removeCurrentTab();
--- a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -107,17 +107,17 @@ function test() {
       subject.QueryInterface(Ci.nsISupportsString);
       let hud = HUDService.getHudReferenceById(subject.data);
       executeSoon(callback.bind(null, hud));
     }
 
     oneTimeObserve("web-console-created", _onWebConsoleOpen);
 
     let target = TargetFactory.forTab(tab);
-    gDevTools.openToolboxForTab(target, "webconsole");
+    gDevTools.showToolbox(target, "webconsole");
   }
 
   function onWebConsoleOpen(hud) {
     waitForValue({
       name: "web console shows the page errors",
       validator: function() {
         return hud.outputNode.querySelectorAll(".hud-exception").length;
       },
--- a/browser/devtools/styleeditor/StyleEditorPanel.jsm
+++ b/browser/devtools/styleeditor/StyleEditorPanel.jsm
@@ -1,23 +1,24 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 this.EXPORTED_SYMBOLS = ["StyleEditorPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorChrome",
-  "resource:///modules/devtools/StyleEditorChrome.jsm");
+                        "resource:///modules/devtools/StyleEditorChrome.jsm");
 
 this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
   new EventEmitter(this);
 
   this._toolbox = toolbox;
   this._target = toolbox.target;
 
   this.reset = this.reset.bind(this);
@@ -25,30 +26,34 @@ this.StyleEditorPanel = function StyleEd
   this.destroy = this.destroy.bind(this);
 
   this._target.on("will-navigate", this.reset);
   this._target.on("navigate", this.newPage);
   this._target.on("close", this.destroy);
 
   this._panelWin = panelWin;
   this._panelDoc = panelWin.document;
-
-  let contentWin = toolbox.target.window;
-  this.setPage(contentWin);
-
-  this.isReady = true;
 }
 
 StyleEditorPanel.prototype = {
   /**
+   * open is effectively an asynchronous constructor
+   */
+  open: function StyleEditor_open() {
+    let contentWin = this._toolbox.target.window;
+    this.setPage(contentWin);
+    this.isReady = true;
+
+    return Promise.resolve(this);
+  },
+
+  /**
    * Target getter.
    */
-  get target() {
-    return this._target;
-  },
+  get target() this._target,
 
   /**
    * Panel window getter.
    */
   get panelWindow() this._panelWin,
 
   /**
    * StyleEditorChrome instance getter.
@@ -89,22 +94,23 @@ StyleEditorPanel.prototype = {
   selectStyleSheet: function StyleEditor_selectStyleSheet(stylesheet, line, col) {
     this._panelWin.styleEditorChrome.selectStyleSheet(stylesheet, line, col);
   },
 
   /**
    * Destroy StyleEditor
    */
   destroy: function StyleEditor_destroy() {
-    if (this._destroyed) {
-      return;
-    }
-    this._destroyed = true;
+    if (!this._destroyed) {
+      this._destroyed = true;
 
-    this._target.off("will-navigate", this.reset);
-    this._target.off("navigate", this.newPage);
-    this._target.off("close", this.destroy);
-    this._target = null;
-    this._toolbox = null;
-    this._panelWin = null;
-    this._panelDoc = null;
+      this._target.off("will-navigate", this.reset);
+      this._target.off("navigate", this.newPage);
+      this._target.off("close", this.destroy);
+      this._target = null;
+      this._toolbox = null;
+      this._panelWin = null;
+      this._panelDoc = null;
+    }
+
+    return Promise.resolve(null);
   },
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
@@ -19,17 +19,17 @@ function test() {
   Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
   let FileUtils = tempScope.FileUtils;
 
 
   waitForExplicitFinish();
 
   addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
-    toolbox = gDevTools.getToolboxForTarget(target);
+    toolbox = gDevTools.getToolbox(target);
 
     aChrome.addChromeListener({
       onEditorAdded: function (aChrome, aEditor) {
         if (aEditor.styleSheetIndex != 0) {
           return; // we want to test against the first stylesheet
         }
 
         if (aEditor.sourceEditor) {
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -4,16 +4,18 @@
 const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_HOST = 'mochi.test:8888';
 
 let tempScope = {};
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
+Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope);
+let console = tempScope.console;
 
 let gChromeWindow;               //StyleEditorChrome window
 let cache = Cc["@mozilla.org/network/cache-service;1"]
               .getService(Ci.nsICacheService);
 
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
@@ -30,36 +32,25 @@ function cleanup()
 function launchStyleEditorChrome(aCallback, aSheet, aLine, aCol)
 {
   launchStyleEditorChromeFromWindow(window, aCallback, aSheet, aLine, aCol);
 }
 
 function launchStyleEditorChromeFromWindow(aWindow, aCallback, aSheet, aLine, aCol)
 {
   let target = TargetFactory.forTab(aWindow.gBrowser.selectedTab);
-
-  let panel = aWindow.gDevTools.getPanelForTarget("styleeditor", target);
-  if (panel && panel.isReady) {
+  gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+    let panel = toolbox.getCurrentPanel();
     gChromeWindow = panel._panelWin;
     gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
     if (aSheet) {
       panel.selectStyleSheet(aSheet, aLine, aCol);
     }
     aCallback(gChromeWindow.styleEditorChrome);
-  } else {
-    let toolbox = aWindow.gDevTools.openToolboxForTab(target, "styleeditor");
-    toolbox.once("styleeditor-ready", function(event, panel) {
-      gChromeWindow = panel._panelWin;
-      gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
-      if (aSheet) {
-        panel.selectStyleSheet(aSheet, aLine, aCol);
-      }
-      aCallback(gChromeWindow.styleEditorChrome);
-    });
-  }
+  });
 }
 
 function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback, aSheet, aLine, aCol)
 {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -1252,22 +1252,19 @@ SelectorView.prototype = {
         }
       }
     }
 
     if (contentSheet) {
       let target = inspector.target;
 
       if (styleEditorDefinition.isTargetSupported(target)) {
-        let toolbox = gDevTools.getToolboxForTarget(target);
-
-        toolbox.once("styleeditor-selected", function SE_selected(id, styleEditor) {
-          styleEditor.selectStyleSheet(styleSheet, line);
+        gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+          toolbox.getCurrentPanel().selectStyleSheet(styleSheet, line);
         });
-        toolbox.selectTool("styleeditor");
       }
     } else {
       let href = styleSheet ? styleSheet.href : "";
       let viewSourceUtils = inspector.viewSourceUtils;
 
       if (this.selectorInfo.sourceElement) {
         href = this.selectorInfo.sourceElement.ownerDocument.location.href;
       }
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -57,22 +57,19 @@ this.RuleViewTool = function RVT_RuleVie
         break;
       }
     }
 
     if (contentSheet)  {
       let target = this.inspector.target;
 
       if (styleEditorDefinition.isTargetSupported(target)) {
-        let toolbox = gDevTools.getToolboxForTarget(target);
-
-        toolbox.once("styleeditor-selected", function SE_selected(id, styleEditor) {
-          styleEditor.selectStyleSheet(styleSheet, line);
+        gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+          toolbox.getCurrentPanel().selectStyleSheet(styleSheet, line);
         });
-        toolbox.selectTool("styleeditor");
       }
     } else {
       let href = styleSheet ? styleSheet.href : "";
       if (rule.elementStyle.element) {
         href = rule.elementStyle.element.ownerDocument.location.href;
       }
       let viewSourceUtils = this.inspector.viewSourceUtils;
       viewSourceUtils.viewSource(href, null, contentDoc, line);
--- a/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
@@ -77,19 +77,18 @@ function testInlineStyle()
   });
 }
 
 function testInlineStyleSheet()
 {
   info("clicking an inline stylesheet");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.openToolboxForTab(target, "styleeditor");
-
-  toolbox.once("styleeditor-ready", function(event, panel) {
+  gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+    let panel = toolbox.getCurrentPanel();
     let win = panel._panelWin;
 
     win.styleEditorChrome.addChromeListener({
       onEditorAdded: function checkEditor(aChrome, aEditor) {
         if (!aEditor.sourceEditor) {
           aEditor.addActionListener({
             onAttach: function (aEditor) {
               aEditor.removeActionListener(this);
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -29,19 +29,19 @@ function createDocument()
     'yet, write some test-cases for another bit of code. ' +
     '<span style="font-style: italic">some text</span></p>\n' +
     '<p id="closing">more text</p>\n' +
     '<p>even more text</p>' +
     '</div>';
   doc.title = "Rule view style editor link test";
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  toolbox = gDevTools.openToolboxForTab(target, "inspector");
-  toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
-    inspector = aInspector;
+  gDevTools.showToolbox(target, "inspector").then(function(aToolbox) {
+    toolbox = aToolbox;
+    inspector = toolbox.getCurrentPanel();
     inspector.sidebar.select("ruleview");
     highlightNode();
   });
 }
 
 function highlightNode()
 {
   // Highlight a node.
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -30,19 +30,18 @@ function createDocument()
     'yet, write some test-cases for another bit of code. ' +
     '<span style="font-style: italic">some text</span></p>\n' +
     '<p id="closing">more text</p>\n' +
     '<p>even more text</p>' +
     '</div>';
   doc.title = "Rule view context menu test";
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-  toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
-    inspector = aInspector;
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    inspector = toolbox.getCurrentPanel();
     inspector.sidebar.select("ruleview");
     win = inspector.sidebar.getWindowForTab("ruleview");
     highlightNode();
   });
 }
 
 function highlightNode()
 {
--- a/browser/devtools/styleinspector/test/browser_ruleview_focus.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js
@@ -10,19 +10,18 @@ Cu.import("resource:///modules/devtools/
 let inplaceEditor = tempScope._getInplaceEditorForSpan;
 let doc;
 let inspector;
 let stylePanel;
 
 function openRuleView()
 {
   var target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-  toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
-    inspector = aInspector;
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    inspector = toolbox.getCurrentPanel();
     inspector.sidebar.select("ruleview");
 
     // Highlight a node.
     let node = content.document.getElementsByTagName("h1")[0];
     inspector.selection.once("new-node", testFocus);
     executeSoon(function() {
       inspector.selection.setNode(doc.body);
     });
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -8,39 +8,34 @@ Cu.import("resource:///modules/devtools/
 Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tempScope);
 Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
 let ConsoleUtils = tempScope.ConsoleUtils;
 let CssLogic = tempScope.CssLogic;
 let CssHtmlTree = tempScope.CssHtmlTree;
 let gDevTools = tempScope.gDevTools;
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
+Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope);
+let console = tempScope.console;
 
 let browser, hudId, hud, hudBox, filterBox, outputNode, cs;
 
 function addTab(aURL)
 {
   gBrowser.selectedTab = gBrowser.addTab();
   content.location = aURL;
   browser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
 }
 
 function openInspector(callback)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
-  if (inspector && inspector.isReady) {
-    callback(inspector);
-  } else {
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-ready", function(event, panel) {
-      let inspector = gDevTools.getPanelForTarget("inspector", target);
-      callback(inspector);
-    });
-  }
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    callback(toolbox.getCurrentPanel());
+  });
 }
 
 function addStyle(aDocument, aString)
 {
   let node = aDocument.createElement('style');
   node.setAttribute("type", "text/css");
   node.textContent = aString;
   aDocument.getElementsByTagName("head")[0].appendChild(node);
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -170,19 +170,19 @@ TiltVisualizer.prototype = {
     Services.obs.addObserver(this.onNewNodeFromTilt,
                              this.presenter.NOTIFICATIONS.HIGHLIGHTING,
                              false);
     Services.obs.addObserver(this.onNewNodeFromTilt,
                              this.presenter.NOTIFICATIONS.UNHIGHLIGHTING,
                              false);
 
     let target = TargetFactory.forTab(aTab);
-    let inspector = gDevTools.getPanelForTarget("inspector", target);
-    if (inspector) {
-      this.inspector = inspector;
+    let toolbox = gDevTools.getToolbox(target);
+    if (toolbox) {
+      this.inspector = toolbox.getPanel("inspector");
       this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
       this.inspector.selection.on("detached", this.onNewNodeFromInspector);
       this.onNewNodeFromInspector();
     }
   },
 
   /**
    * Unregister inspector event listeners.
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -530,22 +530,19 @@ WebConsole.prototype = {
   viewSourceInStyleEditor:
   function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
   {
     let styleSheets = this.tab.linkedBrowser.contentWindow.document.styleSheets;
     for each (let style in styleSheets) {
       if (style.href == aSourceURL) {
         let target = TargetFactory.forTab(this.tab);
         let gDevTools = this.chromeWindow.gDevTools;
-        let toolbox = gDevTools.getToolboxForTarget(target);
-        toolbox.once("styleeditor-selected",
-          function _onStyleEditorReady(aEvent, aPanel) {
-            aPanel.selectStyleSheet(style, aSourceLine);
-          });
-        toolbox.selectTool("styleeditor");
+        gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+          toolbox.getCurrentPanel().selectStyleSheet(style, aSourceLine);
+        });
         return;
       }
     }
     // Open view source if style editor fails.
     this.viewSource(aSourceURL, aSourceLine);
   },
 
   /**
@@ -597,17 +594,21 @@ WebConsole.prototype = {
 // HeadsUpDisplayUICommands
 //////////////////////////////////////////////////////////////////////////
 
 var HeadsUpDisplayUICommands = {
   toggleHUD: function UIC_toggleHUD(aOptions)
   {
     var window = HUDService.currentContext();
     let target = TargetFactory.forTab(window.gBrowser.selectedTab);
-    gDevTools.toggleToolboxForTarget(target, "webconsole");
+    let toolbox = gDevTools.getToolbox(target);
+
+    return toolbox && toolbox.currentToolId == "webconsole" ?
+        toolbox.destroy() :
+        gDevTools.showToolbox(target, "webconsole");
   },
 
   toggleRemoteHUD: function UIC_toggleRemoteHUD()
   {
     if (this.getOpenHUD()) {
       this.toggleHUD();
       return;
     }
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/WebConsolePanel.jsm
@@ -1,73 +1,88 @@
 /* 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/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
 
-const Cu = Components.utils;
-const Ci = Components.interfaces;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
-  "resource:///modules/HUDService.jsm");
+                                  "resource:///modules/HUDService.jsm");
 
 /**
  * A DevToolPanel that controls the Web Console.
  */
 function WebConsolePanel(iframeWindow, toolbox) {
   this._frameWindow = iframeWindow;
   this._toolbox = toolbox;
   new EventEmitter(this);
-
-  let tab = this._toolbox._getHostTab();
-  let parentDoc = iframeWindow.document.defaultView.parent.document;
-  let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
-  this.hud = HUDService.activateHUDForContext(tab, iframe, toolbox.target);
-
-  let hudId = this.hud.hudId;
-  let onOpen = function _onWebConsoleOpen(aSubject)
-  {
-    aSubject.QueryInterface(Ci.nsISupportsString);
-    if (hudId == aSubject.data) {
-      Services.obs.removeObserver(onOpen, "web-console-created");
-      this.setReady();
-    }
-  }.bind(this);
-
-  Services.obs.addObserver(onOpen, "web-console-created", false);
 }
 
 WebConsolePanel.prototype = {
+  /**
+   * open is effectively an asynchronous constructor
+   */
+  open: function StyleEditor_open() {
+    let tab = this._toolbox._getHostTab();
+    let parentDoc = this._frameWindow.document.defaultView.parent.document;
+    let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
+    this.hud = HUDService.activateHUDForContext(tab, iframe, this._toolbox.target);
+
+    let deferred = Promise.defer();
+
+    let hudId = this.hud.hudId;
+    let onOpen = function _onWebConsoleOpen(aSubject) {
+      aSubject.QueryInterface(Ci.nsISupportsString);
+      if (hudId == aSubject.data) {
+        Services.obs.removeObserver(onOpen, "web-console-created");
+
+        this._isReady = true;
+        this.emit("ready");
+        deferred.resolve(this);
+      }
+    }.bind(this);
+
+    Services.obs.addObserver(onOpen, "web-console-created", false);
+
+    return deferred.promise;
+  },
+
   get target() this._toolbox.target,
 
   _isReady: false,
   get isReady() this._isReady,
 
   destroy: function WCP_destroy()
   {
+    if (this.destroyer) {
+      return this.destroyer.promise;
+    }
+
+    this.destroyer = Promise.defer();
+
     let hudId = this.hud.hudId;
 
     let onClose = function _onWebConsoleClose(aSubject)
     {
       aSubject.QueryInterface(Ci.nsISupportsString);
       if (hudId == aSubject.data) {
         Services.obs.removeObserver(onClose, "web-console-destroyed");
+
         this.emit("destroyed");
+        this.destroyer.resolve(null);
       }
     }.bind(this);
 
     Services.obs.addObserver(onClose, "web-console-destroyed", false);
     HUDService.deactivateHUDForContext(this.hud.tab, false);
-  },
 
-  setReady: function WCP_setReady()
-  {
-    this._isReady = true;
-    this.emit("ready");
+    return this.destroyer.promise;
   },
 };
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
@@ -40,29 +40,29 @@ function tab2Loaded(aEvent) {
     }
   }
 
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
   function openConsoles() {
     try {
       let target1 = TargetFactory.forTab(tab1);
-      gDevTools.openToolboxForTab(target1, "webconsole");
+      gDevTools.showToolbox(target1, "webconsole");
     }
     catch (ex) {
-      ok(false, "gDevTools.openToolboxForTab(target1) exception: " + ex);
+      ok(false, "gDevTools.showToolbox(target1) exception: " + ex);
       noErrors = false;
     }
 
     try {
       let target2 = TargetFactory.forTab(tab2);
-      gDevTools.openToolboxForTab(target2, "webconsole");
+      gDevTools.showToolbox(target2, "webconsole");
     }
     catch (ex) {
-      ok(false, "gDevTools.openToolboxForTab(target2) exception: " + ex);
+      ok(false, "gDevTools.showToolbox(target2) exception: " + ex);
       noErrors = false;
     }
   }
 
   let consolesClosed = 0;
   function onWebConsoleClose()
   {
     consolesClosed++;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js
@@ -11,17 +11,21 @@
 const TEST_URI = "http://example.com/browser/browser/devtools/" +
                  "webconsole/test/test-bug-597136-external-script-" +
                  "errors.html";
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
-    openConsole(null, consoleOpened);
+    openConsole(null, function(hud) {
+      executeSoon(function() {
+        consoleOpened(hud);
+      });
+    });
   }, true);
 }
 
 function consoleOpened(hud) {
   let button = content.document.querySelector("button");
   let outputNode = hud.outputNode;
 
   expectUncaughtException();
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -58,17 +58,17 @@ function runSelectionTests(aInspector)
     let h1 = content.document.querySelector("h1");
     EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
   });
 }
 
 function performTestComparisons()
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
+  let inspector = gDevTools.getToolbox(target).getPanel("inspector");
   inspector.highlighter.lock();
 
   let isHighlighting =
     !(inspector.highlighter.outline.getAttribute("hidden") == "true");
 
   ok(isHighlighting, "inspector is highlighting");
 
   let h1 = content.document.querySelector("h1");
@@ -111,17 +111,17 @@ function performWebConsoleTests(hud)
     {
       return outputNode.querySelector(".webconsole-msg-output");
     },
     successFn: function()
     {
       let node = outputNode.querySelector(".webconsole-msg-output");
       isnot(node.textContent.indexOf("bug653531"), -1,
             "correct output for $0.textContent");
-      let inspector = gDevTools.getPanelForTarget("inspector", target);
+      let inspector = gDevTools.getToolbox(target).getPanel("inspector");
       is(inspector.selection.node.textContent, "bug653531",
          "node successfully updated");
 
       executeSoon(finishUp);
     },
     failureFn: finishUp,
   };
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -35,17 +35,17 @@ function testViewSource(aHud)
       return hud.outputNode.querySelector(selector);
     },
     successFn: function()
     {
       nodes = hud.outputNode.querySelectorAll(selector);
       is(nodes.length, 2, "correct number of css messages");
 
       let target = TargetFactory.forTab(gBrowser.selectedTab);
-      let toolbox = gDevTools.getToolboxForTarget(target);
+      let toolbox = gDevTools.getToolbox(target);
       toolbox.once("styleeditor-selected", onStyleEditorReady);
 
       EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
     },
     failureFn: finishTest,
   });
 }
 
@@ -76,39 +76,37 @@ function onStyleEditorReady(aEvent, aPan
     ok(sheet, "sheet found");
     let line = nodes[0].sourceLine;
     ok(line, "found source line");
 
     checkStyleEditorForSheetAndLine(sheet, line - 1, function() {
       info("first check done");
 
       let target = TargetFactory.forTab(gBrowser.selectedTab);
-      let toolbox = gDevTools.getToolboxForTarget(target);
+      let toolbox = gDevTools.getToolbox(target);
 
       let sheet = sheetForNode(nodes[1]);
       ok(sheet, "sheet found");
       let line = nodes[1].sourceLine;
       ok(line, "found source line");
 
-      toolbox.once("webconsole-selected", function(aEvent) {
-        info(aEvent + " event fired");
+      toolbox.selectTool("webconsole").then(function() {
+        info("webconsole selected");
 
-        toolbox.once("styleeditor-selected", function() {
+        toolbox.once("styleeditor-selected", function(aEvent) {
           info(aEvent + " event fired");
 
           checkStyleEditorForSheetAndLine(sheet, line - 1, function() {
             info("second check done");
             finishTest();
           });
         });
 
         EventUtils.sendMouseEvent({ type: "click" }, nodes[1]);
       });
-
-      toolbox.selectTool("webconsole");
     });
   }, win);
 }
 
 function checkStyleEditorForSheetAndLine(aStyleSheet, aLine, aCallback)
 {
   let foundEditor = null;
   waitForSuccess({
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -5,17 +5,21 @@
 // standard View Source window.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
-    openConsole(null, testViewSource);
+    openConsole(null, function(hud) {
+      executeSoon(function() {
+        testViewSource(hud);
+      });
+    });
   }, true);
 }
 
 function testViewSource(hud) {
   let button = content.document.querySelector("button");
   button = XPCNativeWrapper.unwrap(button);
   ok(button, "we have the button on the page");
 
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -7,16 +7,18 @@ let tempScope = {};
 Cu.import("resource:///modules/HUDService.jsm", tempScope);
 let HUDService = tempScope.HUDService;
 Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
 let WebConsoleUtils = tempScope.WebConsoleUtils;
 Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
 let gDevTools = tempScope.gDevTools;
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
+Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope);
+let console = tempScope.console;
 
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
@@ -134,61 +136,47 @@ function findLogEntry(aString)
  *        Optional tab element for which you want open the Web Console. The
  *        default tab is taken from the global variable |tab|.
  * @param function [aCallback]
  *        Optional function to invoke after the Web Console completes
  *        initialization (web-console-created).
  */
 function openConsole(aTab, aCallback = function() { })
 {
-  function onWebConsoleOpen(aEvent, aPanel)
-  {
-    executeSoon(aCallback.bind(null, aPanel.hud));
-  }
-
   let target = TargetFactory.forTab(aTab || tab);
-  let toolbox = gDevTools.getToolboxForTarget(target);
-  if (toolbox) {
-    toolbox.once("webconsole-selected", onWebConsoleOpen);
-    toolbox.selectTool("webconsole");
-  }
-  else {
-    let target = TargetFactory.forTab(aTab || tab);
-    toolbox = gDevTools.openToolboxForTab(target, "webconsole");
-    toolbox.once("webconsole-selected", onWebConsoleOpen);
-  }
+  gDevTools.showToolbox(target, "webconsole").then(function(toolbox) {
+    aCallback(toolbox.getCurrentPanel().hud);
+  });
 }
 
 /**
  * Close the Web Console for the given tab.
  *
  * @param nsIDOMElement [aTab]
  *        Optional tab element for which you want close the Web Console. The
  *        default tab is taken from the global variable |tab|.
  * @param function [aCallback]
  *        Optional function to invoke after the Web Console completes
  *        closing (web-console-destroyed).
  */
 function closeConsole(aTab, aCallback = function() { })
 {
   let target = TargetFactory.forTab(aTab || tab);
-  let toolbox = gDevTools.getToolboxForTarget(target);
+  let toolbox = gDevTools.getToolbox(target);
   if (toolbox) {
-    let panel = gDevTools.getPanelForTarget("webconsole", target);
+    let panel = toolbox.getPanel("webconsole");
     if (panel) {
       let hudId = panel.hud.hudId;
-      panel.once("destroyed", function() {
+      toolbox.destroy().then(function() {
         executeSoon(aCallback.bind(null, hudId));
-      });
+      }).then(null, console.error);
     }
     else {
-      toolbox.once("destroyed", aCallback.bind(null, null));
+      toolbox.destroy().then(aCallback.bind(null));
     }
-
-    toolbox.destroy();
   }
   else {
     aCallback();
   }
 }
 
 /**
  * Polls a given function waiting for opening context menu.
@@ -316,18 +304,12 @@ function waitForSuccess(aOptions)
   }
 
   wait(aOptions.validatorFn, aOptions.successFn, aOptions.failureFn);
 }
 
 function openInspector(aCallback, aTab = gBrowser.selectedTab)
 {
   let target = TargetFactory.forTab(aTab);
-  let inspector = gDevTools.getPanelForTarget("inspector", target);
-  if (inspector && inspector.isReady) {
-    aCallback(inspector);
-  } else {
-    let toolbox = gDevTools.openToolboxForTab(target, "inspector");
-    toolbox.once("inspector-ready", function _onSelect(aEvent, aPanel) {
-      aCallback(aPanel);
-    });
-  }
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    aCallback(toolbox.getCurrentPanel());
+  });
 }
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -298,16 +298,20 @@ tiltResetManual=Resets any transformatio
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 tiltCloseDesc=Close the visualization if open
 
 # LOCALIZATION NOTE (tiltCloseManual) A fuller description of the 'tilt close'
 # command, displayed when the user asks for help on what it does.
 tiltCloseManual=Close the visualization and switch back to the Inspector's default highlighter
 
+# LOCALIZATION NOTE (debuggerStopped) Used in the output of several commands
+# to explain that the debugger must be opened first.
+debuggerStopped=The debugger must be opened before setting breakpoints
+
 # LOCALIZATION NOTE (breakDesc) A very short string used to describe the
 # function of the break command.
 breakDesc=Manage breakpoints
 
 # LOCALIZATION NOTE (breakManual) A longer description describing the
 # set of commands that control breakpoints.
 breakManual=Commands to list, add and remove breakpoints
 
@@ -339,20 +343,16 @@ breakaddFailed=Could not set breakpoint:
 # LOCALIZATION NOTE (breakaddDesc) A very short string used to describe the
 # function of the 'break add' command.
 breakaddDesc=Add a breakpoint
 
 # LOCALIZATION NOTE (breakaddManual) A longer description describing the
 # set of commands that are responsible for adding breakpoints.
 breakaddManual=Breakpoint types supported: line
 
-# LOCALIZATION NOTE (breakaddDebuggerStopped) Used in the output of the
-# 'break add' command to explain that the debugger must be opened first.
-breakaddDebuggerStopped=The debugger must be opened before setting breakpoints
-
 # LOCALIZATION NOTE (breakaddlineDesc) A very short string used to describe the
 # function of the 'break add line' command.
 breakaddlineDesc=Add a line breakpoint
 
 # LOCALIZATION NOTE (breakaddlineFileDesc) A very short string used to describe
 # the function of the file parameter in the 'break add line' command.
 breakaddlineFileDesc=JS file URI
 
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -157,30 +157,44 @@ function log(aThing) {
 
   if (aThing === undefined) {
     return "undefined\n";
   }
 
   if (typeof aThing == "object") {
     let reply = "";
     let type = getCtorName(aThing);
-    if (type == "Error") {
+    if (type == "Map") {
+      reply += "Map\n";
+      for (let [key, value] of aThing) {
+        reply += logProperty(key, value);
+      }
+    }
+    else if (type == "Set") {
+      let i = 0;
+      reply += "Set\n";
+      for (let value of aThing) {
+        reply += logProperty('' + i, value);
+        i++;
+      }
+    }
+    else if (type == "Error") {
       reply += "  " + aThing.message + "\n";
       reply += logProperty("stack", aThing.stack);
     }
     else if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
       reply += "  " + debugElement(aThing) + "\n";
     }
     else {
       let keys = Object.getOwnPropertyNames(aThing);
       if (keys.length > 0) {
         reply += type + "\n";
         keys.forEach(function(aProp) {
           reply += logProperty(aProp, aThing[aProp]);
-        }, this);
+        });
       }
       else {
         reply += type + "\n";
         let root = aThing;
         let logged = [];
         while (root != null) {
           let properties = Object.keys(root);
           properties.sort();
@@ -245,17 +259,17 @@ function parseStack(aStack) {
     }
     let at = line.lastIndexOf("@");
     let posn = line.substring(at + 1);
     trace.push({
       file: posn.split(":")[0],
       line: posn.split(":")[1],
       call: line.substring(0, at)
     });
-  }, this);
+  });
   return trace;
 }
 
 /**
  * parseStack() takes output from an exception from which it creates the an
  * array of stack frame objects, this has the same output but using data from
  * Components.stack
  *
@@ -310,34 +324,34 @@ function formatTrace(aTrace) {
  * @see createMultiLineDumper()
  */
 function createDumper(aLevel) {
   return function() {
     let args = Array.prototype.slice.call(arguments, 0);
     let data = args.map(function(arg) {
       return stringify(arg);
     });
-    dump(aLevel + ": " + data.join(", ") + "\n");
+    dump("console." + aLevel + ": " + data.join(", ") + "\n");
   };
 }
 
 /**
  * Create a function which will output more detailed level of output when
  * used as a logging function
  *
  * @param {string} aLevel
  *        A prefix to all output generated from this function detailing the
  *        level at which output occurred
  * @return {function}
  *        A logging function
  * @see createDumper()
  */
 function createMultiLineDumper(aLevel) {
   return function() {
-    dump(aLevel + "\n");
+    dump("console." + aLevel + ": \n");
     let args = Array.prototype.slice.call(arguments, 0);
     args.forEach(function(arg) {
       dump(log(arg));
     });
   };
 }
 
 /**
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -1571,20 +1571,21 @@ this.JSTermHelpers = function JSTermHelp
    * @return nsIDOMElement|null
    *         The DOM element currently selected in the highlighter.
    */
   Object.defineProperty(aOwner.sandbox, "$0", {
     get: function() {
       try {
         let window = aOwner.chromeWindow();
         let target = TargetFactory.forTab(window.gBrowser.selectedTab);
-        let panel = gDevTools.getPanelForTarget("inspector", target);
-        if (panel) {
-          return panel.selection.node;
-        }
+        let toolbox = gDevTools.getToolbox(target);
+
+        return toolbox == null ?
+            undefined :
+            toolbox.getPanel("inspector").selection.node;
       }
       catch (ex) {
         aOwner.window.console.error(ex.message);
       }
     },
     enumerable: true,
     configurable: false
   });