Merge mozilla-central to mozilla-inbound
authorEd Morley <emorley@mozilla.com>
Fri, 14 Dec 2012 16:52:20 +0000
changeset 125174 9d1bb565ab3ad8df0593d1fbbe0d101116da219b
parent 125173 71759cb2a7d932a1faf2e61f04f879b970449f5b (current diff)
parent 125153 50d8f411d3059f95c4242febc114852d3c836222 (diff)
child 125175 e4da3fe4a9da44011eeb29895457f28de4571e80
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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
Merge mozilla-central to mozilla-inbound
--- a/b2g/config/unagi/releng-unagi.tt
+++ b/b2g/config/unagi/releng-unagi.tt
@@ -1,14 +1,14 @@
 [
 {
 "size": 832341968,
 "digest": "e2d099677f5f930db0b2c03eaf3812a53f02be0773bdb3bad89b1c387041e920c17b90f041bf70d9c2ab715dd4556503534483d4ebc28fb035f41cd5ebba6a52",
 "algorithm": "sha512",
 "filename": "gonk.tar.xz"
 },
 {
-"size": 8859648,
-"digest": "05d4a99e0f36cd91d1b10a2b558979ea776e9a7e03b8a921af3b0bfc62e2d96cf4faa20586c39885b6f8b25089fe07726794620a3b18c4826a2f71e29d90a8ef",
+"size": 8622080,
+"digest": "7a2bbf0c76f7b7d5e4b89f758f69b5d8bcf08ec579374877de8939ad69883ab8cd842f04fdaa03a4ef9cdf8170f242e0381dd437e969d5212ead6cdd6f79ab50",
 "algorithm": "sha512",
 "filename": "boot.img"
 }
 ]
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -42,12 +42,13 @@ MOZ_XULRUNNER=
 fi
 
 MOZ_APP_ID={3c2e2abc-06d4-11e1-ac3b-374f68613e61}
 MOZ_EXTENSION_MANAGER=1
 
 MOZ_SYS_MSG=1
 MOZ_TIME_MANAGER=1
 
+MOZ_B2G_CERTDATA=1
 MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -84,25 +84,25 @@
     <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="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
 
     <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
@@ -1457,17 +1457,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();
@@ -1501,17 +1501,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 (!this._loadHandled)
       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);
@@ -7292,17 +7292,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.shouldDisplay = false;
--- a/browser/devtools/commandline/CmdAddon.jsm
+++ b/browser/devtools/commandline/CmdAddon.jsm
@@ -130,21 +130,23 @@ gcli.addCommand({
         case "all":
           header = gcli.lookup("addonListAllHeading");
           break;
         default:
           header = gcli.lookup("addonListUnknownHeading");
       }
 
       // Map and sort the add-ons, and create an HTML list.
-      this.resolve(header +
-        "<ol>" +
-        enabledAddons.sort(compareAddonNames).map(representEnabledAddon).join("") +
-        disabledAddons.sort(compareAddonNames).map(representDisabledAddon).join("") +
-        "</ol>");
+      let message = header +
+                    "<ol>" +
+                    enabledAddons.sort(compareAddonNames).map(representEnabledAddon).join("") +
+                    disabledAddons.sort(compareAddonNames).map(representDisabledAddon).join("") +
+                    "</ol>";
+
+      this.resolve(context.createView({ html: message }));
     }
 
     // Create the promise that will be resolved when the add-on listing has
     // been finished.
     let promise = context.createPromise();
     let types = aArgs.type == "all" ? null : [aArgs.type];
     AddonManager.getAddonsByTypes(types, list.bind(promise, aArgs.type));
     return promise;
@@ -226,26 +228,25 @@ AddonManager.getAllAddons(function addon
             addon = candidate;
             return true;
           } else {
             return false;
           }
         });
 
         let name = representAddon(addon);
+        let message = "";
 
         if (!addon.userDisabled) {
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonAlreadyEnabled", [name]) + "]]>");
+          message = gcli.lookupFormat("addonAlreadyEnabled", [name]);
         } else {
           addon.userDisabled = false;
-          // nl-nl: {$1} is ingeschakeld.
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonEnabled", [name]) + "]]>");
+          message = gcli.lookupFormat("addonEnabled", [name]);
         }
+        this.resolve(message);
       }
 
       let promise = context.createPromise();
       // List the installed add-ons, enable one when done listing.
       AddonManager.getAllAddons(enable.bind(promise, aArgs.name));
       return promise;
     }
   });
@@ -269,26 +270,25 @@ AddonManager.getAllAddons(function addon
             addon = candidate;
             return true;
           } else {
             return false;
           }
         });
 
         let name = representAddon(addon);
+        let message = "";
 
         if (addon.userDisabled) {
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonAlreadyDisabled", [name]) + "]]>");
+          message = gcli.lookupFormat("addonAlreadyDisabled", [name]);
         } else {
           addon.userDisabled = true;
-          // nl-nl: {$1} is uitgeschakeld.
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonDisabled", [name]) + "]]>");
+          message = gcli.lookupFormat("addonDisabled", [name]);
         }
+        this.resolve(message);
       }
 
       let promise = context.createPromise();
       // List the installed add-ons, disable one when done listing.
       AddonManager.getAllAddons(disable.bind(promise, aArgs.name));
       return promise;
     }
   });
--- 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/CmdCmd.jsm
+++ b/browser/devtools/commandline/CmdCmd.jsm
@@ -119,11 +119,11 @@ gcli.addCommand({
  * 'cmd refresh' command
  */
 gcli.addCommand({
   name: "cmd refresh",
   description: gcli.lookup("cmdRefreshDesc"),
   get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); },
   exec: function Command_cmdRefresh(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
-    GcliCommands.refreshAutoCommands(chromeWindow);
+    CmdCommands.refreshAutoCommands(chromeWindow);
   }
 });
