Bug 820524 - Debugger, Web Console and Profiler should share the debugger client even for local sessions; r=jwalker,vporof,msucan,anton
authorPanos Astithas <past@mozilla.com>
Thu, 07 Mar 2013 09:30:03 +0200
changeset 124243 ca621a4ceaa17e8d7ff4687eac0de2c24181acc3
parent 124108 ee4879719f7884e7f2e88ad466c5aa5b5c1c2c4c
child 124244 58a1cb8c0cd0cf95c9a1c52ad789b7667c87c004
push id24315
push userryanvm@gmail.com
push dateFri, 08 Mar 2013 19:30:04 +0000
treeherdermozilla-inbound@b4bfc1c0829c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker, vporof, msucan, anton
bugs820524
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 820524 - Debugger, Web Console and Profiler should share the debugger client even for local sessions; r=jwalker,vporof,msucan,anton
browser/devtools/commandline/test/browser_dbg_cmd.js
browser/devtools/debugger/DebuggerPanel.jsm
browser/devtools/debugger/debugger-controller.js
browser/devtools/framework/Target.jsm
browser/devtools/framework/Toolbox.jsm
browser/devtools/framework/connect/connect.js
browser/devtools/framework/test/browser_new_activation_workflow.js
browser/devtools/framework/test/browser_target_events.js
browser/devtools/framework/test/browser_toolbox_hosts.js
browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
browser/devtools/framework/test/browser_toolbox_window_title_changes.js
browser/devtools/inspector/InspectorPanel.jsm
browser/devtools/markupview/MarkupView.jsm
browser/devtools/profiler/ProfilerController.jsm
browser/devtools/profiler/ProfilerPanel.jsm
browser/devtools/responsivedesign/CmdResize.jsm
browser/devtools/tilt/CmdTilt.jsm
browser/devtools/webconsole/AutocompletePopup.jsm
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/WebConsolePanel.jsm
browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
browser/devtools/webconsole/webconsole.js
toolkit/devtools/debugger/server/dbg-browser-actors.js
--- a/browser/devtools/commandline/test/browser_dbg_cmd.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -42,23 +42,31 @@ function testCommands(dbg, cmd) {
                   is(output.value, "step over", "debugger stepped over");
                   cmd("dbg step out", function() {
                     is(output.value, "step out", "debugger stepped out");
                     cmd("dbg continue", function() {
                       cmd("dbg continue", function() {
                         is(output.value, "dbg continue", "debugger continued");
                         DeveloperToolbarTest.exec({
                           typed: "dbg close",
+                          completed: false,
                           blankOutput: true
                         });
 
                         let target = TargetFactory.forTab(gBrowser.selectedTab);
-                        ok(!gDevTools.getToolbox(target),
-                          "Debugger was closed.");
-                        finish();
+                        let toolbox = gDevTools.getToolbox(target);
+                        if (!toolbox) {
+                          ok(true, "Debugger was closed.");
+                          finish();
+                        } else {
+                          toolbox.on("destroyed", function () {
+                            ok(true, "Debugger was closed.");
+                            finish();
+                          });
+                        }
                       });
                     });
                   });
                 });
               });
             });
           });
         });
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -5,19 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 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/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 function DebuggerPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
 
   this._controller = this.panelWin.DebuggerController;
@@ -32,23 +34,16 @@ 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);
 
@@ -57,25 +52,35 @@ DebuggerPanel.prototype = {
                                         onDebuggerConnected, true);
       this.emit("connected");
     }.bind(this);
 
     this.panelWin.addEventListener("Debugger:Loaded", onDebuggerLoaded, true);
     this.panelWin.addEventListener("Debugger:Connected",
                                    onDebuggerConnected, true);
 