--- 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/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -632,16 +632,17 @@ StackFrames.prototype = {
 
     // If watch expressions evaluation results are available, create a scope
     // to contain all the values.
     if (this.syncedWatchExpressions && watchExpressionsEvaluation) {
       let label = L10N.getStr("watchExpressionsScopeLabel");
       let arrow = L10N.getStr("watchExpressionsSeparatorLabel");
       let scope = DebuggerView.Variables.addScope(label);
       scope.separator = arrow;
+      scope.showDescriptorTooltip = false;
       scope.allowNameInput = true;
       scope.allowDeletion = true;
       scope.contextMenu = "debuggerWatchExpressionsContextMenu";
       scope.switch = DebuggerView.WatchExpressions.switchExpression;
       scope.delete = DebuggerView.WatchExpressions.deleteExpression;
 
       // The evaluation hasn't thrown, so display the returned results and
       // always expand the watch expressions scope by default.
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1450,18 +1450,22 @@ create({ constructor: GlobalSearchView, 
       this._onFetchSourcesFinished();
     }
   },
 
   /**
    * Called when all the sources have been fetched.
    */
   _onFetchSourcesFinished: function DVGS__onFetchSourcesFinished() {
+    if (!this._sourcesCount) {
+      return;
+    }
     // All sources are fetched and stored in the cache, we can start searching.
     this._performGlobalSearch();
+    this._sourcesCount = 0;
   },
 
   /**
    * Finds string matches in all the  sources stored in the cache, and groups
    * them by location and line number.
    */
   _performGlobalSearch: function DVGS__performGlobalSearch() {
     // Get the currently searched token from the filtering input.
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -708,17 +708,17 @@ FilterView.prototype = {
    */
   get searchedToken() this.searchboxInfo[2],
 
   /**
    * Clears the text from the searchbox and resets any changed view.
    */
   clearSearch: function DVF_clearSearch() {
     this._searchbox.value = "";
-    this._onSearch();
+    this._searchboxPanel.hidePopup();
   },
 
   /**
    * Performs a file search if necessary.
    *
    * @param string aFile
    *        The source location to search for.
    */
@@ -749,62 +749,74 @@ FilterView.prototype = {
         // Search is not case sensitive, and is tied to the label not the value.
         if (lowerCaseLabel.match(lowerCaseFile)) {
           element.hidden = false;
 
           // Automatically select the first match.
           if (!found) {
             found = true;
             view.selectedItem = item;
+            view.refresh();
           }
         }
         // Item not matched, hide the corresponding node.
         else {
           element.hidden = true;
         }
       }
       // If no matches were found, display the appropriate info.
       if (!found) {
         view.setUnavailable();
       }
     }
+    // Synchronize with the view's filtered sources container.
+    DebuggerView.FilteredSources.syncFileSearch();
+
     this._prevSearchedFile = aFile;
   },
 
   /**
    * Performs a line search if necessary.
    * (Jump to lines in the currently visible source).
    *
    * @param number aLine
    *        The source line number to jump to.
    */
   _performLineSearch: function DVF__performLineSearch(aLine) {
     // Don't search for lines if the input hasn't changed.
-    if (this._prevSearchedLine != aLine && aLine > 0) {
+    if (this._prevSearchedLine != aLine && aLine) {
       DebuggerView.editor.setCaretPosition(aLine - 1);
     }
+    // Can't search for lines and tokens at the same time.
+    if (this._prevSearchedToken && !aLine) {
+      this._target.refresh();
+    }
     this._prevSearchedLine = aLine;
   },
 
   /**
    * Performs a token search if necessary.
    * (Search for tokens in the currently visible source).
    *
    * @param string aToken
    *        The source token to find.
    */
   _performTokenSearch: function DVF__performTokenSearch(aToken) {
     // Don't search for tokens if the input hasn't changed.
-    if (this._prevSearchedToken != aToken && aToken.length > 0) {
+    if (this._prevSearchedToken != aToken && aToken) {
       let editor = DebuggerView.editor;
       let offset = editor.find(aToken, { ignoreCase: true });
       if (offset > -1) {
         editor.setSelection(offset, offset + aToken.length)
       }
     }
+    // Can't search for tokens and lines at the same time.
+    if (this._prevSearchedLine && !aToken) {
+      this._target.refresh();
+    }
     this._prevSearchedToken = aToken;
   },
 
   /**
    * The click listener for the search container.
    */
   _onClick: function DVF__onClick() {
     this._searchboxPanel.openPopup(this._searchbox);
@@ -816,23 +828,25 @@ FilterView.prototype = {
   _onSearch: function DVF__onScriptsSearch() {
     this._searchboxPanel.hidePopup();
     let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
 
     // If this is a global search, schedule it for when the user stops typing,
     // or hide the corresponding pane otherwise.
     if (isGlobal) {
       DebuggerView.GlobalSearch.scheduleSearch(token);
+      this._prevSearchedToken = token;
       return;
     }
 
     // If this is a variable search, defer the action to the corresponding
     // variables view instance.
     if (isVariable) {
       DebuggerView.Variables.scheduleSearch(token);
+      this._prevSearchedToken = token;
       return;
     }
 
     DebuggerView.GlobalSearch.clearView();
     this._performFileSearch(file);
     this._performLineSearch(line);
     this._performTokenSearch(token);
   },
@@ -840,18 +854,25 @@ FilterView.prototype = {
   /**
    * The key press listener for the search container.
    */
   _onKeyPress: function DVF__onScriptsKeyPress(e) {
     // This attribute is not implemented in Gecko at this time, see bug 680830.
     e.char = String.fromCharCode(e.charCode);
 
     let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
-    let isDifferentToken, isReturnKey, action = -1;
+    let isFileSearch, isLineSearch, isDifferentToken, isReturnKey;
+    let action = -1;
 
+    if (file && !line && !token) {
+      isFileSearch = true;
+    }
+    if (line && !token) {
+      isLineSearch = true;
+    }
     if (this._prevSearchedToken != token) {
       isDifferentToken = true;
     }
 
     // Meta+G and Ctrl+N focus next matches.
     if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
       action = 0;
     }
@@ -876,46 +897,61 @@ FilterView.prototype = {
         action = 2;
         break;
     }
 
     if (action == 2) {
       DebuggerView.editor.focus();
       return;
     }
-    if (action == -1 || (token.length == 0 && line == 0)) {
+    if (action == -1 || (!file && !line && !token)) {
+      DebuggerView.FilteredSources.hidden = true;
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
+    // Select the next or previous file search entry.
+    if (isFileSearch) {
+      if (isReturnKey) {
+        DebuggerView.FilteredSources.hidden = true;
+        DebuggerView.editor.focus();
+        this.clearSearch();
+      } else {
+        DebuggerView.FilteredSources[["focusNext", "focusPrev"][action]]();
+      }
+      this._prevSearchedFile = file;
+      return;
+    }
+
     // Perform a global search based on the specified operator.
     if (isGlobal) {
       if (isReturnKey && (isDifferentToken || DebuggerView.GlobalSearch.hidden)) {
         DebuggerView.GlobalSearch.performSearch(token);
       } else {
         DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
       }
       this._prevSearchedToken = token;
       return;
     }
 
     // Perform a variable search based on the specified operator.
     if (isVariable) {
       if (isReturnKey && isDifferentToken) {
         DebuggerView.Variables.performSearch(token);
+      } else {
         DebuggerView.Variables.expandFirstSearchResults();
       }
       this._prevSearchedToken = token;
       return;
     }
 
     // Increment or decrement the specified line.
-    if (!isReturnKey && token.length == 0 && line > 0) {
+    if (isLineSearch && !isReturnKey) {
       line += action == 0 ? 1 : -1;
       let lineCount = DebuggerView.editor.getLineCount();
       let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
 
       DebuggerView.editor.setCaretPosition(lineTarget - 1);
       this._searchbox.value = file + SEARCH_LINE_FLAG + lineTarget;
       this._prevSearchedLine = lineTarget;
       return;
@@ -1006,15 +1042,178 @@ FilterView.prototype = {
   _variableSearchKey: "",
   _target: null,
   _prevSearchedFile: "",
   _prevSearchedLine: 0,
   _prevSearchedToken: ""
 };
 
 /**
+ * Functions handling the filtered sources UI.
+ */
+function FilteredSourcesView() {
+  MenuContainer.call(this);
+  this._onClick = this._onClick.bind(this);
+}
+
+create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVFS_initialize() {
+    dumpn("Initializing the FilteredSourcesView");
+
+    let panel = this._panel = document.createElement("panel");
+    panel.id = "filtered-sources-panel";
+    panel.setAttribute("noautofocus", "true");
+    panel.setAttribute("position", FILTERED_SOURCES_POPUP_POSITION);
+    document.documentElement.appendChild(panel);
+
+    this._searchbox = document.getElementById("searchbox");
+    this._container = new StackList(panel);
+
+    this._container.itemFactory = this._createItemView;
+    this._container.itemType = "vbox";
+    this._container.addEventListener("click", this._onClick, false);
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVFS_destroy() {
+    dumpn("Destroying the FilteredSourcesView");
+    document.documentElement.removeChild(this._panel);
+    this._container.removeEventListener("click", this._onClick, false);
+  },
+
+  /**
+   * Sets the files container hidden or visible. It's hidden by default.
+   * @param boolean aFlag
+   */
+  set hidden(aFlag) {
+    if (aFlag) {
+      this._container._parent.hidePopup();
+    } else {
+      this._container._parent.openPopup(this._searchbox);
+    }
+  },
+
+  /**
+   * Updates the list of sources displayed in this container.
+   */
+  syncFileSearch: function DVFS_syncFileSearch() {
+    this.empty();
+
+    // If there's no currently searched file, or there are no matches found,
+    // hide the popup.
+    if (!DebuggerView.Filtering.searchedFile ||
+        !DebuggerView.Sources.visibleItems.length) {
+      this.hidden = true;
+      return;
+    }
+
+    // Get the currently visible items in the sources container.
+    let visibleItems = DebuggerView.Sources.visibleItems;
+    let displayedItems = visibleItems.slice(0, FILTERED_SOURCES_MAX_RESULTS);
+
+    for (let item of displayedItems) {
+      // Append a location item item to this container.
+      let trimmedLabel = SourceUtils.trimUrlLength(item.label);
+      let trimmedValue = SourceUtils.trimUrlLength(item.value);
+
+      let locationItem = this.push(trimmedLabel, trimmedValue, {
+        forced: true,
+        relaxed: true,
+        unsorted: true,
+        attachment: {
+          fullLabel: item.label,
+          fullValue: item.value
+        }
+      });
+
+      let element = locationItem.target;
+      element.className = "dbg-source-item list-item";
+      element.labelNode.className = "dbg-source-item-name plain";
+      element.valueNode.className = "dbg-source-item-details plain";
+    }
+
+    this._updateSelection(this.getItemAtIndex(0));
+    this.hidden = false;
+  },
+
+  /**
+   * Focuses the next found match in this container.
+   */
+  focusNext: function DVFS_focusNext() {
+    let nextIndex = this.selectedIndex + 1;
+    if (nextIndex >= this.totalItems) {
+      nextIndex = 0;
+    }
+    this._updateSelection(this.getItemAtIndex(nextIndex));
+  },
+
+  /**
+   * Focuses the previously found match in this container.
+   */
+  focusPrev: function DVFS_focusPrev() {
+    let prevIndex = this.selectedIndex - 1;
+    if (prevIndex < 0) {
+      prevIndex = this.totalItems - 1;
+    }
+    this._updateSelection(this.getItemAtIndex(prevIndex));
+  },
+
+  /**
+   * The click listener for this container.
+   */
+  _onClick: function DVFS__onClick(e) {
+    let locationItem = this.getItemForElement(e.target);
+    if (locationItem) {
+      this._updateSelection(locationItem);
+    }
+  },
+
+  /**
+   * Updates the selected item in this container and other views.
+   *
+   * @param MenuItem aItem
+   *        The item associated with the element to select.
+   */
+  _updateSelection: function DVFS__updateSelection(aItem) {
+    this.selectedItem = aItem;
+    DebuggerView.Filtering._target.selectedValue = aItem.attachment.fullValue;
+  },
+
+  /**
+   * Customization function for creating an item's UI.
+   *
+   * @param string aLabel
+   *        The item's label.
+   * @param string aValue
+   *        The item's value.
+   */
+  _createItemView: function DVFS__createItemView(aElementNode, aLabel, aValue) {
+    let labelNode = document.createElement("label");
+    let valueNode = document.createElement("label");
+
+    labelNode.setAttribute("value", aLabel);
+    valueNode.setAttribute("value", aValue);
+
+    aElementNode.appendChild(labelNode);
+    aElementNode.appendChild(valueNode);
+
+    aElementNode.labelNode = labelNode;
+    aElementNode.valueNode = valueNode;
+  },
+
+  _panel: null,
+  _searchbox: null
+});
+
+/**
  * Preliminary setup for the DebuggerView object.
  */
 DebuggerView.Toolbar = new ToolbarView();
 DebuggerView.Options = new OptionsView();
 DebuggerView.ChromeGlobals = new ChromeGlobalsView();
 DebuggerView.Sources = new SourcesView();
 DebuggerView.Filtering = new FilterView();
+DebuggerView.FilteredSources = new FilteredSourcesView();
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -6,18 +6,20 @@
 "use strict";
 
 const SOURCE_URL_MAX_LENGTH = 64; // chars
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const PANES_APPEARANCE_DELAY = 50; // ms
 const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
 const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "after_start";
 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px
+const FILTERED_SOURCES_POPUP_POSITION = "before_start";
+const FILTERED_SOURCES_MAX_RESULTS = 10;
+const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
 const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
-const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
 const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 
 /**
  * Object defining the debugger view components.
@@ -35,16 +37,17 @@ let DebuggerView = {
     this._initializeWindow();
     this._initializePanes();
 
     this.Toolbar.initialize();
     this.Options.initialize();
     this.ChromeGlobals.initialize();
     this.Sources.initialize();
     this.Filtering.initialize();
+    this.FilteredSources.initialize();
     this.StackFrames.initialize();
     this.Breakpoints.initialize();
     this.WatchExpressions.initialize();
     this.GlobalSearch.initialize();
 
     this.Variables = new VariablesView(document.getElementById("variables"));
     this.Variables.searchPlaceholder = L10N.getStr("emptyVariablesFilterText");
     this.Variables.emptyText = L10N.getStr("emptyVariablesText");
@@ -65,16 +68,17 @@ let DebuggerView = {
   destroy: function DV_destroy(aCallback) {
     dumpn("Destroying the DebuggerView");
 
     this.Toolbar.destroy();
     this.Options.destroy();
     this.ChromeGlobals.destroy();
     this.Sources.destroy();
     this.Filtering.destroy();
+    this.FilteredSources.destroy();
     this.StackFrames.destroy();
     this.Breakpoints.destroy();
     this.WatchExpressions.destroy();
     this.GlobalSearch.destroy();
 
     this._destroyWindow();
     this._destroyPanes();
     this._destroyEditor();
@@ -465,16 +469,17 @@ let DebuggerView = {
     this.StackFrames.empty();
     this.Breakpoints.empty();
     this.Breakpoints.unhighlightBreakpoint();
     this.Variables.empty();
     SourceUtils.clearLabelsCache();
 
     if (this.editor) {
       this.editor.setText("");
+      this.editor.focus();
       this._editorSource = null;
     }
   },
 
   Toolbar: null,
   Options: null,
   ChromeGlobals: null,
   Sources: null,
@@ -931,33 +936,35 @@ MenuContainer.prototype = {
     let values = [];
     for (let [value] of this._itemsByValue) {
       values.push(value);
     }
     return values;
   },
 
   /**
-   * Gets the total items in this container.
+   * Gets the total number of items in this container.
    * @return number
    */
   get totalItems() {
     return this._itemsByElement.size;
   },
 
   /**
-   * Gets the total visible (non-hidden) items in this container.
-   * @return number
+   * Returns a list of all the visible (non-hidden) items in this container.
+   * @return array
    */
   get visibleItems() {
-    let count = 0;
-    for (let [element] of this._itemsByElement) {
-      count += element.hidden ? 0 : 1;
+    let items = [];
+    for (let [element, item] of this._itemsByElement) {
+      if (!element.hidden) {
+        items.push(item);
+      }
     }
-    return count;
+    return items;
   },
 
   /**
    * Specifies the required conditions for an item to be considered unique.
    * Possible values:
    *   - 1: label AND value are different from all other items
    *   - 2: label OR value are different from all other items
    *   - 3: only label is required to be different
@@ -1129,18 +1136,16 @@ MenuContainer.prototype = {
  * displaying views like the StackFrames, Breakpoints etc.
  *
  * Custom methods introduced by this view, not necessary for a MenuContainer:
  * set emptyText(aValue:string)
  * set permaText(aValue:string)
  * set itemType(aType:string)
  * set itemFactory(aCallback:function)
  *
- * TODO: Use this in #796135 - "Provide some obvious UI for scripts filtering".
- *
  * @param nsIDOMNode aAssociatedNode
  *        The element associated with the displayed container.
  */
 function StackList(aAssociatedNode) {
   this._parent = aAssociatedNode;
 
   // Create an internal list container.
   this._list = document.createElement("vbox");
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -214,19 +214,21 @@
         <toolbarbutton id="step-in"
                        class="devtools-toolbarbutton"
                        tabindex="0"/>
         <toolbarbutton id="step-out"
                        class="devtools-toolbarbutton"
                        tabindex="0"/>
       </hbox>
       <menulist id="chrome-globals"
-                class="devtools-menulist" hidden="true"/>
+                class="devtools-menulist"
+                sizetopopup="none" hidden="true"/>
       <menulist id="sources"
-                class="devtools-menulist"/>
+                class="devtools-menulist"
+                sizetopopup="none"/>
       <textbox id="searchbox"
                class="devtools-searchinput" type="search"/>
       <spacer flex="1"/>
       <toolbarbutton id="toggle-panes"
                      class="devtools-toolbarbutton"
                      tooltiptext="&debuggerUI.panesButton.tooltip;"
                      tabindex="0"/>
       <toolbarbutton id="debugger-options"
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -61,16 +61,17 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_scripts-searching-01.js \
 	browser_dbg_scripts-searching-02.js \
 	browser_dbg_scripts-searching-03.js \
 	browser_dbg_scripts-searching-04.js \
 	browser_dbg_scripts-searching-05.js \
 	browser_dbg_scripts-searching-06.js \
 	browser_dbg_scripts-searching-07.js \
 	browser_dbg_scripts-searching-08.js \
+	browser_dbg_scripts-searching-files_ui.js \
 	browser_dbg_scripts-searching-popup.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_update-editor-mode.js \
 	$(filter temporarily-disabled-due-to-oranges--bug-726609, browser_dbg_select-line.js) \
 	browser_dbg_clean-exit.js \
 	browser_dbg_bug723069_editor-breakpoints.js \
 	browser_dbg_bug723071_editor-breakpoints-pane.js \
 	browser_dbg_bug740825_conditional-breakpoints-01.js \
--- a/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
@@ -138,19 +138,19 @@ function test()
     gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
       gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
 
       is(gBreakpointsPane.selectedItem, null,
         "There should be no selected breakpoint in the breakpoints pane.")
       is(gBreakpointsPane._popupShown, false,
         "The breakpoint conditional expression popup should not be shown.");
 
-      is(gDebugger.DebuggerView.StackFrames.visibleItems, 0,
+      is(gDebugger.DebuggerView.StackFrames.visibleItems.length, 0,
         "There should be no visible stackframes.");
-      is(gDebugger.DebuggerView.Breakpoints.visibleItems, 13,
+      is(gDebugger.DebuggerView.Breakpoints.visibleItems.length, 13,
         "There should be thirteen visible breakpoints.");
 
       testReload();
     }, true);
 
     gDebugger.DebuggerController.activeThread.resume();
   }
 
@@ -336,17 +336,17 @@ function test()
     let count = 0;
     let intervalID = window.setInterval(function() {
       info("count: " + count + " ");
       if (++count > 50) {
         ok(false, "Timed out while polling for the breakpoints.");
         window.clearInterval(intervalID);
         return closeDebuggerAndFinish();
       }
-      if (gBreakpointsPane.visibleItems != total) {
+      if (gBreakpointsPane.visibleItems.length != total) {
         return;
       }
       // We got all the breakpoints, it's safe to callback.
       window.clearInterval(intervalID);
       callback();
     }, 100);
   }
 
--- 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/browser_dbg_propertyview-filter-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
@@ -30,79 +30,90 @@ function test()
     prepareVariables(testVariablesFiltering);
   });
 }
 
 function testSearchbox()
 {
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There should not initially be a searchbox available in the variables view.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should not be found.");
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should not be found.");
 
   gDebugger.DebuggerView.Variables.enableSearch();
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should be a searchbox available after enabling.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should be hidden at this point.");
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should be found.");
 
 
   gDebugger.DebuggerView.Variables.disableSearch();
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There shouldn't be a searchbox available after disabling.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should not be found.");
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should not be found.");
 
   gDebugger.DebuggerView.Variables.enableSearch();
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should be a searchbox available after enabling.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should be hidden at this point.");
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should be found.");
 
   let placeholder = "freshly squeezed mango juice";
 
   gDebugger.DebuggerView.Variables.searchPlaceholder = placeholder;
   ok(gDebugger.DebuggerView.Variables._searchboxNode.getAttribute("placeholder"),
     placeholder, "There correct placeholder should be applied to the searchbox.");
 
 
   gDebugger.DebuggerView.Variables.disableSearch();
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There shouldn't be a searchbox available after disabling again.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should not be found.");
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should not be found.");
 
   gDebugger.DebuggerView.Variables.enableSearch();
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should be a searchbox available after enabling again.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should be hidden at this point.");
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should be found.");
 
   ok(gDebugger.DebuggerView.Variables._searchboxNode.getAttribute("placeholder"),
     placeholder, "There correct placeholder should be applied to the searchbox again.");
 
 
   gDebugger.DebuggerView.Variables.searchEnabled = false;
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There shouldn't be a searchbox available after disabling again.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should not be found.");
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should not be found.");
 
   gDebugger.DebuggerView.Variables.searchEnabled = true;
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should be a searchbox available after enabling again.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should be hidden at this point.");
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should be found.");
 
   ok(gDebugger.DebuggerView.Variables._searchboxNode.getAttribute("placeholder"),
     placeholder, "There correct placeholder should be applied to the searchbox again.");
 }
 
 function testVariablesFiltering()
 {
+  ok(!gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should not be hidden at this point.");
+
   function test1()
   {
     write("location");
 
     is(innerScopeItem.expanded, true,
       "The innerScope expanded getter should return true");
     is(mathScopeItem.expanded, true,
       "The mathScope expanded getter should return true");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
@@ -30,28 +30,33 @@ function test()
     prepareVariables(testVariablesFiltering);
   });
 }
 
 function testSearchbox()
 {
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There should not initially be a searchbox available in the variables view.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should not be found.");
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should not be found.");
 
   gDebugger.DebuggerView.Variables.enableSearch();
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should be a searchbox available after enabling.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
-    "There searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
+    "The searchbox element should be found.");
+  ok(gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should be hidden at this point.");
 }
 
 function testVariablesFiltering()
 {
+  ok(!gDebugger.DebuggerView.Variables._searchboxContainer.hidden,
+    "The searchbox container should not be hidden at this point.");
+
   function test1()
   {
     write("htmldocument");
 
     is(innerScopeItem.expanded, true,
       "The innerScope expanded getter should return true");
     is(mathScopeItem.expanded, true,
       "The mathScope expanded getter should return true");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-03.js
@@ -31,47 +31,47 @@ function test()
     testPref();
   });
 }
 
 function testSearchbox()
 {
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
     "There should not initially be a searchbox available in the variables view.");
-  ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+  ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
     "There searchbox element should not be found.");
 }
 
 function testPref()
 {
   is(gDebugger.Prefs.variablesSearchboxVisible, false,
     "The debugger searchbox should be preffed as hidden.");
   isnot(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
     "The options menu item should not be checked.");
 
   gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.setAttribute("checked", "true");
   gDebugger.DebuggerView.Options._toggleShowVariablesFilterBox();
 
   executeSoon(function() {
     ok(gDebugger.DebuggerView.Variables._searchboxNode,
       "There should be a searchbox available in the variables view.");
-    ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+    ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
       "There searchbox element should be found.");
     is(gDebugger.Prefs.variablesSearchboxVisible, true,
       "The debugger searchbox should now be preffed as visible.");
     is(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
       "The options menu item should now be checked.");
 
     gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.setAttribute("checked", "false");
     gDebugger.DebuggerView.Options._toggleShowVariablesFilterBox();
 
     executeSoon(function() {
       ok(!gDebugger.DebuggerView.Variables._searchboxNode,
         "There should not be a searchbox available in the variables view.");
-      ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+      ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
         "There searchbox element should not be found.");
       is(gDebugger.Prefs.variablesSearchboxVisible, false,
         "The debugger searchbox should now be preffed as hidden.");
       isnot(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
         "The options menu item should now be unchecked.");
 
       executeSoon(function() {
         Services.prefs.setBoolPref(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-04.js
@@ -31,47 +31,47 @@ function test()
     testPref();
   });
 }
 
 function testSearchbox()
 {
   ok(gDebugger.DebuggerView.Variables._searchboxNode,
     "There should initially be a searchbox available in the variables view.");
-  ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+  ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
     "There searchbox element should be found.");
 }
 
 function testPref()
 {
   is(gDebugger.Prefs.variablesSearchboxVisible, true,
     "The debugger searchbox should be preffed as visible.");
   is(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
     "The options menu item should be checked.");
 
   gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.setAttribute("checked", "false");
   gDebugger.DebuggerView.Options._toggleShowVariablesFilterBox();
 
   executeSoon(function() {
     ok(!gDebugger.DebuggerView.Variables._searchboxNode,
       "There should not be a searchbox available in the variables view.");
-    ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+    ok(!gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
       "There searchbox element should not be found.");
     is(gDebugger.Prefs.variablesSearchboxVisible, false,
       "The debugger searchbox should now be preffed as hidden.");
     isnot(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
       "The options menu item should now be unchecked.");
 
     gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.setAttribute("checked", "true");
     gDebugger.DebuggerView.Options._toggleShowVariablesFilterBox();
 
     executeSoon(function() {
       ok(gDebugger.DebuggerView.Variables._searchboxNode,
         "There should be a searchbox available in the variables view.");
-      ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
+      ok(gDebugger.DebuggerView.Variables._parent.parentNode.querySelector(".variables-searchinput.devtools-searchinput"),
         "There searchbox element should be found.");
       is(gDebugger.Prefs.variablesSearchboxVisible, true,
         "The debugger searchbox should now be preffed as visible.");
       is(gDebugger.DebuggerView.Options._showVariablesFilterBoxItem.getAttribute("checked"), "true",
         "The options menu item should now be checked.");
 
       executeSoon(function() {
         Services.prefs.setBoolPref(
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
@@ -20,17 +20,16 @@ function test()
   let scriptShown = false;
   let framesAdded = false;
 
   debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.panelWin;
-    gDebugger.SourceResults.prototype.alwaysExpand = false;
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
       runTest();
     });
 
     gDebuggee.simpleCall();
   });
@@ -45,16 +44,17 @@ function test()
   {
     if (scriptShown && framesAdded) {
       Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
     }
   }
 }
 
 function testScriptSearching() {
+  let noMatchingScripts = gDebugger.L10N.getStr("noMatchingScriptsText");
   var token;
 
   gDebugger.DebuggerController.activeThread.resume(function() {
     gEditor = gDebugger.DebuggerView.editor;
     gScripts = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
     gMenulist = gScripts._container;
 
@@ -130,78 +130,133 @@ function testScriptSearching() {
       "The editor didn't jump to the correct token. (5.1)");
 
 
     token = "debugger;";
     write(":bogus#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (6)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     write(":13#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (7)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     write(":#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (8)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     write("::#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (9)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     write(":::#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (10)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
+
+
+    write("#" + token + ":bogus");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2 + token.length,
+      "The editor didn't jump to the correct token. (6)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
+
+    write("#" + token + ":13");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2 + token.length,
+      "The editor didn't jump to the correct token. (7)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
+
+    write("#" + token + ":");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2 + token.length,
+      "The editor didn't jump to the correct token. (8)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
+
+    write("#" + token + "::");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2 + token.length,
+      "The editor didn't jump to the correct token. (9)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
+
+    write("#" + token + ":::");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2 + token.length,
+      "The editor didn't jump to the correct token. (10)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
 
     write(":i am not a number");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't remain at the correct token. (11)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     write("#__i do not exist__");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't remain at the correct token. (12)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
 
     token = "debugger";
     write("#" + token);
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor didn't jump to the correct token. (12.1)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     clear();
     EventUtils.sendKey("RETURN");
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor shouldn't jump to another token. (12.2)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     EventUtils.sendKey("ENTER");
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor shouldn't jump to another token. (12.3)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
 
     write(":1:2:3:a:b:c:::12");
     ok(gEditor.getCaretPosition().line == 11 &&
        gEditor.getCaretPosition().col == 0,
       "The editor didn't jump to the correct line. (13)");
 
     write("#don't#find#me#instead#find#" + token);
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor didn't jump to the correct token. (14)");
 
-
     EventUtils.sendKey("DOWN");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (15)");
 
     EventUtils.sendKey("DOWN");
     ok(gEditor.getCaretPosition().line == 12 &&
        gEditor.getCaretPosition().col == 8 + token.length,
@@ -222,18 +277,20 @@ function testScriptSearching() {
        gEditor.getCaretPosition().col == 4 + token.length,
       "The editor didn't jump to the correct token. (18.1)");
 
 
     clear();
     ok(gEditor.getCaretPosition().line == 19 &&
        gEditor.getCaretPosition().col == 4 + token.length,
       "The editor didn't remain at the correct token. (19)");
-    is(gScripts.visibleItems, 1,
+    is(gScripts.visibleItems.length, 1,
       "Not all the scripts are shown after the search. (20)");
+    isnot(gMenulist.getAttribute("label"), noMatchingScripts,
+      "The menulist should not display a notice that matches are found.");
 
     closeDebuggerAndFinish();
   });
 }
 
 function clear() {
   gSearchBox.focus();
   gSearchBox.value = "";
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
@@ -22,17 +22,16 @@ function test()
   let scriptShown = false;
   let framesAdded = false;
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.panelWin;
-    gDebugger.SourceResults.prototype.alwaysExpand = false;
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
       runTest();
     });
 
     gDebuggee.firstCall();
   });
@@ -74,17 +73,17 @@ function firstSearch() {
     if (url.indexOf("-01.js") != -1) {
       window.removeEventListener(aEvent.type, _onEvent);
 
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 4 &&
            gEditor.getCaretPosition().col == 0,
           "The editor didn't jump to the correct line. (1)");
-        is(gScripts.visibleItems, 1,
+        is(gScripts.visibleItems.length, 1,
           "Not all the correct scripts are shown after the search. (1)");
 
         secondSearch();
       });
     }
   });
   write(".*-01\.js:5");
 }
@@ -102,17 +101,17 @@ function secondSearch() {
 
       executeSoon(function() {
         append("#" + token);
 
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 8 + token.length,
           "The editor didn't jump to the correct line. (2)");
-        is(gScripts.visibleItems, 1,
+        is(gScripts.visibleItems.length, 1,
           "Not all the correct scripts are shown after the search. (2)");
 
         waitForFirstScript();
       });
     }
   });
   gScripts.selectedIndex = 1;
 }
@@ -145,17 +144,17 @@ function thirdSearch() {
     if (url.indexOf("-02.js") != -1) {
       window.removeEventListener(aEvent.type, _onEvent);
 
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 8 + token.length,
           "The editor didn't jump to the correct line. (3)");
-        is(gScripts.visibleItems, 1,
+        is(gScripts.visibleItems.length, 1,
           "Not all the correct scripts are shown after the search. (3)");
 
         fourthSearch(0, "ugger;", token);
       });
     }
   });
   write(".*-02\.js#" + token);
 }
@@ -176,41 +175,41 @@ function fourthSearch(i, string, token) 
   clear();
   ok(gEditor.getCaretPosition().line == 5 &&
      gEditor.getCaretPosition().col == 8 + token.length + i,
     "The editor didn't remain at the correct token. (5)");
 
   executeSoon(function() {
     let noMatchingScripts = gDebugger.L10N.getStr("noMatchingScriptsText");
 
-    is(gScripts.visibleItems, 2,
+    is(gScripts.visibleItems.length, 2,
       "Not all the scripts are shown after the searchbox was emptied.");
     is(gMenulist.selectedIndex, 1,
       "The menulist should have retained its selected index after the searchbox was emptied.");
 
     write("BOGUS");
     ok(gEditor.getCaretPosition().line == 5 &&
        gEditor.getCaretPosition().col == 8 + token.length + i,
       "The editor didn't remain at the correct token. (6)");
 
     is(gMenulist.getAttribute("label"), noMatchingScripts,
       "The menulist should display a notice that no scripts match the searched token.");
-    is(gScripts.visibleItems, 0,
+    is(gScripts.visibleItems.length, 0,
       "No scripts should be displayed in the menulist after a bogus search.");
     is(gMenulist.selectedIndex, 1,
       "The menulist should retain its selected index after a bogus search.");
 
     clear();
     ok(gEditor.getCaretPosition().line == 5 &&
        gEditor.getCaretPosition().col == 8 + token.length + i,
       "The editor didn't remain at the correct token. (7)");
 
     isnot(gMenulist.getAttribute("label"), noMatchingScripts,
       "The menulist should not display a notice after the searchbox was emptied.");
-    is(gScripts.visibleItems, 2,
+    is(gScripts.visibleItems.length, 2,
       "Not all the scripts are shown after the searchbox was emptied.");
     is(gMenulist.selectedIndex, 1,
       "The menulist should have retained its selected index after the searchbox was emptied of a bogus search.");
 
     noMatchingScriptsSingleCharCheck(token, i);
   });
 }
 
@@ -219,17 +218,17 @@ function noMatchingScriptsSingleCharChec
 
   write("x");
   ok(gEditor.getCaretPosition().line == 5 &&
      gEditor.getCaretPosition().col == 8 + token.length + i,
     "The editor didn't remain at the correct token. (8)");
 
   is(gMenulist.getAttribute("label"), noMatchingScripts,
     "The menulist should display a notice after no matches are found.");
-  is(gScripts.visibleItems, 0,
+  is(gScripts.visibleItems.length, 0,
     "No scripts should be shown after no matches are found.");
   is(gMenulist.selectedIndex, 1,
     "The menulist should have retained its selected index after no matches are found.");
 
   closeDebuggerAndFinish();
 }
 
 function clear() {
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
@@ -81,17 +81,17 @@ function firstSearch() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
         let scriptResults = gDebugger.document.querySelectorAll(".dbg-source-results");
         is(scriptResults.length, 2,
           "There should be matches found in two scripts.");
 
         let item0 = gDebugger.SourceResults.getItemForElement(scriptResults[0]);
         let item1 = gDebugger.SourceResults.getItemForElement(scriptResults[1]);
@@ -197,17 +197,17 @@ function secondSearch() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
         let scriptResults = gDebugger.document.querySelectorAll(".dbg-source-results");
         is(scriptResults.length, 2,
           "There should be matches found in two scripts.");
 
         let item0 = gDebugger.SourceResults.getItemForElement(scriptResults[0]);
         let item1 = gDebugger.SourceResults.getItemForElement(scriptResults[1]);
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
@@ -81,17 +81,17 @@ function doSearch() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
         isnot(gSearchView._container._list.childNodes.length, 0,
           "The global search pane should be visible now.");
         isnot(gSearchView._container._parent.hidden, true,
           "The global search pane should be visible now.");
         isnot(gSearchView._splitter.hidden, true,
           "The global search pane splitter should be visible now.");
@@ -115,17 +115,17 @@ function doFirstJump() {
 
     let url = aEvent.detail.url;
     if (url.indexOf("-01.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 4 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't jump to the correct line. (1)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (1)");
 
         doSecondJump();
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
@@ -142,17 +142,17 @@ function doSecondJump() {
 
     let url = aEvent.detail.url;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't jump to the correct line. (2)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (2)");
 
         doWrapAroundJump();
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
@@ -169,17 +169,17 @@ function doWrapAroundJump() {
 
     let url = aEvent.detail.url;
     if (url.indexOf("-01.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 4 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't jump to the correct line. (3)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (3)");
 
         doBackwardsWrapAroundJump();
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
@@ -196,17 +196,17 @@ function doBackwardsWrapAroundJump() {
 
     let url = aEvent.detail.url;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't jump to the correct line. (4)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (4)");
 
         testSearchTokenEmpty();
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
@@ -223,17 +223,17 @@ function testSearchTokenEmpty() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't remain at the correct line. (5)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (5)");
 
         is(gSearchView._container._list.childNodes.length, 0,
           "The global search pane shouldn't have any child nodes after clear().");
         is(gSearchView._container._parent.hidden, true,
           "The global search pane shouldn't be visible after clear().");
         is(gSearchView._splitter.hidden, true,
           "The global search pane splitter shouldn't be visible after clear().");
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
@@ -81,17 +81,17 @@ function doSearch() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
         isnot(gSearchView._container._list.childNodes.length, 0,
           "The global search pane should be visible now.");
         isnot(gSearchView._container._parent.hidden, true,
           "The global search pane should be visible now.");
         isnot(gSearchView._splitter.hidden, true,
           "The global search pane splitter should be visible now.");
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-06.js
@@ -74,17 +74,17 @@ function doSearch() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
         testSearchMatchNotFound();
       });
     } else {
       ok(false, "The current script shouldn't have changed after a global search.");
     }
   });
@@ -101,17 +101,17 @@ function testSearchMatchNotFound() {
 
     let url = gScripts.selectedValue;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor didn't remain at the correct line.");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search.");
 
         closeDebuggerAndFinish();
       });
     } else {
       ok(false, "How did you get here? Go away!");
     }
   });
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-07.js
@@ -177,17 +177,17 @@ function testClickLineToJump(scriptResul
 
     let url = aEvent.detail.url;
     if (url.indexOf("-01.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 0 &&
            gEditor.getCaretPosition().col == 4,
           "The editor didn't jump to the correct line. (1)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (1)");
 
         callbacks[0](scriptResults, callbacks.slice(1));
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
@@ -212,17 +212,17 @@ function testClickMatchToJump(scriptResu
 
     let url = aEvent.detail.url;
     if (url.indexOf("-02.js") != -1) {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 5,
           "The editor didn't jump to the correct line. (1)");
-        is(gScripts.visibleItems, 2,
+        is(gScripts.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (1)");
 
         callbacks[0]();
       });
     } else {
       ok(false, "We jumped in a bowl of hot lava (aka WRONG MATCH). That was bad for us.");
     }
   });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-files_ui.js