-    return deferred.promise;
+    // Remote debugging gets the debuggee from a RemoteTarget object.
+    if (this.target.isRemote) {
+      this.panelWin._remoteFlag = true;
+      return deferred.promise;
+    }
+
+    // Local debugging needs to convert the TabTarget to a RemoteTarget.
+    return this.target.makeRemote().then(function success() {
+      return deferred.promise;
+    });
   },
 
   // DevToolPanel API
   get target() this._toolbox.target,
 
   get isReady() this._isReady,
 
   destroy: function() {
+    this.emit("destroyed");
     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
@@ -94,17 +94,17 @@ let DebuggerController = {
       window.dispatchEvent("Debugger:Unloaded");
       window._isChromeDebugger && this._quitApp();
     }.bind(this));
   },
 
   /**
    * Prepares the hostname and port number for a remote debugger connection
    * and handles connection retries and timeouts.
-   *
+   * XXX: remove all this (bug 823577)
    * @return boolean
    *         True if connection should proceed normally, false otherwise.
    */
   _prepareConnection: function DC__prepareConnection() {
     // If we exceeded the total number of connection retries, bail.
     if (this._remoteConnectionTry === Prefs.remoteConnectionRetries) {
       Services.prompt.alert(null,
         L10N.getStr("remoteDebuggerPromptTitle"),
@@ -160,71 +160,59 @@ let DebuggerController = {
    * Initializes a debugger client and connects it to the debugger server,
    * wiring event handlers as necessary.
    */
   _connect: function DC__connect() {
     function callback() {
       window.dispatchEvent("Debugger:Connected");
     }
 
-    let client;
-
-    // Remote debugging gets the debuggee from a RemoteTarget object.
-    if (this._target && this._target.isRemote) {
-      window._isRemoteDebugger = true;
-
-      client = this.client = this._target.client;
+    if (!window._isChromeDebugger) {
+      let client = this.client = this._target.client;
       this._target.on("close", this._onTabDetached);
       this._target.on("navigate", this._onTabNavigated);
+      this._target.on("will-navigate", this._onTabNavigated);
 
       if (this._target.chrome) {
         let dbg = this._target.form.chromeDebugger;
         this._startChromeDebugging(client, dbg, callback);
       } else {
         this._startDebuggingTab(client, this._target.form, callback);
       }
       return;
     }
 
-    // Content or chrome debugging can connect directly to the debuggee.
-    // TODO: convert this to use a TabTarget.
-    let transport = window._isChromeDebugger
-      ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
-      : DebuggerServer.connectPipe();
+    // Chrome debugging needs to make the connection to the debuggee.
+    let transport = debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort);
 
-    client = this.client = new DebuggerClient(transport);
+    let client = this.client = new DebuggerClient(transport);
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
 
     client.connect(function(aType, aTraits) {
       client.listTabs(function(aResponse) {
-        if (window._isChromeDebugger) {
-          let dbg = aResponse.chromeDebugger;
-          this._startChromeDebugging(client, dbg, callback);
-        } else {
-          let tab = aResponse.tabs[aResponse.selected];
-          this._startDebuggingTab(client, tab, callback);
-        }
+        this._startChromeDebugging(client, aResponse.chromeDebugger, callback);
       }.bind(this));
     }.bind(this));
   },
 
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   _disconnect: function DC__disconnect() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
       return;
     }
     this.client.removeListener("tabNavigated", this._onTabNavigated);
     this.client.removeListener("tabDetached", this._onTabDetached);
 
-    // When remote debugging, the connection is closed by the RemoteTarget.
-    if (!window._isRemoteDebugger) {
+    // When debugging content or a remote instance, the connection is closed by
+    // the RemoteTarget.
+    if (window._isChromeDebugger) {
       this.client.close();
     }
 
     this.client = null;
     this.tabClient = null;
     this.activeThread = null;
   },
 
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -7,27 +7,34 @@
 this.EXPORTED_SYMBOLS = [ "TargetFactory" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
 
 const targets = new WeakMap();
 
 /**
  * Functions for creating Targets
  */
 this.TargetFactory = {
   /**
    * Construct a Target
-   * @param {XULTab} tab
-   *        The tab to use in creating a new target
+   * @param {XULTab} | {Object} tab
+   *        The tab to use in creating a new target, or an options object in
+   *        case of remote targets.
+   *
    * @return A target object
    */
   forTab: function TF_forTab(tab) {
     let target = targets.get(tab);
     if (target == null) {
       target = new TabTarget(tab);
       targets.set(tab, target);
     }
@@ -56,37 +63,17 @@ this.TargetFactory = {
     if (target == null) {
       target = new WindowTarget(window);
       targets.set(window, target);
     }
     return target;
   },
 
   /**
-   * Construct a Target for a remote global
-   * @param {Object} form
-   *        The serialized form of a debugging protocol actor.
-   * @param {DebuggerClient} client
-   *        The debuger client instance to communicate with the server.
-   * @param {boolean} chrome
-   *        A flag denoting that the debugging target is the remote process as a
-   *        whole and not a single tab.
-   * @return A target object
-   */
-  forRemote: function TF_forRemote(form, client, chrome) {
-    let target = targets.get(form);
-    if (target == null) {
-      target = new RemoteTarget(form, client, chrome);
-      targets.set(form, target);
-    }
-    return target;
-  },
-
-  /**
-   * Get all of the targets known to some browser instance (local if null)
+   * Get all of the targets known to the local browser instance
    * @return An array of target objects
    */
   allTargets: function TF_allTargets() {
     let windows = [];
     let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                        .getService(Components.interfaces.nsIWindowMediator);
     let en = wm.getXULWindowEnumerator(null);
     while (en.hasMoreElements()) {
@@ -166,66 +153,161 @@ Object.defineProperty(Target.prototype, 
 
 
 /**
  * A TabTarget represents a page living in a browser tab. Generally these will
  * be web pages served over http(s), but they don't have to be.
  */
 function TabTarget(tab) {
   EventEmitter.decorate(this);
-  this._tab = tab;
-  this._setupListeners();
+  this.destroy = this.destroy.bind(this);
+  this._handleThreadState = this._handleThreadState.bind(this);
+  this.on("thread-resumed", this._handleThreadState);
+  this.on("thread-paused", this._handleThreadState);
+  // Only real tabs need initialization here. Placeholder objects for remote
+  // targets will be initialized after a makeRemote method call.
+  if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
+    this._tab = tab;
+    this._setupListeners();
+  }
 }
 
 TabTarget.prototype = {
   _webProgressListener: null,
 
   supports: supports,
   get version() { return getVersion(); },
 
   get tab() {
     return this._tab;
   },
 
+  get form() {
+    return this._form;
+  },
+
+  get client() {
+    return this._client;
+  },
+
+  get chrome() {
+    return this._chrome;
+  },
+
   get window() {
-    return this._tab.linkedBrowser.contentWindow;
+    // Be extra careful here, since this may be called by HS_getHudByWindow
+    // during shutdown.
+    if (this._tab && this._tab.linkedBrowser) {
+      return this._tab.linkedBrowser.contentWindow;
+    }
   },
 
   get name() {
-    return this._tab.linkedBrowser.contentDocument.title;
+    return this._tab ? this._tab.linkedBrowser.contentDocument.title :
+                       this._form.title;
   },
 
   get url() {
-    return this._tab.linkedBrowser.contentDocument.location.href;
+    return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
+                       this._form.url;
   },
 
   get isRemote() {
-    return false;
+    return !this.isLocalTab;
   },
 
   get isLocalTab() {
-    return true;
+    return !!this._tab;
   },
 
   get isThreadPaused() {
     return !!this._isThreadPaused;
   },
 
   /**
+   * Adds remote protocol capabilities to the target, so that it can be used
+   * for tools that support the Remote Debugging Protocol even for local
+   * connections.
+   *
+   * @param object aOptions
+   *        An optional object containing remote connection options that is
+   *        supplied when connecting to another instance.
+   */
+  makeRemote: function TabTarget_makeRemote(aOptions) {
+    if (this._remote) {
+      return this._remote.promise;
+    }
+
+    this._remote = Promise.defer();
+
+    if (aOptions) {
+      this._form = aOptions.form;
+      this._client = aOptions.client;
+      this._chrome = aOptions.chrome;
+    } else {
+      // Since a remote protocol connection will be made, let's start the
+      // DebuggerServer here, once and for all tools.
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+
+      this._client = new DebuggerClient(DebuggerServer.connectPipe());
+      // A local TabTarget will never perform chrome debugging.
+      this._chrome = false;
+    }
+
+    this._setupRemoteListeners();
+
+    if (aOptions) {
+      // In the remote debugging case, the protocol connection will have been
+      // already initialized in the connection screen code.
+      this._remote.resolve(null);
+    } else {
+      this._client.connect(function(aType, aTraits) {
+        this._client.listTabs(function(aResponse) {
+          this._form = aResponse.tabs[aResponse.selected];
+          this._remote.resolve(null);
+        }.bind(this));
+      }.bind(this));
+    }
+
+    return this._remote.promise;
+  },
+
+  /**
    * Listen to the different events.
    */
   _setupListeners: function TabTarget__setupListeners() {
     this._webProgressListener = new TabWebProgressListener(this);
     this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
     this.tab.addEventListener("TabClose", this);
     this.tab.parentNode.addEventListener("TabSelect", this);
     this.tab.ownerDocument.defaultView.addEventListener("unload", this);
-    this._handleThreadState = this._handleThreadState.bind(this);
-    this.on("thread-resumed", this._handleThreadState);
-    this.on("thread-paused", this._handleThreadState);
+  },
+
+  /**
+   * Setup listeners for remote debugging, updating existing ones as necessary.
+   */
+  _setupRemoteListeners: function TabTarget__setupRemoteListeners() {
+    // Reset any conflicting event handlers that were set before makeRemote().
+    if (this._webProgressListener) {
+      this._webProgressListener.destroy();
+    }
+
+    this.client.addListener("tabDetached", this.destroy);
+
+    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
+      if (aPacket.state == "start") {
+        this.emit("will-navigate", aPacket);
+      } else {
+        this.emit("navigate", aPacket);
+      }
+    }.bind(this);
+    this.client.addListener("tabNavigated", this._onTabNavigated);
   },
 
   /**
    * Handle tabs events.
    */
   handleEvent: function (event) {
     switch (event.type) {
       case "TabClose":
@@ -255,38 +337,76 @@ TabTarget.prototype = {
         break;
     }
   },
 
   /**
    * Target is not alive anymore.
    */
   destroy: function() {
-    if (!this._destroyed) {
-      this._destroyed = true;
+    // 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();
+
+    // Before taking any action, notify listeners that destruction is imminent.
+    this.emit("close");
 
-      this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
-      this._webProgressListener.target = null;
-      this._webProgressListener = null;
-      this.tab.ownerDocument.defaultView.removeEventListener("unload", this);
-      this.tab.removeEventListener("TabClose", this);
-      this.tab.parentNode.removeEventListener("TabSelect", this);
-      this.off("thread-resumed", this._handleThreadState);
-      this.off("thread-paused", this._handleThreadState);
-      this.emit("close");
+    // First of all, do cleanup tasks that pertain to both remoted and
+    // non-remoted targets.
+    this.off("thread-resumed", this._handleThreadState);
+    this.off("thread-paused", this._handleThreadState);
+
+    if (this._tab) {
+      this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
+      this._tab.removeEventListener("TabClose", this);
+      this._tab.parentNode.removeEventListener("TabSelect", this);
+    }
+
+    // If this target was not remoted, the promise will be resolved before the
+    // function returns.
+    // if (!this._remote) {
+    if (this._tab && !this._client) {
+      if (this._webProgressListener) {
+        this._webProgressListener.destroy();
+      }
 
       targets.delete(this._tab);
       this._tab = null;
+      this._client = null;
+      this._form = null;
+      this._remote = null;
+
+      this._destroyer.resolve(null);
+    } else if (this._client) {
+      // If, on the other hand, this target was remoted, the promise will be
+      // resolved after the remote connection is closed.
+      this.client.removeListener("tabNavigated", this._onTabNavigated);
+      this.client.removeListener("tabDetached", this.destroy);
+
+      this._client.close(function onClosed() {
+        let key = this._tab ? this._tab : this._form;
+        targets.delete(key);
+        this._client = null;
+        this._tab = null;
+        this._form = null;
+        this._remote = null;
+
+        this._destroyer.resolve(null);
+      }.bind(this));
     }
 
-    return Promise.resolve(null);
+    return this._destroyer.promise;
   },
 
   toString: function() {
-    return 'TabTarget:' + this.tab;
+    return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
   },
 };
 
 
 /**
  * WebProgressListener for TabTarget.
  *
  * @param object aTarget
@@ -317,23 +437,34 @@ TabWebProgressListener.prototype = {
       this.target.emit("will-navigate", request);
     }
   },
 
   onProgressChange: function() {},
   onSecurityChange: function() {},
   onStatusChange: function() {},
 
-  onLocationChange: function TwPL_onLocationChange(webProgress, request, URI, flags) {
+  onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
     if (this.target &&
         !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
       let window = webProgress.DOMWindow;
       this.target.emit("navigate", window);
     }
   },
+
+  /**
+   * Destroy the progress listener instance.
+   */
+  destroy: function TWPL_destroy() {
+    if (this.target.tab) {
+      this.target.tab.linkedBrowser.removeProgressListener(this);
+    }
+    this.target._webProgressListener = null;
+    this.target = null;
+  }
 };
 
 
 /**
  * A WindowTarget represents a page living in a xul window or panel. Generally
  * these will have a chrome: URL
  */
 function WindowTarget(window) {
@@ -407,106 +538,8 @@ WindowTarget.prototype = {
 
     return Promise.resolve(null);
   },
 
   toString: function() {
     return 'WindowTarget:' + this.window;
   },
 };
-
-/**
- * A RemoteTarget represents a page living in a remote Firefox instance.
- */
-function RemoteTarget(form, client, chrome) {
-  EventEmitter.decorate(this);
-  this._client = client;
-  this._form = form;
-  this._chrome = chrome;
-  this._setupListeners();
-}
-
-RemoteTarget.prototype = {
-  supports: supports,
-  get version() getVersion(),
-
-  get isRemote() true,
-
-  get chrome() this._chrome,
-
-  get name() this._form.title,
-
-  get url() this._form.url,
-
-  get client() this._client,
-
-  get form() this._form,
-
-  get isLocalTab() false,
-
-  get isThreadPaused() !!this._isThreadPaused,
-
-  /**
-   * Listen to the different events.
-   */
-  _setupListeners: function() {
-    this.destroy = this.destroy.bind(this);
-    this.client.addListener("tabDetached", this.destroy);
-
-    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
-      if (aPacket.state == "start") {
-        this.emit("will-navigate", aPacket);
-      } else {
-        this.emit("navigate", aPacket);
-      }
-    }.bind(this);
-    this.client.addListener("tabNavigated", this._onTabNavigated);
-
-    this._handleThreadState = this._handleThreadState.bind(this);
-    this.on("thread-resumed", this._handleThreadState);
-    this.on("thread-paused", this._handleThreadState);
-  },
-
-  /**
-   * Handle script status.
-   */
-  _handleThreadState: function(event) {
-    switch (event) {
-      case "thread-resumed":
-        this._isThreadPaused = false;
-        break;
-      case "thread-paused":
-        this._isThreadPaused = true;
-        break;
-    }
-  },
-
-  /**
-   * Target is not alive anymore.
-   */
-  destroy: function RT_destroy() {
-    // 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.off("thread-resumed", this._handleThreadState);
-      this.off("thread-paused", this._handleThreadState);
-      this.emit("close");
-
-      this._destroyer.resolve(null);
-    }.bind(this));
-
-    return this._destroyer.promise;
-  },
-
-  toString: function() {
-    return 'RemoteTarget:' + this.form.actor;
-  },
-};
--- a/browser/devtools/framework/Toolbox.jsm
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -121,17 +121,17 @@ Promise.all = Promise.promised(Array);
 this.Toolbox = function Toolbox(target, selectedTool, hostType) {
   this._target = target;
   this._toolPanels = new Map();
 
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this.destroy = this.destroy.bind(this);
 
-  this._target.once("close", this.destroy);
+  this._target.on("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
   }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   let definitions = gDevTools.getToolDefinitionMap();
@@ -427,22 +427,23 @@ 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._currentToolId == id) {
-      return;
+      // Return the existing panel in order to have a consistent return value.
+      return Promise.resolve(this._toolPanels.get(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");
     }
@@ -681,32 +682,34 @@ Toolbox.prototype = {
     // method is only executed once.
     let deferred = Promise.defer();
     this._destroyer = deferred.promise;
 
     this._target.off("navigate", this._refreshHostTitle);
     this.off("select", this._refreshHostTitle);
     this.off("host-changed", this._refreshHostTitle);
 
-    let outstanding = [];
+    gDevTools.off("tool-registered", this._toolRegistered);
+    gDevTools.off("tool-unregistered", this._toolUnregistered);
 
-    // Remote targets need to be notified that the toolbox is being torn down.
-    if (this._target && this._target.isRemote) {
-      outstanding.push(this._target.destroy());
-    }
-    this._target = null;
+    let outstanding = [];
 
     for (let [id, panel] of this._toolPanels) {
       outstanding.push(panel.destroy());
     }
 
     outstanding.push(this._host.destroy());
 
-    gDevTools.off("tool-registered", this._toolRegistered);
-    gDevTools.off("tool-unregistered", this._toolUnregistered);
+    // Targets need to be notified that the toolbox is being torn down, so that
+    // remote protocol connections can be gracefully terminated.
+    if (this._target) {
+      this._target.off("close", this.destroy);
+      outstanding.push(this._target.destroy());
+    }
+    this._target = null;
 
     Promise.all(outstanding).then(function() {
       this.emit("destroyed");
       deferred.resolve();
     }.bind(this));
 
     return this._destroyer;
   }
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -159,12 +159,19 @@ function handleConnectionTimeout() {
   showError("timeout");
 }
 
 /**
  * The user clicked on one of the buttons.
  * Opens the toolbox.
  */
 function openToolbox(form, chrome=false) {
-  let target = TargetFactory.forRemote(form, gClient, chrome);
-  gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
-  window.close();
+  let options = {
+    form: form,
+    client: gClient,
+    chrome: chrome
+  };
+  let target = TargetFactory.forTab(options);
+  target.makeRemote(options).then(function() {
+    gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
+    window.close();
+  });
 }
--- a/browser/devtools/framework/test/browser_new_activation_workflow.js
+++ b/browser/devtools/framework/test/browser_new_activation_workflow.js
@@ -46,24 +46,28 @@ function selectAndCheckById(id) {
   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() {
-    gDevTools.showToolbox(target, "styleeditor").then(function() {
+    // Cannot reuse a target after it's destroyed.
+    target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
+      toolbox = aToolbox;
       is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
       finishUp();
     });
   }.bind(this));
 
   toolbox.destroy();
 }
 
 function finishUp() {
-  toolbox.destroy();
-  toolbox = null;
-  target = null;
-  gBrowser.removeCurrentTab();
-  finish();
+  toolbox.destroy().then(function() {
+    toolbox = null;
+    target = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
--- a/browser/devtools/framework/test/browser_target_events.js
+++ b/browser/devtools/framework/test/browser_target_events.js
@@ -36,17 +36,19 @@ function onHidden() {
 function onVisible() {
   ok(true, "Visible event received");
   target.once("will-navigate", onWillNavigate);
   gBrowser.contentWindow.location = "data:text/html,test navigation";
 }
 
 function onWillNavigate(event, request) {
   ok(true, "will-navigate event received");
-  target.once("navigate", onNavigate);
+  // Wait for navigation handling to complete before removing the tab, in order
+  // to avoid triggering assertions.
+  target.once("navigate", executeSoon.bind(null, onNavigate));
 }
 
 function onNavigate() {
   ok(true, "navigate event received");
   target.once("close", onClose);
   gBrowser.removeCurrentTab();
 }
 
--- a/browser/devtools/framework/test/browser_toolbox_hosts.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts.js
@@ -85,22 +85,24 @@ function testToolSelect()
 {
   // make sure we can load a tool after switching hosts
   toolbox.selectTool("inspector").then(testDestroy);
 }
 
 function testDestroy()
 {
   toolbox.destroy().then(function() {
+    target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target).then(testRememberHost);
   });
 }
 
-function testRememberHost()
+function testRememberHost(aToolbox)
 {
+  toolbox = aToolbox;
   // 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");
   ok(win, "toolbox separate window exists");
 
   cleanup();
 }
@@ -118,13 +120,14 @@ function checkToolboxLoaded(iframe)
   let tabs = iframe.contentDocument.getElementById("toolbox-tabs");
   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 = target = null;
-  gBrowser.removeCurrentTab();
-  finish();
-}
+  toolbox.destroy().then(function() {
+    DevTools = Toolbox = toolbox = target = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  });
+ }
--- a/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
@@ -61,14 +61,15 @@ function selectCB(event, id) {
 
   is(toolIDs.indexOf(id), idIndex,
      "Correct tool is selected on pressing the shortcut for " + id);
 
   testShortcuts(toolbox, idIndex + 1);
 }
 
 function tidyUp() {
-  toolbox.destroy();
-  gBrowser.removeCurrentTab();
+  toolbox.destroy().then(function() {
+    gBrowser.removeCurrentTab();
 
-  toolbox = toolIDs = idIndex = Toolbox = null;
-  finish();
+    toolbox = toolIDs = idIndex = Toolbox = null;
+    finish();
+  });
 }
--- a/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
@@ -49,33 +49,42 @@ function test() {
         target.once("navigate", function () deferred.resolve());
         gBrowser.loadURI(URL_2);
         return deferred.promise;
       })
       .then(checkTitle.bind(null, LABEL_2, URL_2, "url changed"))
 
     // destroy toolbox, create new one hosted in a window (with a
     // different tool id), and check title
-      .then(function () toolbox.destroy())
-      .then(function () gDevTools.showToolbox(target, null,
-                                              Toolbox.HostType.WINDOW))
-      .then(function (aToolbox) { toolbox = aToolbox; })
-      .then(function () toolbox.selectTool(TOOL_ID_1))
-      .then(checkTitle.bind(null, LABEL_1, URL_2,
-                            "toolbox destroyed and recreated"))
+      .then(function () {
+        // Give the tools a chance to handle the navigation event before
+        // destroying the toolbox.
+        executeSoon(function() {
+          toolbox.destroy()
+            .then(function () {
+              // After destroying the toolbox, a fresh target is required.
+              target = TargetFactory.forTab(gBrowser.selectedTab);
+              return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
+            })
+            .then(function (aToolbox) { toolbox = aToolbox; })
+            .then(function () toolbox.selectTool(TOOL_ID_1))
+            .then(checkTitle.bind(null, LABEL_1, URL_2,
+                                  "toolbox destroyed and recreated"))
 
-    // clean up
-      .then(function () toolbox.destroy())
-      .then(function () {
-        toolbox = null;
-        gBrowser.removeCurrentTab();
-        Services.prefs.clearUserPref("devtools.toolbox.host");
-        Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
-        Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
-        finish();
+            // clean up
+            .then(function () toolbox.destroy())
+            .then(function () {
+              toolbox = null;
+              gBrowser.removeCurrentTab();
+              Services.prefs.clearUserPref("devtools.toolbox.host");
+              Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
+              Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
+              finish();
+            });
+        });
       });
   });
 }
 
 function checkTitle(toolLabel, url, context) {
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
   let definitions = gDevTools.getToolDefinitionMap();
   let expectedTitle = toolLabel + " - " + url;
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -407,16 +407,17 @@ InspectorPanel.prototype = {
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
     this.lastNodemenuItem = null;
     this.nodemenu = null;
     this.searchBox = null;
     this.highlighter = null;
+    this._searchResults = null;
 
     return Promise.resolve(null);
   },
 
   /**
    * The command callback for the HTML search box. This function is
    * automatically invoked as the user is typing.
    */
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -639,17 +639,17 @@ MarkupView.prototype = {
   {
     this.undo.destroy();
     delete this.undo;
 
     this._frame.removeEventListener("focus", this._boundFocus, false);
     delete this._boundFocus;
 
     this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
-    this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
+    this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true);
     this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
     this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
     delete this._boundUpdatePreview;
 
     this._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, true);
     delete this._boundKeyDown;
 
     this._inspector.selection.off("new-node", this._boundOnNewSelection);
--- a/browser/devtools/profiler/ProfilerController.jsm
+++ b/browser/devtools/profiler/ProfilerController.jsm
@@ -18,58 +18,33 @@ XPCOMUtils.defineLazyGetter(this, "Debug
   return DebuggerServer;
 });
 
 /**
  * Object acting as a mediator between the ProfilerController and
  * DebuggerServer.
  */
 function ProfilerConnection(client) {
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
-  }
-
-  this.isRemote = true;
-
-  if (!client) {
-    let transport = DebuggerServer.connectPipe();
-    client = new DebuggerClient(transport);
-    this.isRemote = false;
-  }
-
   this.client = client;
 }
 
 ProfilerConnection.prototype = {
   actor: null,
 
   /**
    * Connects to a debugee and executes a callback when ready.
    *
    * @param function aCallback
    *        Function to be called once we're connected to the client.
    */
   connect: function PCn_connect(aCallback) {
-    let client = this.client;
-
-    let listTabs = function () {
-      client.listTabs(function (aResponse) {
-        this.actor = aResponse.profilerActor;
-        aCallback();
-      }.bind(this));
-    }.bind(this);
-
-    if (this.isRemote) {
-      return void listTabs();
-    }
-
-    client.connect(function (aType, aTraits) {
-      listTabs();
-    });
+    this.client.listTabs(function (aResponse) {
+      this.actor = aResponse.profilerActor;
+      aCallback();
+    }.bind(this));
   },
 
   /**
    * Sends a message to check if the profiler is currently active.
    *
    * @param function aCallback
    *        Function to be called once we have a response from
    *        the client. It will be called with a single argument
@@ -124,48 +99,45 @@ ProfilerConnection.prototype = {
     var message = { to: this.actor, type: "getProfile" };
     this.client.request(message, aCallback);
   },
 
   /**
    * Cleanup.
    */
   destroy: function PCn_destroy() {
-    this.client.close(function () {
-      this.client = null;
-    }.bind(this));
+    this.client = null;
   }
 };
 
 /**
  * Object defining the profiler controller components.
  */
 function ProfilerController(target) {
-  let client;
-
-  if (target.isRemote) {
-    client = target.client;
+  this.profiler = new ProfilerConnection(target.client);
+  // Chrome debugging targets have already obtained a reference to the profiler
+  // actor.
+  this._connected = !!target.chrome;
+  if (target.chrome) {
+    this.profiler.actor = target.form.profilerActor;
   }
-
-  this.profiler = new ProfilerConnection(client);
-  this._connected = false;
 }
 
 ProfilerController.prototype = {
   /**
    * Connects to the client unless we're already connected.
    *
    * @param function aCallback
    *        Function to be called once we're connected. If
    *        the controller is already connected, this function
    *        will be called immediately (synchronously).
    */
   connect: function (aCallback) {
     if (this._connected) {
-      aCallback();
+      return void aCallback();
     }
 
     this.profiler.connect(function onConnect() {
       this._connected = true;
       aCallback();
     }.bind(this));
   },
 
--- a/browser/devtools/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -4,22 +4,24 @@
 
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource:///modules/devtools/ProfilerController.jsm");
 Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 
 /**
  * An instance of a profile UI. Profile UI consists of
@@ -200,17 +202,16 @@ ProfileUI.prototype = {
  *   - profileSwitched: after user switches to a different
  *                      profile.
  */
 function ProfilerPanel(frame, toolbox) {
   this.isReady = false;
   this.window = frame.window;
   this.document = frame.document;
   this.target = toolbox.target;
-  this.controller = new ProfilerController(this.target);
 
   this.profiles = new Map();
   this._uid = 0;
 
   EventEmitter.decorate(this);
 }
 
 ProfilerPanel.prototype = {
@@ -251,33 +252,49 @@ ProfilerPanel.prototype = {
 
   /**
    * Open a debug connection and, on success, switch to the newly created
    * profile.
    *
    * @return Promise
    */
   open: function PP_open() {
-    let deferred = Promise.defer();
+    let promise;
+    // Local profiling needs to make the target remote.
+    if (!this.target.isRemote) {
+      promise = this.target.makeRemote();
+    } else {
+      promise = Promise.resolve(this.target);
+    }
 
-    this.controller.connect(function onConnect() {
-      let create = this.document.getElementById("profiler-create");
-      create.addEventListener("click", this.createProfile.bind(this), false);
-      create.removeAttribute("disabled");
+    return promise
+      .then(function(target) {
+        let deferred = Promise.defer();
+        this.controller = new ProfilerController(this.target);
 
-      let profile = this.createProfile();
-      this.switchToProfile(profile, function () {
-        this.isReady = true;
-        this.emit("ready");
+        this.controller.connect(function onConnect() {
+          let create = this.document.getElementById("profiler-create");
+          create.addEventListener("click", this.createProfile.bind(this), false);
+          create.removeAttribute("disabled");
+
+          let profile = this.createProfile();
+          this.switchToProfile(profile, function () {
+            this.isReady = true;
+            this.emit("ready");
 
-        deferred.resolve(this);
+            deferred.resolve(this);
+          }.bind(this))
+        }.bind(this));
+
+        return deferred.promise;
       }.bind(this))
-    }.bind(this));
-
-    return deferred.promise;
+      .then(null, function onError(reason) {
+        Cu.reportError("ProfilerPanel open failed. " +
+                       reason.error + ": " + reason.message);
+      });
   },
 
   /**
    * Creates a new profile instance (see ProfileUI) and
    * adds an appropriate item to the sidebar. Note that
    * this method doesn't automatically switch user to
    * the newly created profile, they have do to switch
    * explicitly.
--- a/browser/devtools/responsivedesign/CmdResize.jsm
+++ b/browser/devtools/responsivedesign/CmdResize.jsm
@@ -48,20 +48,22 @@ gcli.addCommand({
     },
     onChange: function(aTarget, aChangeHandler) {
       let browserWindow = aTarget.tab.ownerDocument.defaultView;
       let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
       mgr.on("on", aChangeHandler);
       mgr.on("off", aChangeHandler);
     },
     offChange: function(aTarget, aChangeHandler) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-      mgr.off("on", aChangeHandler);
-      mgr.off("off", aChangeHandler);
+      if (aTarget.tab) {
+        let browserWindow = aTarget.tab.ownerDocument.defaultView;
+        let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
+        mgr.off("on", aChangeHandler);
+        mgr.off("off", aChangeHandler);
+      }
     },
   },
   exec: gcli_cmd_resize
 });
 
 gcli.addCommand({
   name: 'resize to',
   description: gcli.lookup('resizeModeToDesc'),
--- a/browser/devtools/tilt/CmdTilt.jsm
+++ b/browser/devtools/tilt/CmdTilt.jsm
@@ -52,19 +52,21 @@ gcli.addCommand({
       return !!TiltManager.getTiltForBrowser(browserWindow).currentInstance;
     },
     onChange: function(aTarget, aChangeHandler) {
       let browserWindow = aTarget.tab.ownerDocument.defaultView;
       let tilt = TiltManager.getTiltForBrowser(browserWindow);
       tilt.on("change", aChangeHandler);
     },
     offChange: function(aTarget, aChangeHandler) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let tilt = TiltManager.getTiltForBrowser(browserWindow);
-      tilt.off("change", aChangeHandler);
+      if (aTarget.tab) {
+        let browserWindow = aTarget.tab.ownerDocument.defaultView;
+        let tilt = TiltManager.getTiltForBrowser(browserWindow);
+        tilt.off("change", aChangeHandler);
+      }
     },
   },
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     Tilt.toggle();
   }
 });
--- a/browser/devtools/webconsole/AutocompletePopup.jsm
+++ b/browser/devtools/webconsole/AutocompletePopup.jsm
@@ -220,17 +220,19 @@ AutocompletePopup.prototype = {
   /**
    * Setter for the selected index.
    *
    * @param number aIndex
    *        The number (index) of the item you want to select in the list.
    */
   set selectedIndex(aIndex) {
     this._list.selectedIndex = aIndex;
-    this._list.ensureIndexIsVisible(this._list.selectedIndex);
+    if (this._list.ensureIndexIsVisible) {
+      this._list.ensureIndexIsVisible(this._list.selectedIndex);
+    }
   },
 
   /**
    * Getter for the selected item.
    * @type object
    */
   get selectedItem() {
     return this._list.selectedItem ?
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -232,22 +232,21 @@ WebConsole.prototype = {
     let onFailure = function(aReason) {
       deferred.reject(aReason);
     };
 
     let win, doc;
     if ((win = this.iframe.contentWindow) &&
         (doc = win.document) &&
         doc.readyState == "complete") {
-      this.iframe.addEventListener("load", onIframeLoad, true);
+      initUI();
     }
     else {
-      initUI();
+      this.iframe.addEventListener("load", onIframeLoad, true);
     }
-
     return deferred.promise;
   },
 
   /**
    * Retrieve the Web Console panel title.
    *
    * @return string
    *         The Web Console panel title.
@@ -354,37 +353,35 @@ WebConsole.prototype = {
   destroy: function WC_destroy()
   {
     if (this._destroyer) {
       return this._destroyer.promise;
     }
 
     delete HUDService.hudReferences[this.hudId];
 
-    let tabWindow = this.target.isLocalTab ? this.target.window : null;
-
     this._destroyer = Promise.defer();
 
     let popupset = this.mainPopupSet;
     let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
     for (let panel of panels) {
       panel.hidePopup();
     }
 
     let onDestroy = function WC_onDestroyUI() {
       try {
+        let tabWindow = this.target.isLocalTab ? this.target.window : null;
         tabWindow && tabWindow.focus();
       }
       catch (ex) {
-        // Tab focus can fail if the tab is closed.
+        // Tab focus can fail if the tab or target is closed.
       }
 
       let id = WebConsoleUtils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-destroyed", null);
-
       this._destroyer.resolve(null);
     }.bind(this);
 
     if (this.ui) {
       this.ui.destroy().then(onDestroy);
     }
     else {
       onDestroy();
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/WebConsolePanel.jsm
@@ -5,16 +5,19 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
     "resource:///modules/HUDService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
     "resource:///modules/devtools/EventEmitter.jsm");
 
 /**
  * A DevToolPanel that controls the Web Console.
@@ -33,27 +36,41 @@ WebConsolePanel.prototype = {
    *
    * @return object
    *         A Promise that is resolved when the Web Console completes opening.
    */
   open: function WCP_open()
   {
     let parentDoc = this._toolbox.doc;
     let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
-    let promise = HUDService.openWebConsole(this.target, iframe);
+    let promise;
+
+    // Local debugging needs to make the target remote.
+    if (!this.target.isRemote) {
+      promise = this.target.makeRemote();
+    } else {
+      promise = Promise.resolve(this.target);
+    }
 
-    return promise.then(function onSuccess(aWebConsole) {
-      this.hud = aWebConsole;
-      this._isReady = true;
-      this.emit("ready");
-      return this;
-    }.bind(this), function onError(aReason) {
-      Cu.reportError("WebConsolePanel open failed. " +
-                     aReason.error + ": " + aReason.message);
-    });
+    return promise
+      .then(function(aTarget) {
+        this._frameWindow._remoteTarget = aTarget;
+        return HUDService.openWebConsole(this.target, iframe);
+      }.bind(this))
+      .then(function onSuccess(aWebConsole) {
+        this.hud = aWebConsole;
+        this._isReady = true;
+        this.emit("ready");
+        return this;
+      }.bind(this), function onError(aReason) {
+        let msg = "WebConsolePanel open failed. " +
+                  aReason.error + ": " + aReason.message;
+        dump(msg + "\n");
+        Cu.reportError(msg);
+      });
   },
 
   get target() this._toolbox.target,
 
   _isReady: false,
   get isReady() this._isReady,
 
   destroy: function WCP_destroy()
--- 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
@@ -72,31 +72,31 @@ function tab2Loaded(aEvent) {
     }
   }
 
   function closeConsoles() {
     Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
 
     try {
       let target1 = TargetFactory.forTab(tab1);
-      gDevTools.closeToolbox(target1);
+      gDevTools.closeToolbox(target1).then(function() {
+        try {
+          let target2 = TargetFactory.forTab(tab2);
+          gDevTools.closeToolbox(target2);
+        }
+        catch (ex) {
+          ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
+          noErrors = false;
+        }
+      });
     }
     catch (ex) {
       ok(false, "gDevTools.closeToolbox(target1) exception: " + ex);
       noErrors = false;
     }
-
-    try {
-      let target2 = TargetFactory.forTab(tab2);
-      gDevTools.closeToolbox(target2);
-    }
-    catch (ex) {
-      ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
-      noErrors = false;
-    }
   }
 
   function testEnd() {
     ok(noErrors, "there were no errors");
 
     Array.forEach(win1.gBrowser.tabs, function(aTab) {
       win1.gBrowser.removeTab(aTab);
     });
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -10,25 +10,16 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
-                                  "resource://gre/modules/devtools/dbg-server.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
-                                  "resource://gre/modules/devtools/dbg-client.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
-                                  "resource://gre/modules/devtools/dbg-client.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyPanel",
                                   "resource:///modules/PropertyPanel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyTreeView",
@@ -3983,17 +3974,16 @@ function WebConsoleConnectionProxy(aWebC
   this.target = aTarget;
 
   this._onPageError = this._onPageError.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
-  this._onListTabs = this._onListTabs.bind(this);
   this._onAttachTab = this._onAttachTab.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
   /**
@@ -4068,27 +4058,16 @@ WebConsoleConnectionProxy.prototype = {
    * Tells if the window.console object of the remote web page is the native
    * object or not.
    * @private
    * @type boolean
    */
   _hasNativeConsoleAPI: false,
 
   /**
-   * Initialize the debugger server.
-   */
-  initServer: function WCCP_initServer()
-  {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-  },
-
-  /**
    * Initialize a debugger client and connect it to the debugger server.
    *
    * @return object
    *         A Promise object that is resolved/rejected based on the success of
    *         the connection initialization.
    */
   connect: function WCCP_connect()
   {
@@ -4106,49 +4085,33 @@ WebConsoleConnectionProxy.prototype = {
     let promise = this._connectDefer.promise;
     promise.then(function _onSucess() {
       this._connectTimer.cancel();
       this._connectTimer = null;
     }.bind(this), function _onFailure() {
       this._connectTimer = null;
     }.bind(this));
 
-    // TODO: convert the non-remote path to use the target API as well.
-    let transport, client;
-    if (this.target.isRemote) {
-      client = this.client = this.target.client;
-    }
-    else {
-      this.initServer();
-      transport = DebuggerServer.connectPipe();
-      client = this.client = new DebuggerClient(transport);
-    }
+    let client = this.client = this.target.client;
 
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("tabNavigated", this._onTabNavigated);
 
-    if (this.target.isRemote) {
-      if (!this.target.chrome) {
-        // target.form is a TabActor grip
-        this._attachTab(this.target.form);
-      }
-      else {
-        // target.form is a RootActor grip
-        this._consoleActor = this.target.form.consoleActor;
-        this._attachConsole();
-      }
+    if (!this.target.chrome) {
+      // target.form is a TabActor grip
+      this._attachTab(this.target.form);
     }
     else {
-      client.connect(function(aType, aTraits) {
-        client.listTabs(this._onListTabs);
-      }.bind(this));
+      // target.form is a RootActor grip
+      this._consoleActor = this.target.form.consoleActor;
+      this._attachConsole();
     }
 
     return promise;
   },
 
   /**
    * Connection timeout handler.
    * @private
@@ -4159,35 +4122,16 @@ WebConsoleConnectionProxy.prototype = {
       error: "timeout",
       message: l10n.getStr("connectionTimeout"),
     };
 
     this._connectDefer.reject(error);
   },
 
   /**
-   * The "listTabs" response handler.
-   *
-   * @private
-   * @param object aResponse
-   *        The JSON response object received from the server.
-   */
-  _onListTabs: function WCCP__onListTabs(aResponse)
-  {
-    if (aResponse.error) {
-      Cu.reportError("listTabs failed: " + aResponse.error + " " +
-                     aResponse.message);
-      this._connectDefer.reject(aResponse);
-      return;
-    }
-
-    this._attachTab(aResponse.tabs[aResponse.selected]);
-  },
-
-  /**
    * Attach to the tab actor.
    *
    * @private
    * @param object aTab
    *        Grip for the tab to attach to.
    */
   _attachTab: function WCCP__attachTab(aTab)
   {
@@ -4428,60 +4372,30 @@ WebConsoleConnectionProxy.prototype = {
 
     this._disconnecter = Promise.defer();
 
     if (!this.client) {
       this._disconnecter.resolve(null);
       return this._disconnecter.promise;
     }
 
-    let onDisconnect = function() {
-      if (timer) {
-        timer.cancel();
-        timer = null;
-        this._disconnecter.resolve(null);
-      }
-    }.bind(this);
-
-    let timer = null;
-    let remoteTarget = this.target.isRemote;
-    if (!remoteTarget) {
-      timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-      timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
-    }
-
     this.client.removeListener("pageError", this._onPageError);
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
     this.client.removeListener("networkEvent", this._onNetworkEvent);
     this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("tabNavigated", this._onTabNavigated);
 
-    let client = this.client;
-
     this.client = null;
     this.webConsoleClient = null;
     this.tabClient = null;
     this.target = null;
     this.connected = false;
     this.owner = null;
-
-    if (!remoteTarget) {
-      try {
-        client.close(onDisconnect);
-      }
-      catch (ex) {
-        Cu.reportError("Web Console disconnect exception: " + ex);
-        Cu.reportError(ex.stack);
-        onDisconnect();
-      }
-    }
-    else {
-      onDisconnect();
-    }
+    this._disconnecter.resolve(null);
 
     return this._disconnecter.promise;
   },
 };
 
 function gSequenceId()
 {
   return gSequenceId.n++;
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -674,17 +674,16 @@ DebuggerProgressListener.prototype = {
         this._tabActor.threadActor.dbg.enabled = false;
         this._tabActor._pendingNavigation = aRequest;
       }
 
       this._tabActor.conn.send({
         from: this._tabActor.actorID,
         type: "tabNavigated",
         url: aRequest.URI.spec,
-        title: "",
         nativeConsoleAPI: true,
         state: "start",
       });
     } else if (isStop) {
       if (this._tabActor.threadActor.state == "running") {
         this._tabActor.threadActor.dbg.enabled = true;
       }