@@ -0,0 +1,604 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_update-editor-mode.html";
+
+/**
+ * Tests basic functionality of scripts filtering (file search) helper UI.
+ */
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+var gEditor = null;
+var gScripts = null;
+var gSearchBox = null;
+var gFilteredSources = null;
+var gMenulist = null;
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+  });
+
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+    Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
+  });
+}
+
+function testScriptSearching() {
+  gEditor = gDebugger.DebuggerView.editor;
+  gScripts = gDebugger.DebuggerView.Sources;
+  gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+  gFilteredSources = gDebugger.DebuggerView.FilteredSources;
+  gMenulist = gScripts._container;
+
+  firstSearch();
+}
+
+function firstSearch() {
+  window.addEventListener("popupshown", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    for (let i = 0; i < gFilteredSources.totalItems; i++) {
+      is(gFilteredSources.labels[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.labels[i]),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.values[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.values[i]),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].label,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.labels[i]),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].value,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.values[i]),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].attachment.fullLabel, gScripts.labels[i],
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].attachment.fullValue, gScripts.values[i],
+        "The filtered sources view should have the correct values.");
+    }
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("update-editor-mode.html") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        secondSearch();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  write(".");
+}
+
+function secondSearch() {
+  let sourceshown = false;
+  let popupshown = false;
+  let proceeded = false;
+
+  window.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent1);
+    sourceshown = true;
+    executeSoon(proceed);
+  });
+  window.addEventListener("popupshown", function _onEvent2(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent2);
+    popupshown = true;
+    executeSoon(proceed);
+  });
+
+  function proceed() {
+    if (!sourceshown || !popupshown || proceeded) {
+      return;
+    }
+    proceeded = true;
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 1,
+      "The filtered sources view should have 1 items available.");
+    is(gFilteredSources.visibleItems.length, 1,
+      "The filtered sources view should have 1 items visible.");
+
+    for (let i = 0; i < gFilteredSources.totalItems; i++) {
+      is(gFilteredSources.labels[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].label),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.values[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].value),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].label,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].label),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].value,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].value),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].attachment.fullLabel, gScripts.visibleItems[i].label,
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].attachment.fullValue, gScripts.visibleItems[i].value,
+        "The filtered sources view should have the correct values.");
+    }
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 1,
+          "Not all the correct scripts are shown after the search.");
+
+        thirdSearch();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  }
+  append("-0")
+}
+
+function thirdSearch() {
+  let sourceshown = false;
+  let popupshown = false;
+  let proceeded = false;
+
+  window.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent1);
+    sourceshown = true;
+    executeSoon(proceed);
+  });
+  window.addEventListener("popupshown", function _onEvent2(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent2);
+    popupshown = true;
+    executeSoon(proceed);
+  });
+
+  function proceed() {
+    if (!sourceshown || !popupshown || proceeded) {
+      return;
+    }
+    proceeded = true;
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    for (let i = 0; i < gFilteredSources.totalItems; i++) {
+      is(gFilteredSources.labels[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].label),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.values[i],
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].value),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].label,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].label),
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].value,
+         gDebugger.SourceUtils.trimUrlLength(gScripts.visibleItems[i].value),
+        "The filtered sources view should have the correct values.");
+
+      is(gFilteredSources.visibleItems[i].attachment.fullLabel, gScripts.visibleItems[i].label,
+        "The filtered sources view should have the correct labels.");
+      is(gFilteredSources.visibleItems[i].attachment.fullValue, gScripts.visibleItems[i].value,
+        "The filtered sources view should have the correct values.");
+    }
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("update-editor-mode.html") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        goDown();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  }
+  backspace(1)
+}
+
+function goDown() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-editor-mode") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        goDownAgain();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendKey("DOWN");
+}
+
+function goDownAgain() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        goDownAndWrap();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.synthesizeKey("g", { metaKey: true });
+}
+
+function goDownAndWrap() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("update-editor-mode.html") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        goUpAndWrap();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.synthesizeKey("n", { ctrlKey: true });
+}
+
+function goUpAndWrap() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        clickAndSwitch();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendKey("UP");
+}
+
+function clickAndSwitch() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("update-editor-mode.html") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        clickAndSwitchAgain();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendMouseEvent({ type: "click" }, gFilteredSources.visibleItems[0].target);
+}
+
+function clickAndSwitchAgain() {
+  window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    is(gFilteredSources.totalItems, 3,
+      "The filtered sources view should have 3 items available.");
+    is(gFilteredSources.visibleItems.length, 3,
+      "The filtered sources view should have 3 items visible.");
+
+    is(gFilteredSources.selectedValue,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedValue),
+      "The correct item should be selected in the filtered sources view");
+    is(gFilteredSources.selectedLabel,
+       gDebugger.SourceUtils.trimUrlLength(gScripts.selectedLabel),
+      "The correct item should be selected in the filtered sources view");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        switchFocusWithEscape();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendMouseEvent({ type: "click" }, gFilteredSources.visibleItems[2].target);
+}
+
+function switchFocusWithEscape() {
+  window.addEventListener("popuphidden", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        focusAgainAfterEscape();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendKey("ESCAPE");
+}
+
+function focusAgainAfterEscape() {
+  window.addEventListener("popupshown", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 1,
+          "Not all the correct scripts are shown after the search.");
+
+        switchFocusWithReturn();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  append("0");
+}
+
+function switchFocusWithReturn() {
+  window.addEventListener("popuphidden", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+
+    info("Current script url:\n" + gScripts.selectedValue + "\n");
+    info("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    let url = gScripts.selectedValue;
+    if (url.indexOf("test-script-switching-01.js") != -1) {
+
+      executeSoon(function() {
+        info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 0 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line.");
+        is(gScripts.visibleItems.length, 3,
+          "Not all the correct scripts are shown after the search.");
+
+        closeDebuggerAndFinish();
+      });
+    } else {
+      ok(false, "How did you get here?");
+    }
+  });
+  EventUtils.sendKey("RETURN");
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+  append(text);
+}
+
+function backspace(times) {
+  for (let i = 0; i < times; i++) {
+    EventUtils.sendKey("BACK_SPACE")
+  }
+}
+
+function append(text) {
+  gSearchBox.focus();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+  info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+  gEditor = null;
+  gScripts = null;
+  gSearchBox = null;
+  gFilteredSources = null;
+  gMenulist = null;
+});
--- 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 = {
@@ -179,16 +180,20 @@ TabTarget.prototype = {
 
   supports: supports,
   get version() { return getVersion(); },
 
   get tab() {
     return this._tab;
   },
 
+  get window() {
+    return this._tab.linkedBrowser.contentWindow;
+  },
+
   get name() {
     return this._tab.linkedBrowser.contentDocument.title;
   },
 
   get url() {
     return this._tab.linkedBrowser.contentDocument.location.href;
   },
 
@@ -219,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;
   },
 };
 
 
@@ -318,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.
  */
@@ -361,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:///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,59 +277,43 @@ 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);
     }
   },
 
   /**
    * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
    *
    * @param {iframe} frame
    *        The iframe to contain the buttons
    */
   _buildButtons: function TBOX_buildButtons(frame) {
+    if (this.target.isRemote) {
+      return;
+    }
+
     let toolbarSpec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
     let environment = { chromeDocument: frame.ownerDocument };
     let requisition = new Requisition(environment);
     requisition.commandOutputManager = new CommandOutputManager();
 
     let buttons = CommandUtils.createButtons(toolbarSpec, this.doc, requisition);
 
     let container = this.doc.getElementById("toolbox-buttons");
@@ -287,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");
     }
@@ -327,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
    *
@@ -389,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;
@@ -495,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://gre/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:///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/CmdResize.jsm
+++ b/browser/devtools/responsivedesign/CmdResize.jsm
@@ -27,16 +27,17 @@ gcli.addCommand({
   manual: gcli.lookup('resizeModeManual'),
   exec: gcli_cmd_resize
 });
 
 gcli.addCommand({
   name: 'resize toggle',
   buttonId: "command-button-responsive",
   buttonClass: "command-button devtools-toolbarbutton",
+  tooltipText: gcli.lookup("resizeModeToggleTooltip"),
   description: gcli.lookup('resizeModeToggleDesc'),
   manual: gcli.lookup('resizeModeManual'),
   exec: gcli_cmd_resize
 });
 
 gcli.addCommand({
   name: 'resize to',
   description: gcli.lookup('resizeModeToDesc'),
--- 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();
       }
     }
   },
 
@@ -603,49 +603,49 @@ ResponsiveUI.prototype = {
     }
     this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true);
     this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true);
     this.container.style.pointerEvents = "none";
 
     this._resizing = true;
     this.stack.setAttribute("notransition", "true");
 
-    this.lastClientX = aEvent.clientX;
-    this.lastClientY = aEvent.clientY;
+    this.lastScreenX = aEvent.screenX;
+    this.lastScreenY = aEvent.screenY;
 
     this.ignoreY = (aEvent.target === this.resizeBar);
 
     this.isResizing = true;
   },
 
   /**
    * Resizing on mouse move.
    *
    * @param aEvent
    */
   onDrag: function RUI_onDrag(aEvent) {
-    let deltaX = aEvent.clientX - this.lastClientX;
-    let deltaY = aEvent.clientY - this.lastClientY;
+    let deltaX = aEvent.screenX - this.lastScreenX;
+    let deltaY = aEvent.screenY - this.lastScreenY;
 
     if (this.ignoreY)
       deltaY = 0;
 
     let width = this.customPreset.width + deltaX;
     let height = this.customPreset.height + deltaY;
 
     if (width < MIN_WIDTH) {
         width = MIN_WIDTH;
     } else {
-        this.lastClientX = aEvent.clientX;
+        this.lastScreenX = aEvent.screenX;
     }
 
     if (height < MIN_HEIGHT) {
         height = MIN_HEIGHT;
     } else {
-        this.lastClientY = aEvent.clientY;
+        this.lastScreenY = aEvent.screenY;
     }
 
     this.setSize(width, height);
   },
 
   /**
    * Stop End resizing
    */
--- 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/CmdScratchpad.jsm
+++ b/browser/devtools/scratchpad/CmdScratchpad.jsm
@@ -8,14 +8,15 @@ Components.utils.import("resource:///mod
 
 /**
  * 'scratchpad' command
  */
 gcli.addCommand({
   name: "scratchpad",
   buttonId: "command-button-scratchpad",
   buttonClass: "command-button devtools-toolbarbutton",
+  tooltipText: gcli.lookup("scratchpadOpenTooltip"),
   hidden: true,
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     chromeWindow.Scratchpad.ScratchpadManager.openScratchpad();
   }
 });
--- 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/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -76,16 +76,22 @@ this.CommandUtils = {
       }
       else {
         if (command.buttonId != null) {
           button.id = command.buttonId;
         }
         if (command.buttonClass != null) {
           button.className = command.buttonClass;
         }
+        if (command.tooltipText != null) {
+          button.setAttribute("tooltiptext", command.tooltipText);
+        }
+        else if (command.description != null) {
+          button.setAttribute("tooltiptext", command.description);
+        }
 
         button.addEventListener("click", function() {
           requisition.update(buttonSpec.typed);
           //if (requisition.getStatus() == Status.VALID) {
             requisition.exec();
           /*
           }
           else {
--- 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/VariablesView.jsm
+++ b/browser/devtools/shared/VariablesView.jsm
@@ -60,16 +60,17 @@ VariablesView.prototype = {
    *
    * @param string aName
    *        The scope's name (e.g. "Local", "Global" etc.).
    * @return Scope
    *         The newly created Scope instance.
    */
   addScope: function VV_addScope(aName = "") {
     this._removeEmptyNotice();
+    this._toggleSearch(true);
 
     let scope = new Scope(this, aName);
     this._store.set(scope.id, scope);
     this._currHierarchy.set(aName, scope);
     scope.header = !!aName;
     return scope;
   },
 
@@ -95,16 +96,17 @@ VariablesView.prototype = {
     let firstChild;
 
     while (firstChild = list.firstChild) {
       list.removeChild(firstChild);
     }
 
     this._store = new Map();
     this._appendEmptyNotice();
+    this._toggleSearch(false);
   },
 
   /**
    * Emptying this container and rebuilding it immediately afterwards would
    * result in a brief redraw flicker, because the previously expanded nodes
    * may get asynchronously re-expanded, after fetching the prototype and
    * properties from a server.
    *
@@ -128,16 +130,17 @@ VariablesView.prototype = {
     this._emptyTimeout = window.setTimeout(function() {
       this._emptyTimeout = null;
 
       this._parent.removeChild(prevList);
       this._parent.appendChild(currList);
 
       if (!this._store.size) {
         this._appendEmptyNotice();
+        this._toggleSearch(false);
       }
     }.bind(this), aTimeout);
   },
 
   /**
    * Specifies if enumerable properties and variables should be displayed.
    * These variables and properties are visible by default.
    * @param boolean aFlag
@@ -182,50 +185,65 @@ VariablesView.prototype = {
    * Enables variable and property searching in this view.
    */
   enableSearch: function VV_enableSearch() {
     // If searching was already enabled, no need to re-enable it again.
     if (this._searchboxContainer) {
       return;
     }
     let document = this.document;
-    let parent = this._parent;
+    let ownerView = this._parent.parentNode;
 
     let container = this._searchboxContainer = document.createElement("hbox");
     container.className = "devtools-toolbar";
+    container.hidden = !this._store.size;
 
     let searchbox = this._searchboxNode = document.createElement("textbox");
-    searchbox.className = "devtools-searchinput";
+    searchbox.className = "variables-searchinput devtools-searchinput";
     searchbox.setAttribute("placeholder", this._searchboxPlaceholder);
     searchbox.setAttribute("type", "search");
     searchbox.setAttribute("flex", "1");
     searchbox.addEventListener("input", this._onSearchboxInput, false);
     searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
 
     container.appendChild(searchbox);
-    parent.insertBefore(container, parent.firstChild);
+    ownerView.insertBefore(container, this._parent);
   },
 
   /**
    * Disables variable and property searching in this view.
    */
   disableSearch: function VV_disableSearch() {
     // If searching was already disabled, no need to re-disable it again.
     if (!this._searchboxContainer) {
       return;
     }
-    this._parent.removeChild(this._searchboxContainer);
+    this._searchboxContainer.parentNode.removeChild(this._searchboxContainer);
     this._searchboxNode.addEventListener("input", this._onSearchboxInput, false);
     this._searchboxNode.addEventListener("keypress", this._onSearchboxKeyPress, false);
 
     this._searchboxContainer = null;
     this._searchboxNode = null;
   },
 
   /**
+   * Sets the variables searchbox hidden or visible. It's hidden by default.
+   *
+   * @param boolean aVisibleFlag
+   *        Specifies the intended visibility.
+   */
+  _toggleSearch: function VV__toggleSearch(aVisibleFlag) {
+    // If searching was already disabled, there's no need to hide it.
+    if (!this._searchboxContainer) {
+      return;
+    }
+    this._searchboxContainer.hidden = !aVisibleFlag;
+  },
+
+  /**
    * Sets if the variable and property searching is enabled.
    */
   set searchEnabled(aFlag) aFlag ? this.enableSearch() : this.disableSearch(),
 
   /**
    * Gets if the variable and property searching is enabled.
    */
   get searchEnabled() !!this._searchboxContainer,
@@ -656,16 +674,23 @@ Scope.prototype = {
 
   /**
    * Sets the twisty visibility state.
    * @param boolean aFlag
    */
   set twisty(aFlag) aFlag ? this.showArrow() : this.hideArrow(),
 
   /**
+   * Specifies if the configurable/enumerable/writable tooltip should be shown
+   * whenever a variable or property descriptor is available.
+   * This flag applies non-recursively to the current scope.
+   */
+  showDescriptorTooltip: true,
+
+  /**
    * Specifies if editing variable or property names is allowed.
    * This flag applies non-recursively to the current scope.
    */
   allowNameInput: false,
 
   /**
    * Specifies if editing variable or property values is allowed.
    * This flag applies non-recursively to the current scope.
@@ -1251,36 +1276,37 @@ create({ constructor: Variable, proto: S
 
   /**
    * Creates a tooltip for this variable.
    */
   _displayTooltip: function V__displayTooltip() {
     this._target.removeEventListener("mouseover", this._displayTooltip, false);
     let document = this.document;
 
-    let tooltip = document.createElement("tooltip");
-    tooltip.id = "tooltip-" + this.id;
+    if (this.ownerView.showDescriptorTooltip) {
+      let tooltip = document.createElement("tooltip");
+      tooltip.id = "tooltip-" + this.id;
 
-    let configurableLabel = document.createElement("label");
-    configurableLabel.setAttribute("value", "configurable");
+      let configurableLabel = document.createElement("label");
+      configurableLabel.setAttribute("value", "configurable");
 
-    let enumerableLabel = document.createElement("label");
-    enumerableLabel.setAttribute("value", "enumerable");
+      let enumerableLabel = document.createElement("label");
+      enumerableLabel.setAttribute("value", "enumerable");
 
-    let writableLabel = document.createElement("label");
-    writableLabel.setAttribute("value", "writable");
+      let writableLabel = document.createElement("label");
+      writableLabel.setAttribute("value", "writable");
 
-    tooltip.setAttribute("orient", "horizontal")
-    tooltip.appendChild(configurableLabel);
-    tooltip.appendChild(enumerableLabel);
-    tooltip.appendChild(writableLabel);
+      tooltip.setAttribute("orient", "horizontal")
+      tooltip.appendChild(configurableLabel);
+      tooltip.appendChild(enumerableLabel);
+      tooltip.appendChild(writableLabel);
 
-    this._target.appendChild(tooltip);
-    this._target.setAttribute("tooltip", tooltip.id);
-
+      this._target.appendChild(tooltip);
+      this._target.setAttribute("tooltip", tooltip.id);
+    }
     if (this.ownerView.allowNameInput) {
       this._name.setAttribute("tooltiptext", L10N.getStr("variablesEditableNameTooltip"));
     }
     if (this.ownerView.allowValueInput) {
       this._valueLabel.setAttribute("tooltiptext", L10N.getStr("variablesEditableValueTooltip"));
     }
     if (this.ownerView.allowDeletion) {
       this._closeNode.setAttribute("tooltiptext", L10N.getStr("variablesCloseButtonTooltip"));
--- 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/StyleEditor.jsm
+++ b/browser/devtools/styleeditor/StyleEditor.jsm
@@ -655,17 +655,17 @@ StyleEditor.prototype = {
     }
 
     let content = this.contentDocument;
 
     // Insert the global transition rule
     // Use a ref count to make sure we do not add it multiple times.. and remove
     // it only when all pending StyleEditor-generated transitions ended.
     if (!this._transitionRefCount) {
-      this._styleSheet.insertRule(TRANSITION_RULE, 0);
+      this.styleSheet.insertRule(TRANSITION_RULE, this.styleSheet.cssRules.length);
       content.documentElement.classList.add(TRANSITION_CLASS);
     }
 
     this._transitionRefCount++;
 
     // Set up clean up and commit after transition duration (+10% buffer)
     // @see _onTransitionEnd
     content.defaultView.setTimeout(this._onTransitionEnd.bind(this),
@@ -677,17 +677,17 @@ StyleEditor.prototype = {
   /**
     * This cleans up class and rule added for transition effect and then trigger
     * Commit as the changes have been completed.
     */
   _onTransitionEnd: function SE__onTransitionEnd()
   {
     if (--this._transitionRefCount == 0) {
       this.contentDocument.documentElement.classList.remove(TRANSITION_CLASS);
-      this.styleSheet.deleteRule(0);
+      this.styleSheet.deleteRule(this.styleSheet.cssRules.length - 1);
     }
 
     this._triggerAction("Commit");
   },
 
   /**
    * Show file picker and return the file user selected.
    *
@@ -804,18 +804,18 @@ StyleEditor.prototype = {
       if (sheet.cssRules) {
         let rules = sheet.cssRules;
         if (rules.length
             && rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
           return this._convertToUnicode(aString, rules.item(0).encoding);
         }
       }
 
-      if (sheet.ownerNode) {
-        // step 3: see <link charset="…">
+      // step 3: charset attribute of <link> or <style> element, if it exists
+      if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
         let linkCharset = sheet.ownerNode.getAttribute("charset");
         if (linkCharset != null) {
           return this._convertToUnicode(aString, linkCharset);
         }
       }
 
       // step 4 (1 of 2): charset of referring stylesheet.
       let parentSheet = sheet.parentStyleSheet;
@@ -947,17 +947,17 @@ StyleEditor.prototype = {
    *
    * @param string aText
    *        Optional CSS text.
    */
   _appendNewStyleSheet: function SE__appendNewStyleSheet(aText)
   {
     let document = this.contentDocument;
     let parent = document.documentElement;
-    let style = document.createElement("style");
+    let style = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
     style.setAttribute("type", "text/css");
     if (aText) {
       style.appendChild(document.createTextNode(aText));
     }
     parent.appendChild(style);
 
     this._styleSheet = document.styleSheets[document.styleSheets.length - 1];
     if (aText) {
--- 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.tab.linkedBrowser.contentWindow;
-  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
@@ -731,17 +731,17 @@ CssHtmlTree.prototype = {
  * instance will render the rules.
  */
 this.PropertyView = function PropertyView(aTree, aName)
 {
   this.tree = aTree;
   this.name = aName;
   this.getRTLAttr = aTree.getRTLAttr;
 
-  this.link = "https://developer.mozilla.org/en/CSS/" + aName;
+  this.link = "https://developer.mozilla.org/CSS/" + aName;
 
   this.templateMatchedSelectors = aTree.styleDocument.getElementById("templateMatchedSelectors");
 }
 
 PropertyView.prototype = {
   // The parent element which contains the open attribute
   element: null,
 
@@ -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/CmdTilt.jsm
+++ b/browser/devtools/tilt/CmdTilt.jsm
@@ -39,16 +39,17 @@ gcli.addCommand({
 
 /**
  * 'tilt toggle' command
  */
 gcli.addCommand({
   name: "tilt toggle",
   buttonId: "command-button-tilt",
   buttonClass: "command-button  devtools-toolbarbutton",
+  tooltipText: gcli.lookup("tiltToggleTooltip"),
   hidden: true,
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     Tilt.toggle();
   }
 });
 
--- 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
@@ -184,16 +184,20 @@ tiltManual=Investigate the relationship 
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 tiltOpenDesc=Open the Inspector 3D view
 
 # LOCALIZATION NOTE (tiltOpenManual) A fuller description of the 'tilt translate'
 # command, displayed when the user asks for help on what it does.
 tiltOpenManual=Initialize the 3D page inspector and optionally highlight a node using a CSS selector
 
+# LOCALIZATION NOTE (tiltToggleTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles Tilt 3D View.
+tiltToggleTooltip=3D View
+
 # LOCALIZATION NOTE (tiltTranslateDesc) A very short description of the 'tilt translate'
 # command. See tiltTranslateManual for a fuller description of what it does. This
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 tiltTranslateDesc=Move the webpage mesh
 
 # LOCALIZATION NOTE (tiltTranslateManual) A fuller description of the 'tilt translate'
 # command, displayed when the user asks for help on what it does.
@@ -294,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
 
@@ -335,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
 
@@ -471,16 +475,20 @@ resizeModeOnDesc=Enter Responsive Design
 # alongside the command name, which is why it should be as short as possible.
 resizeModeOffDesc=Exit Responsive Design Mode
 
 # LOCALIZATION NOTE (resizeModeToggleDesc) A very short string to describe the
 # 'resize toggle' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
 resizeModeToggleDesc=Toggle Responsive Design Mode
 
+# LOCALIZATION NOTE (resizeModeToggleTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles Responsive Design Mode.
+resizeModeToggleTooltip=Responsive Design Mode
+
 # LOCALIZATION NOTE (resizeModeToDesc) A very short string to describe the
 # 'resize to' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
 resizeModeToDesc=Alter page size
 
 # LOCALIZATION NOTE (resizeModeDesc) A very short string to describe the
 # 'resize' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
@@ -972,8 +980,12 @@ callLogChromeVarNotFoundChrome=Variable 
 # result of the 'calllog chromestart' command with a source type of JavaScript
 # and invalid JavaScript code.
 callLogChromeEvalException=Evaluated JavaScript threw the following exception
 
 # LOCALIZATION NOTE (callLogChromeEvalNeedsObject) A string displayed as the
 # result of passing a non-JavaScript object creating source via the
 # 'calllog chromestart javascript' command.
 callLogChromeEvalNeedsObject=The JavaScript source must evaluate to an object whose method calls are to be logged e.g. "({a1: function() {this.a2()},a2: function() {}});"
+
+# LOCALIZATION NOTE (scratchpadOpenTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which opens Scratchpad.
+scratchpadOpenTooltip=Scratchpad
--- a/browser/themes/gnomestripe/devtools/debugger.css
+++ b/browser/themes/gnomestripe/devtools/debugger.css
@@ -8,17 +8,17 @@
   background-color: white;
 }
 
 /**
  * Debugger content
  */
 
 #chrome-globals, #sources {
-  min-width: 150px;
+  width: 200px;
 }
 
 /**
  * Lists and headers
  */
 
 .list-item {
   padding: 2px;
@@ -39,16 +39,21 @@
 }
 
 /**
  * Sources searching
  */
 
 #globalsearch {
   background-color: white;
+  min-height: 10px;
+}
+
+#globalsearch > vbox:not(:empty) {
+  min-height: 10px;
   max-height: 150px;
 }
 
 .dbg-source-results:not(:last-child) {
   border-bottom: 1px dotted #aaa;
 }
 
 .dbg-results-header {
@@ -96,16 +101,20 @@
   transition: transform 0.25s ease-in-out;
 }
 
 .dbg-results-container .line-contents > .string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
+/**
+ * Searchbox panel
+ */
+
 #searchbox-panel .description {
   margin: -6px 0 8px 0;
 }
 
 #searchbox-panel button.operator {
   min-width: 0;
   margin: 0;
   padding: 0;
@@ -113,16 +122,60 @@
 }
 
 #searchbox-panel label.operator {
   -moz-padding-start: 6px;
   padding-bottom: 1px;
 }
 
 /**
+ * Filtered sources panel
+ */
+
+#filtered-sources-panel {
+  padding: 4px;
+}
+
+.dbg-source-item {
+  background: #f4f4f4;
+  border: 1px solid #ddd;
+  border-top-color: #fff;
+}
+
+.dbg-source-item.selected {
+  background: #cddae5;
+}
+
+.dbg-source-item:first-of-type {
+  border-top-color: #ddd;
+  border-radius: 4px 4px 0 0;
+}
+
+.dbg-source-item:last-of-type {
+  border-radius: 0 0 4px 4px;
+}
+
+.dbg-source-item:only-of-type {
+  border-radius: 4px 4px 4px 4px;
+}
+
+.dbg-source-item:not(:hover) {
+  text-shadow: 0 1px #fff;
+}
+
+.dbg-source-item-name {
+  color: #333;
+  font-weight: 600;
+}
+
+.dbg-source-item-details {
+  color: #888;
+}
+
+/**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
   background-color: white;
   min-width: 50px;
 }
 
@@ -389,17 +442,17 @@
   color: #048;
   font-weight: 600;
 }
 
 /**
  * Variables and properties searching
  */
 
-#variables .devtools-searchinput {
+.variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
 .property[non-match] {
   border: none;
   margin: 0;
 }
--- a/browser/themes/pinstripe/devtools/debugger.css
+++ b/browser/themes/pinstripe/devtools/debugger.css
@@ -10,17 +10,17 @@
   background-color: white;
 }
 
 /**
  * Debugger content
  */
 
 #chrome-globals, #sources {
-  min-width: 150px;
+  width: 200px;
 }
 
 /**
  * Lists and headers
  */
 
 .list-item {
   padding: 2px;
@@ -41,16 +41,21 @@
 }
 
 /**
  * Sources searching
  */
 
 #globalsearch {
   background-color: white;
+  min-height: 10px;
+}
+
+#globalsearch > vbox:not(:empty) {
+  min-height: 10px;
   max-height: 150px;
 }
 
 .dbg-source-results:not(:last-child) {
   border-bottom: 1px dotted #aaa;
 }
 
 .dbg-results-header {
@@ -98,16 +103,20 @@
   transition: transform 0.25s ease-in-out;
 }
 
 .dbg-results-container .line-contents > .string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
+/**
+ * Searchbox panel
+ */
+
 #searchbox-panel .description {
   margin: -6px 0 8px 0;
 }
 
 #searchbox-panel button.operator {
   min-width: 0;
   margin: 0;
   padding: 0;
@@ -115,16 +124,60 @@
 }
 
 #searchbox-panel label.operator {
   -moz-padding-start: 6px;
   padding-bottom: 1px;
 }
 
 /**
+ * Filtered sources panel
+ */
+
+#filtered-sources-panel {
+  padding: 4px;
+}
+
+.dbg-source-item {
+  background: #f4f4f4;
+  border: 1px solid #ddd;
+  border-top-color: #fff;
+}
+
+.dbg-source-item.selected {
+  background: #cddae5;
+}
+
+.dbg-source-item:first-of-type {
+  border-top-color: #ddd;
+  border-radius: 4px 4px 0 0;
+}
+
+.dbg-source-item:last-of-type {
+  border-radius: 0 0 4px 4px;
+}
+
+.dbg-source-item:only-of-type {
+  border-radius: 4px 4px 4px 4px;
+}
+
+.dbg-source-item:not(.selected):not(:hover) {
+  text-shadow: 0 1px #fff;
+}
+
+.dbg-source-item-name {
+  color: #333;
+  font-weight: 600;
+}
+
+.dbg-source-item-details {
+  color: #888;
+}
+
+/**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
   background-color: white;
   min-width: 50px;
 }
 
@@ -391,17 +444,17 @@
   color: #048;
   font-weight: 600;
 }
 
 /**
  * Variables and properties searching
  */
 
-#variables .devtools-searchinput {
+.variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
 .property[non-match] {
   border: none;
   margin: 0;
 }
--- a/browser/themes/winstripe/devtools/debugger.css
+++ b/browser/themes/winstripe/devtools/debugger.css
@@ -8,17 +8,17 @@
   background-color: white;
 }
 
 /**
  * Debugger content
  */
 
 #chrome-globals, #sources {
-  min-width: 150px;
+  width: 200px;
 }
 
 /**
  * This hardcoded width likely due to a toolkit Windows specific bug.
  * See http://hg.mozilla.org/mozilla-central/annotate/f38d6df93cad/toolkit/themes/winstripe/global/textbox-aero.css#l7
  */
 #searchbox {
   width: 200px;
@@ -47,16 +47,21 @@
 }
 
 /**
  * Sources searching
  */
 
 #globalsearch {
   background-color: white;
+  min-height: 10px;
+}
+
+#globalsearch > vbox:not(:empty) {
+  min-height: 10px;
   max-height: 150px;
 }
 
 .dbg-source-results:not(:last-child) {
   border-bottom: 1px dotted #aaa;
 }
 
 .dbg-results-header {
@@ -104,16 +109,20 @@
   transition: transform 0.25s ease-in-out;
 }
 
 .dbg-results-container .line-contents > .string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
+/**
+ * Searchbox panel
+ */
+
 #searchbox-panel .description {
   margin: -6px 0 8px 0;
 }
 
 #searchbox-panel button.operator {
   min-width: 0;
   margin: 0;
   padding: 0;
@@ -121,16 +130,60 @@
 }
 
 #searchbox-panel label.operator {
   -moz-padding-start: 6px;
   padding-bottom: 1px;
 }
 
 /**
+ * Filtered sources panel
+ */
+
+#filtered-sources-panel {
+  padding: 4px;
+}
+
+.dbg-source-item {
+  background: #f4f4f4;
+  border: 1px solid #ddd;
+  border-top-color: #fff;
+}
+
+.dbg-source-item.selected {
+  background: #cddae5;
+}
+
+.dbg-source-item:first-of-type {
+  border-top-color: #ddd;
+  border-radius: 4px 4px 0 0;
+}
+
+.dbg-source-item:last-of-type {
+  border-radius: 0 0 4px 4px;
+}
+
+.dbg-source-item:only-of-type {
+  border-radius: 4px 4px 4px 4px;
+}
+
+.dbg-source-item:not(:hover) {
+  text-shadow: 0 1px #fff;
+}
+
+.dbg-source-item-name {
+  color: #333;
+  font-weight: 600;
+}
+
+.dbg-source-item-details {
+  color: #888;
+}
+
+/**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
   background-color: white;
   min-width: 50px;
 }
 
@@ -397,17 +450,17 @@
   color: #048;
   font-weight: 600;
 }
 
 /**
  * Variables and properties searching
  */
 
-#variables .devtools-searchinput {
+.variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
 .property[non-match] {
   border: none;
   margin: 0;
 }
--- a/configure.in
+++ b/configure.in
@@ -7597,16 +7597,25 @@ MOZ_ARG_ENABLE_BOOL(b2g-camera,
     MOZ_B2G_CAMERA=1,
     MOZ_B2G_CAMERA= )
 if test -n "$MOZ_B2G_CAMERA"; then
    AC_DEFINE(MOZ_B2G_CAMERA)
 fi
 AC_SUBST(MOZ_B2G_CAMERA)
 
 dnl ========================================================
+dnl = Enable Support B2G-specific changes to the NSS
+dnl = certificate trust database.
+dnl ========================================================
+if test -n "$MOZ_B2G_CERTDATA"; then
+    AC_DEFINE(MOZ_B2G_CERTDATA)
+fi
+AC_SUBST(MOZ_B2G_CERTDATA)
+
+dnl ========================================================
 dnl = Enable Support for Payment API
 dnl ========================================================
 if test -n "$MOZ_PAY"; then
     AC_DEFINE(MOZ_PAY)
 fi
 AC_SUBST(MOZ_PAY)
 
 dnl ========================================================
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1620,16 +1620,17 @@ this.DOMApplicationRegistry = {
         this._readManifests(aData, aFinalCallback, index + 1);
     }).bind(this));
   },
 
   downloadPackage: function(aManifest, aApp, aIsUpdate, aOnSuccess) {
     // Here are the steps when installing a package:
     // - create a temp directory where to store the app.
     // - download the zip in this directory.
+    // - check the signature on the zip.
     // - extract the manifest from the zip and check it.
     // - ask confirmation to the user.
     // - add the new app to the registry.
     // If we fail at any step, we backout the previous ones and return an error.
 
     debug("downloadPackage " + JSON.stringify(aApp));
 
     let id = this._appIdForManifestURL(aApp.manifestURL);
@@ -1659,38 +1660,16 @@ this.DOMApplicationRegistry = {
       app.installState = download ? download.previousState : "pending";
       self.broadcastMessage("Webapps:PackageEvent",
                             { type: "error",
                               manifestURL:  aApp.manifestURL,
                               error: aError,
                               app: app });
     }
 
-    function getInferedStatus() {
-      // XXX Update once we have digital signatures (bug 772365)
-      return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
-    }
-
-    function getAppStatus(aManifest) {
-      let manifestStatus = AppsUtils.getAppManifestStatus(aManifest);
-      let inferedStatus = getInferedStatus();
-
-      return (Services.prefs.getBoolPref("dom.mozApps.dev_mode") ? manifestStatus
-                                                                : inferedStatus);
-    }
-    // Returns true if the privilege level from the manifest
-    // is lower or equal to the one we infered for the app.
-    function checkAppStatus(aManifest) {
-      if (Services.prefs.getBoolPref("dom.mozApps.dev_mode")) {
-        return true;
-      }
-
-      return (AppsUtils.getAppManifestStatus(aManifest) <= getInferedStatus());
-    }
-
     function download() {
       debug("About to download " + aManifest.fullPackagePath());
 
       let requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
                                   .QueryInterface(Ci.nsIHttpChannel);
       self.downloads[aApp.manifestURL] =
         { channel:requestChannel,
           appId: id,
@@ -1745,71 +1724,97 @@ this.DOMApplicationRegistry = {
       app.installState = aIsUpdate ? "updating" : "pending";
 
       NetUtil.asyncFetch(requestChannel, function(aInput, aResult, aRequest) {
         if (!Components.isSuccessCode(aResult)) {
           // We failed to fetch the zip.
           cleanup("NETWORK_ERROR");
           return;
         }
-        // Copy the zip on disk.
+        // Copy the zip on disk. XXX: this can consume all disk space.
         let zipFile = FileUtils.getFile("TmpD",
                                         ["webapps", id, "application.zip"], true);
         let ostream = FileUtils.openSafeFileOutputStream(zipFile);
         NetUtil.asyncCopy(aInput, ostream, function (aResult) {
           if (!Components.isSuccessCode(aResult)) {
             // We failed to save the zip.
             cleanup("DOWNLOAD_ERROR");
             return;
           }
 
-          let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
-                          .createInstance(Ci.nsIZipReader);
-          try {
-            zipReader.open(zipFile);
-            if (!zipReader.hasEntry("manifest.webapp")) {
-              throw "MISSING_MANIFEST";
-            }
-
-            let istream = zipReader.getInputStream("manifest.webapp");
+		  let certdb;
+		  try {
+			certdb = Cc["@mozilla.org/security/x509certdb;1"]
+					   .getService(Ci.nsIX509CertDB);
+		  } catch (e) {
+		    cleanup("CERTDB_ERROR");
+			return;
+		  }
+          certdb.openSignedJARFileAsync(zipFile, function(aRv, aZipReader) {
+            try {
+              let zipReader;
+              let isSigned;
+              if (Components.isSuccessCode(aRv)) {
+                isSigned = true;
+                zipReader = aZipReader;
+              } else if (aRv != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
+                throw "INVALID_SIGNATURE";
+              } else {
+                isSigned = false;
+                zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
+                              .createInstance(Ci.nsIZipReader);
+                zipReader.open(zipFile);
+              }
 
-            // Obtain a converter to read from a UTF-8 encoded input stream.
-            let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                            .createInstance(Ci.nsIScriptableUnicodeConverter);
-            converter.charset = "UTF-8";
+              if (!zipReader.hasEntry("manifest.webapp")) {
+                throw "MISSING_MANIFEST";
+              }
 
-            let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
-                                                                 istream.available()) || ""));
+              let istream = zipReader.getInputStream("manifest.webapp");
 
-            if (!AppsUtils.checkManifest(manifest)) {
-              throw "INVALID_MANIFEST";
-            }
+              // Obtain a converter to read from a UTF-8 encoded input stream.
+              let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                                .createInstance(Ci.nsIScriptableUnicodeConverter);
+              converter.charset = "UTF-8";
 
-            if (!AppsUtils.checkInstallAllowed(manifest, aApp.installOrigin)) {
-              throw "INSTALL_FROM_DENIED";
-            }
+              let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
+                                                                   istream.available()) || ""));
+
+              if (!AppsUtils.checkManifest(manifest)) {
+                throw "INVALID_MANIFEST";
+              }
+
+              if (!AppsUtils.checkInstallAllowed(manifest, aApp.installOrigin)) {
+                throw "INSTALL_FROM_DENIED";
+              }
+
+              let isDevMode = Services.prefs.getBoolPref("dom.mozApps.dev_mode");
+              let maxStatus = isDevMode ? Ci.nsIPrincipal.APP_STATUS_CERTIFIED
+                            : isSigned  ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
+                                        : Ci.nsIPrincipal.APP_STATUS_INSTALLED;
 
-            if (!checkAppStatus(manifest)) {
-              throw "INVALID_SECURITY_LEVEL";
-            }
+              if (AppsUtils.getAppManifestStatus(aManifest) > maxStatus) {
+                throw "INVALID_SECURITY_LEVEL";
+              }
 
-            if (aOnSuccess) {
-              aOnSuccess(id, manifest);
+              if (aOnSuccess) {
+                aOnSuccess(id, manifest);
+              }
+              delete self.downloads[aApp.manifestURL];
+            } catch (e) {
+              // Something bad happened when reading the package.
+              if (typeof e == 'object') {
+                cleanup("INVALID_PACKAGE");
+              } else {
+                cleanup(e);
+              }
+            } finally {
+              zipReader.close();
             }
-            delete self.downloads[aApp.manifestURL];
-          } catch (e) {
-            // Something bad happened when reading the package.
-            if (typeof e == 'object') {
-              cleanup("INVALID_PACKAGE");
-            } else {
-              cleanup(e);
-            }
-          } finally {
-            zipReader.close();
-          }
+          });
         });
       });
     };
 
     let deviceStorage = Services.wm.getMostRecentWindow("navigator:browser")
                                 .navigator.getDeviceStorage("apps");
     let req = deviceStorage.stat();
     req.onsuccess = req.onerror = function statResult(e) {
--- a/security/build/Makefile.in
+++ b/security/build/Makefile.in
@@ -246,16 +246,20 @@ endif
 ifdef MOZ_NO_WLZDEFS
 DEFAULT_GMAKE_FLAGS += ZDEFS_FLAG=
 endif
 ifdef MOZ_CFLAGS_NSS
 DEFAULT_GMAKE_FLAGS += XCFLAGS="$(CFLAGS)"
 DEFAULT_GMAKE_FLAGS += DARWIN_DYLIB_VERSIONS="-compatibility_version 1 -current_version 1 $(LDFLAGS)"
 endif
 
+ifdef MOZ_B2G_CERTDATA
+include $(srcdir)/b2g-certdata.mk
+endif
+
 ifdef MOZ_NSS_PATCH
 # If we're applying a patch, we'll copy the NSS source to the objdir
 # and build it from there.
 NSS_SRCDIR = $(CURDIR)/nss
 
 # This will copy and patch the NSS source for every build.
 # Since we "cp -p", it won't force rebuilds for most files, just
 # for patched files, but that's easier than trying to track
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..490b8682b75cb9b6cee68708dd25a331a055d16d
GIT binary patch
literal 964
zc$_n6Vm@Hd#I#}oGZP~d6C<MmFB_*;n@8JsUPeY%RtAH{7DH(RNjBzC7G_}~*NVj4
zf}EU0g`oWW5(Q^R1w%s-10j$kw=g$c(%I2a)<7C0$|Wp<5X~<t$S+DP$;{6)6f+P3
zNwW*{`sSDBl_X~7DTHOFmK$;#aDv1+gqeV5Ksju}OrgOBa^k#(MutFSWNctyU>qgR
zYXsyPT0*&l#hFcvO2`4o$jZRn#K_M86z5`UVq|2vwYFjB>-~>=1;jUMzc;k~Tk>;?
z+;slczh@h^yX~%$|2KWEN8n-8oCkO1|8iVhXT81d?xoKwHSN#FCSSa}ZAHMPL)Sk_
zuRF3m?COoB+d~D;d|UG2rc})$SKA5o(dTcy-n0Hv@WUWU(?zuwO2O8xFZFL6tzogg
zk{v9#)|1QU#Gm(n8QEq2%=Pptl)F56%BFh{=F56E9S>Toz1~#*$6Oxk_VXV9TD|Vx
z4|_a^d8+g~tDhTdA7^h`FFC_+XF%g_(Lb)w!mpjVbJy*jPmb9Kt5Zvv=l?&Hz4Lzg
zetqZEtF3eY@CEFzl>Gh4nX%S4k^e-{<M7B?OYHSFzf{?6>oRT1@{8NfGchwVFfO(>
zur%NY2AZreBjbM-Rs&`rWxxj#;0Fn?Ff%c+7>I%Rsvtg(0T&yGHXATGvNJQIrB7y1
zs7?>?KJkZ%skq#xXtqdx??y)3#!@|R=j)S_>{2HwPB#;-y_~}LYk%=4r@D2_?zT79
z9y>C5+s?GC1pYET89}XR4uAj1o|vyvxp!>><d{AR%>CA!y6N@y{l@=0-hQmIpRkJg
zZ`B6Zljnu+&(yx&rDeVFXR6OytwWL-j3pbYL)zxNovJq_ufdEV`Ksr`{Msu=!z{J0
z7XQ+}+n%6!Oya}r&fhmzeK0?^NBw=txh45Q%O$QIcqJ*nvF`SxdlTDvdXo68j{Z<f
zh_;FE2naja^y2pON2{;i*jHd<wr!)Jo=Mh^SJO^9{5w2#ciQp2W+ytQJxL2woG;(a
Pc-6~WXN8B7zU%}5z$aza
new file mode 100644
--- /dev/null
+++ b/security/build/b2g-certdata.mk
@@ -0,0 +1,35 @@
+# 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/.
+
+# On B2G, we need to remove the trust bits for code signing from all the
+# built-in CAs, because we are redefining the code signing bit to mean
+# "is trusted to issue certs that are trusted for signing apps," which none
+# of the normal built-in CAs are. This is a temporary hack until we can use
+# libpkix to verify the certificates. (libpkix gives the flexibility we need
+# to verify certificates using different sets of trust anchors per validation.)
+#
+# Whenever we change the B2G app signing trust anchor, we need to manually
+# update certdata-b2g.txt. To do so:
+#
+# 1. replace ./b2g-app-root-cert.der with the new DER-encoded root cert
+#
+# 2. In this directory run:
+#
+#     PATH=$NSS/bin:$NSS/lib addbuiltin -n "b2g-app-root-cert" -t ",,Cu" \
+#       < b2g-app-root-cert.der > b2g-certdata.txt
+#
+# Then, commit the changes. We don't do this step as part of the build because
+# we do not build addbuiltin as part of a Gecko build.
+
+# Distrust all existing builtin CAs for code-signing
+hacked-certdata.txt : $(srcdir)/../nss/lib/ckfw/builtins/certdata.txt
+	sed -e "s/^CKA_TRUST_CODE_SIGNING.*CKT_NSS_TRUSTED_DELEGATOR.*/CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST/" \
+			$< > $@
+
+combined-certdata.txt : hacked-certdata.txt $(srcdir)/b2g-certdata.txt
+	cat $^ > $@
+
+libs:: combined-certdata.txt
+
+DEFAULT_GMAKE_FLAGS += NSS_CERTDATA_TXT='$(CURDIR)/combined-certdata.txt'
new file mode 100644
--- /dev/null
+++ b/security/build/b2g-certdata.txt
@@ -0,0 +1,145 @@
+
+#
+# Certificate "b2g-app-root-cert"
+#
+# Issuer: C=US,ST=CA,L=Mountain View,O=Examplla Corporation,OU=Examplla CA,CN=Examplla Root CA 1
+# Serial Number: 1 (0x1)
+# Subject: C=US,ST=CA,L=Mountain View,O=Examplla Corporation,OU=Examplla CA,CN=Examplla Root CA 1
+# Not Valid Before: Wed Nov 21 23:00:03 2012
+# Not Valid After : Sat Nov 19 23:00:03 2022
+# Fingerprint (MD5): 05:14:37:02:CC:6B:3B:0F:EB:40:2D:FA:C7:CF:D3:B6
+# Fingerprint (SHA1): 33:F8:4F:CB:0C:1F:CE:35:32:6A:8C:A1:C3:CB:C9:BE:1F:B8:ED:9E
+CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
+CKA_TOKEN CK_BBOOL CK_TRUE
+CKA_PRIVATE CK_BBOOL CK_FALSE
+CKA_MODIFIABLE CK_BBOOL CK_FALSE
+CKA_LABEL UTF8 "b2g-app-root-cert"
+CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
+CKA_SUBJECT MULTILINE_OCTAL
+\060\201\204\061\033\060\031\006\003\125\004\003\023\022\105\170
+\141\155\160\154\154\141\040\122\157\157\164\040\103\101\040\061
+\061\024\060\022\006\003\125\004\013\023\013\105\170\141\155\160
+\154\154\141\040\103\101\061\035\060\033\006\003\125\004\012\023
+\024\105\170\141\155\160\154\154\141\040\103\157\162\160\157\162
+\141\164\151\157\156\061\026\060\024\006\003\125\004\007\023\015
+\115\157\165\156\164\141\151\156\040\126\151\145\167\061\013\060
+\011\006\003\125\004\010\023\002\103\101\061\013\060\011\006\003
+\125\004\006\023\002\125\123
+END
+CKA_ID UTF8 "0"
+CKA_ISSUER MULTILINE_OCTAL
+\060\201\204\061\033\060\031\006\003\125\004\003\023\022\105\170
+\141\155\160\154\154\141\040\122\157\157\164\040\103\101\040\061
+\061\024\060\022\006\003\125\004\013\023\013\105\170\141\155\160
+\154\154\141\040\103\101\061\035\060\033\006\003\125\004\012\023
+\024\105\170\141\155\160\154\154\141\040\103\157\162\160\157\162
+\141\164\151\157\156\061\026\060\024\006\003\125\004\007\023\015
+\115\157\165\156\164\141\151\156\040\126\151\145\167\061\013\060
+\011\006\003\125\004\010\023\002\103\101\061\013\060\011\006\003
+\125\004\006\023\002\125\123
+END
+CKA_SERIAL_NUMBER MULTILINE_OCTAL
+\002\001\001
+END
+CKA_VALUE MULTILINE_OCTAL
+\060\202\003\300\060\202\002\250\240\003\002\001\002\002\001\001
+\060\015\006\011\052\206\110\206\367\015\001\001\005\005\000\060
+\201\204\061\033\060\031\006\003\125\004\003\023\022\105\170\141
+\155\160\154\154\141\040\122\157\157\164\040\103\101\040\061\061
+\024\060\022\006\003\125\004\013\023\013\105\170\141\155\160\154
+\154\141\040\103\101\061\035\060\033\006\003\125\004\012\023\024
+\105\170\141\155\160\154\154\141\040\103\157\162\160\157\162\141
+\164\151\157\156\061\026\060\024\006\003\125\004\007\023\015\115
+\157\165\156\164\141\151\156\040\126\151\145\167\061\013\060\011
+\006\003\125\004\010\023\002\103\101\061\013\060\011\006\003\125
+\004\006\023\002\125\123\060\036\027\015\061\062\061\061\062\061
+\062\063\060\060\060\063\132\027\015\062\062\061\061\061\071\062
+\063\060\060\060\063\132\060\201\204\061\033\060\031\006\003\125
+\004\003\023\022\105\170\141\155\160\154\154\141\040\122\157\157
+\164\040\103\101\040\061\061\024\060\022\006\003\125\004\013\023
+\013\105\170\141\155\160\154\154\141\040\103\101\061\035\060\033
+\006\003\125\004\012\023\024\105\170\141\155\160\154\154\141\040
+\103\157\162\160\157\162\141\164\151\157\156\061\026\060\024\006
+\003\125\004\007\023\015\115\157\165\156\164\141\151\156\040\126
+\151\145\167\061\013\060\011\006\003\125\004\010\023\002\103\101
+\061\013\060\011\006\003\125\004\006\023\002\125\123\060\202\001
+\042\060\015\006\011\052\206\110\206\367\015\001\001\001\005\000
+\003\202\001\017\000\060\202\001\012\002\202\001\001\000\332\255
+\200\271\353\277\343\215\020\027\261\053\357\061\075\375\164\371
+\224\036\227\017\253\373\233\061\207\106\273\172\037\376\227\235
+\110\121\303\065\154\340\335\037\375\010\321\256\073\267\176\335
+\322\363\251\051\077\315\135\143\321\335\266\250\120\322\302\327
+\361\033\256\304\267\126\325\330\245\267\125\020\314\366\244\360
+\331\032\174\242\105\075\220\177\133\317\332\353\274\257\322\123
+\341\122\031\065\242\175\070\042\123\073\205\351\057\330\305\174
+\004\073\324\153\123\021\255\111\012\114\310\374\357\375\001\007
+\034\374\235\111\112\161\036\323\223\224\262\336\340\237\035\111
+\202\307\122\255\053\257\065\037\370\235\014\073\207\317\110\376
+\205\112\335\337\126\343\234\003\225\033\356\072\371\261\175\343
+\153\262\257\031\230\116\271\120\201\273\025\374\105\346\127\326
+\314\334\335\106\336\114\154\066\360\072\312\245\003\237\377\302
+\153\271\337\167\277\057\103\145\325\205\235\374\016\120\277\171
+\031\373\362\103\001\175\115\141\017\310\122\343\127\131\232\244
+\077\056\263\351\044\273\075\104\226\224\247\321\266\317\002\003
+\001\000\001\243\073\060\071\060\017\006\003\125\035\023\001\001
+\377\004\005\060\003\001\001\377\060\016\006\003\125\035\017\001
+\001\377\004\004\003\002\002\004\060\026\006\003\125\035\045\001
+\001\377\004\014\060\012\006\010\053\006\001\005\005\007\003\003
+\060\015\006\011\052\206\110\206\367\015\001\001\005\005\000\003
+\202\001\001\000\227\120\113\310\374\002\002\163\167\074\162\233
+\024\157\215\261\001\075\201\165\056\113\103\327\222\142\076\145
+\222\041\227\066\023\175\323\144\016\372\277\163\362\102\176\256
+\003\107\075\330\255\306\304\223\266\271\146\152\140\017\166\056
+\034\021\052\133\010\117\117\131\214\134\365\032\155\335\074\120
+\036\002\361\020\235\366\203\145\262\353\267\277\063\377\210\355
+\361\172\077\220\252\003\375\172\260\105\311\317\023\337\231\053
+\327\212\052\073\241\371\145\114\255\052\302\031\150\001\164\260
+\173\124\206\234\355\225\056\224\156\200\066\000\143\325\111\341
+\157\175\324\305\126\071\053\325\163\372\057\335\207\140\041\306
+\030\360\233\211\373\331\252\360\067\306\274\047\357\164\316\244
+\157\122\247\030\326\300\352\031\037\261\176\333\342\336\221\207
+\014\214\142\016\072\305\370\046\140\133\074\137\210\120\126\301
+\202\350\333\347\342\253\325\330\276\160\074\066\266\261\021\056
+\064\152\370\352\226\311\100\376\303\225\273\146\307\275\066\310
+\211\226\344\146\126\041\237\037\213\001\325\112\113\054\250\110
+\042\057\035\220
+END
+
+# Trust for "b2g-app-root-cert"
+# Issuer: C=US,ST=CA,L=Mountain View,O=Examplla Corporation,OU=Examplla CA,CN=Examplla Root CA 1
+# Serial Number: 1 (0x1)
+# Subject: C=US,ST=CA,L=Mountain View,O=Examplla Corporation,OU=Examplla CA,CN=Examplla Root CA 1
+# Not Valid Before: Wed Nov 21 23:00:03 2012
+# Not Valid After : Sat Nov 19 23:00:03 2022
+# Fingerprint (MD5): 05:14:37:02:CC:6B:3B:0F:EB:40:2D:FA:C7:CF:D3:B6
+# Fingerprint (SHA1): 33:F8:4F:CB:0C:1F:CE:35:32:6A:8C:A1:C3:CB:C9:BE:1F:B8:ED:9E
+CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
+CKA_TOKEN CK_BBOOL CK_TRUE
+CKA_PRIVATE CK_BBOOL CK_FALSE
+CKA_MODIFIABLE CK_BBOOL CK_FALSE
+CKA_LABEL UTF8 "b2g-app-root-cert"
+CKA_CERT_SHA1_HASH MULTILINE_OCTAL
+\063\370\117\313\014\037\316\065\062\152\214\241\303\313\311\276
+\037\270\355\236
+END
+CKA_CERT_MD5_HASH MULTILINE_OCTAL
+\005\024\067\002\314\153\073\017\353\100\055\372\307\317\323\266
+END
+CKA_ISSUER MULTILINE_OCTAL
+\060\201\204\061\033\060\031\006\003\125\004\003\023\022\105\170
+\141\155\160\154\154\141\040\122\157\157\164\040\103\101\040\061
+\061\024\060\022\006\003\125\004\013\023\013\105\170\141\155\160
+\154\154\141\040\103\101\061\035\060\033\006\003\125\004\012\023
+\024\105\170\141\155\160\154\154\141\040\103\157\162\160\157\162
+\141\164\151\157\156\061\026\060\024\006\003\125\004\007\023\015
+\115\157\165\156\164\141\151\156\040\126\151\145\167\061\013\060
+\011\006\003\125\004\010\023\002\103\101\061\013\060\011\006\003
+\125\004\006\023\002\125\123
+END
+CKA_SERIAL_NUMBER MULTILINE_OCTAL
+\002\001\001
+END
+CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
+CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
+CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
+CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps-marketplace.js
@@ -0,0 +1,56 @@
+"use strict";
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+const isB2G = ("@mozilla.org/b2g-keyboard;1" in Components.classes);
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+
+function run_test() {
+  run_next_test();
+}
+
+function check_open_result(name, expectedRv) {
+  if (expectedRv == Cr.NS_OK && !isB2G) {
+    // We do not trust the marketplace trust anchor on non-B2G builds
+
+    // XXX: NSS has many possible error codes for this, e.g.
+    // SEC_ERROR_UNTRUSTED_ISSUER and others are also reasonable. Future
+    // versions of NSS may return one of these alternate errors; in that case
+    // we need to update this test.
+    //
+    // XXX (bug 812089): Cr.NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER is undefined.
+    //
+    // XXX: Cannot use operator| instead of operator+ to combine bits because
+    // bit 31 trigger's JavaScript's crazy interpretation of the numbers as
+    // two's complement negative integers.
+    const NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER = 0x80000000 /*unsigned (1 << 31)*/
+                                            + (    (0x45 + 21) << 16)
+                                            + (-(-0x2000 + 13)      );
+    expectedRv = NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER;
+  }
+
+  return function openSignedJARFileCallback(rv, aZipReader, aSignerCert) {
+    do_print("openSignedJARFileCallback called for " + name);
+    do_check_eq(rv, expectedRv);
+    do_check_eq(aZipReader != null,  Components.isSuccessCode(expectedRv));
+    do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv));
+    run_next_test();
+  };
+}
+
+function original_app_path(test_name) {
+  return do_get_file("test_signed_apps/" + test_name + ".zip", false);
+}
+
+add_test(function () {
+  certdb.openSignedJARFileAsync(
+    original_app_path("privileged-app-test-1.0"),
+    check_open_result("privileged-app-test-1.0", Cr.NS_OK));
+});
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0f828fb858b316204aa2111c7c3f7c0f4e6158e8
GIT binary patch
literal 18161
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
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 head = 
 tail = 
 
 [test_signed_apps.js]
+[test_signed_apps-marketplace.js]
 [test_datasignatureverifier.js]
 # Bug 676972: test hangs consistently on Android
 skip-if = os == "android"
 [test_hash_algorithms.js]
 # Bug 676972: test hangs consistently on Android
 skip-if = os == "android"
 [test_hmac.js]
 # Bug 676972: test hangs consistently on Android
--- 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
@@ -516,20 +516,24 @@ this.WebConsoleUtils = {
     let result = { name: aProperty };
     result.configurable = descriptor.configurable;
     result.enumerable = descriptor.enumerable;
     result.writable = descriptor.writable;
     if (descriptor.value !== undefined) {
       result.value = this.createValueGrip(descriptor.value, aObjectWrapper);
     }
     else if (descriptor.get) {
+      let gotValue = false;
       if (this.isNativeFunction(descriptor.get)) {
-        result.value = this.createValueGrip(aObject[aProperty], aObjectWrapper);
+        try {
+          result.value = this.createValueGrip(aObject[aProperty], aObjectWrapper);
+          gotValue = true;
+        } catch (e) {}
       }
-      else {
+      if (!gotValue) {
         result.get = this.createValueGrip(descriptor.get, aObjectWrapper);
         result.set = this.createValueGrip(descriptor.set, aObjectWrapper);
       }
     }
 
     // There are cases with properties that have no value and no getter. For
     // example window.screen.width.
     if (result.value === undefined && result.get === undefined) {
@@ -1567,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
   });
--- a/toolkit/devtools/webconsole/test/Makefile.in
+++ b/toolkit/devtools/webconsole/test/Makefile.in
@@ -16,15 +16,16 @@ MOCHITEST_CHROME_FILES = \
     test_page_errors.html \
     test_consoleapi.html \
     test_jsterm.html \
     test_object_actor.html \
     test_network_get.html \
     test_network_post.html \
     test_network_longstring.html \
     test_file_uri.html \
+    test_bug819670_getter_throws.html \
     network_requests_iframe.html \
     data.json \
     data.json^headers^ \
     common.js \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_bug819670_getter_throws.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+  attachConsole([], onAttach, true);
+}
+
+function onAttach(aState, aResponse)
+{
+  onEvaluate = onEvaluate.bind(null, aState);
+  aState.client.evaluateJS("document.__proto__", onEvaluate);
+}
+
+function onEvaluate(aState, aResponse)
+{
+  checkObject(aResponse, {
+    from: aState.actor,
+    input: "document.__proto__",
+    result: {
+      type: "object",
+      actor: /[a-z]/,
+      inspectable: true,
+    },
+  });
+
+  ok(!aResponse.error, "no js error");
+  ok(!aResponse.helperResult, "no helper result");
+
+  onInspect = onInspect.bind(null, aState);
+  aState.client.inspectObjectProperties(aResponse.result.actor, onInspect);
+}
+
+function onInspect(aState, aResponse)
+{
+  ok(!aResponse.error, "no response error");
+
+  let expectedProps = [
+    { name: "ATTRIBUTE_NODE", value: 2 },
+    { name: "CDATA_SECTION_NODE", value: 4 },
+    { name: "COMMENT_NODE", value: 8 },
+    { name: "DOCUMENT_FRAGMENT_NODE", value: 11 },
+  ];
+
+  let props = aResponse.properties;
+  ok(props, "response properties available");
+
+  if (props) {
+    ok(props.length > expectedProps.length,
+       "number of enumerable properties");
+    checkObject(props, expectedProps);
+  }
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>