Bug 942756 - Unify debugger server startup: devtools and browser code. r=ochameau
☠☠ backed out by 9a6ce1646135 ☠ ☠
authorPaul Rouget <paul@mozilla.com>
Fri, 18 Apr 2014 10:45:00 +0200
changeset 199544 51a0ebd27ffbbad87ba7da0f24a30aef25af0815
parent 199414 3544b8716eb11f273ac2c3be1cb192aa62a6e613
child 199545 a048dcd963fd7933ee8811fe52cb9e6c713061d5
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs942756
milestone31.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 942756 - Unify debugger server startup: devtools and browser code. r=ochameau
browser/app/profile/firefox.js
browser/base/content/browser-sets.inc
browser/devtools/components/CommandLineHandler.js
browser/devtools/components/DebuggerServerController.js
browser/devtools/components/DevToolsComponents.manifest
browser/devtools/components/moz.build
browser/devtools/debugger/test/head.js
browser/devtools/devtools-clhandler.js
browser/devtools/devtools-clhandler.manifest
browser/devtools/framework/ToolboxProcess.jsm
browser/devtools/framework/gDevTools.jsm
browser/devtools/framework/test/browser_dynamic_tool_enabling.js
browser/devtools/framework/test/head.js
browser/devtools/framework/toolbox-options.xul
browser/devtools/moz.build
browser/devtools/profiler/test/browser_profiler_remote.js
browser/devtools/profiler/test/head.js
browser/installer/package-manifest.in
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
modules/libpref/src/init/all.js
testing/mochitest/mochitest_options.py
toolkit/devtools/DevToolsAppStartup.js
toolkit/devtools/DevToolsComponents.manifest
toolkit/devtools/apps/tests/debugger-protocol-helper.js
toolkit/devtools/gcli/commands/listen.js
toolkit/devtools/moz.build
toolkit/devtools/server/main.js
toolkit/devtools/server/moz.build
toolkit/devtools/server/nsIDebuggerServerController.idl
toolkit/devtools/server/tests/unit/head_dbg.js
toolkit/locales/en-US/chrome/global/devtools/debugger.properties
webapprt/prefs.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1261,16 +1261,17 @@ pref("devtools.responsiveUI.no-reload-no
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-enabled", true);
 pref("devtools.debugger.chrome-debugging-host", "localhost");
 pref("devtools.debugger.chrome-debugging-port", 6080);
 pref("devtools.debugger.remote-host", "localhost");
 pref("devtools.debugger.remote-timeout", 20000);
+pref("devtools.debugger.remote-enabled-pref-migrated", false);
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", true);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.tracer", false);
 
 // The default Debugger UI settings
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -100,17 +100,17 @@
     <command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
     <command id="Tools:BrowserToolbox" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
     <command id="Tools:BrowserConsole" oncommand="HUDService.toggleBrowserConsole();"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
     <command id="Tools:Eyedropper" oncommand="openEyedropper();"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
-    <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
+    <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"
       oncommand="OpenBrowserWindow({private: true});"/>
     <command id="Tools:RemoteWindow"
       oncommand="OpenBrowserWindow({remote: true});"/>
     <command id="Tools:NonRemoteWindow"
       oncommand="OpenBrowserWindow({remote: false});"/>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/components/CommandLineHandler.js
@@ -0,0 +1,76 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const kDebuggerPrefs = [
+  "devtools.debugger.chrome-enabled",
+  "devtools.chrome.enabled"
+];
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+
+function devtoolsCommandlineHandler() {
+}
+devtoolsCommandlineHandler.prototype = {
+  handle: function(cmdLine) {
+    let consoleFlag = cmdLine.handleFlag("jsconsole", false);
+    let debuggerFlag = cmdLine.handleFlag("jsdebugger", false);
+    if (consoleFlag) {
+      this.handleConsoleFlag(cmdLine);
+    }
+    if (debuggerFlag) {
+      this.handleDebuggerFlag(cmdLine);
+    }
+  },
+
+  handleConsoleFlag: function(cmdLine) {
+    let window = Services.wm.getMostRecentWindow("devtools:webconsole");
+    if (!window) {
+      let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+      // Load the browser devtools main module as the loader's main module.
+      Cu.import("resource:///modules/devtools/gDevTools.jsm");
+      let hudservice = devtools.require("devtools/webconsole/hudservice");
+      let console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
+      hudservice.toggleBrowserConsole().then(null, console.error);
+    } else {
+      window.focus(); // the Browser Console was already open
+    }
+
+    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
+      cmdLine.preventDefault = true;
+    }
+  },
+
+  handleDebuggerFlag: function(cmdLine) {
+    let remoteDebuggingEnabled = false;
+    try {
+      remoteDebuggingEnabled = kDebuggerPrefs.every((pref) => Services.prefs.getBoolPref(pref));
+    } catch (ex) {
+      Cu.reportError(ex);
+      return;
+    }
+    if (remoteDebuggingEnabled) {
+      Cu.import("resource:///modules/devtools/ToolboxProcess.jsm");
+      BrowserToolboxProcess.init();
+    } else {
+      let errorMsg = "Could not run chrome debugger! You need the following prefs " +
+                     "to be set to true: " + kDebuggerPrefs.join(", ");
+      Cu.reportError(errorMsg);
+      // Dump as well, as we're doing this from a commandline, make sure people don't miss it:
+      dump(errorMsg + "\n");
+    }
+
+    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
+      cmdLine.preventDefault = true;
+    }
+  },
+
+  helpInfo : "  -jsconsole         Open the Browser Console.\n" +
+             "  -jsdebugger        Open the Browser Toolbox.\n",
+
+  classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([devtoolsCommandlineHandler]);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/components/DebuggerServerController.js
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 { interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+const gPrefRemoteEnabled = "devtools.debugger.remote-enabled";
+const gPrefMigrated = "devtools.debugger.remote-enabled-pref-migrated";
+const gPrefShowNotifications = "devtools.debugger.show-server-notifications";
+
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+XPCOMUtils.defineLazyServiceGetter(this,
+    "Alerts",
+    "@mozilla.org/alerts-service;1", "nsIAlertsService");
+
+XPCOMUtils.defineLazyGetter(this,
+    "l10n",
+    () => Services.strings.createBundle("chrome://global/locale/devtools/debugger.properties"));
+
+function DebuggerServerController() {
+}
+
+DebuggerServerController.prototype = {
+  classID: Components.ID("{f6e8e269-ae4a-4c4a-bf80-fb4164fb072d}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDebuggerServerController, Ci.nsIObserver]),
+
+  init: function(debuggerServer) {
+
+    this.debugger = debuggerServer;
+
+    // The remote-enabled pref used to mean that Firefox was allowed
+    // to connect to a debugger server. In other products (b2g, thunderbird,
+    // fennec, metro and webrt) this pref had a different meaning: it runs a
+    // debugger server. We want Firefox Desktop to follow the same rule.
+    //
+    // We don't want to surprise users with this new behavior. So we reset
+    // the remote-enabled pref once.
+
+    if (!Services.prefs.getBoolPref(gPrefMigrated)) {
+      Services.prefs.clearUserPref(gPrefRemoteEnabled);
+      Services.prefs.setBoolPref(gPrefMigrated, true);
+    }
+
+    Services.obs.addObserver(this, "debugger-server-started", false);
+    Services.obs.addObserver(this, "debugger-server-stopped", false);
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+  },
+
+  uninit: function() {
+    this.debugger = null;
+    Services.obs.removeObserver(this, "debugger-server-started");
+    Services.obs.removeObserver(this, "debugger-server-stopped");
+    Services.obs.removeObserver(this, "xpcom-shutdown");
+  },
+
+  start: function(pathOrPort) {
+    if (!this.debugger.initialized) {
+      this.debugger.init();
+      this.debugger.addBrowserActors();
+    }
+
+    if (!pathOrPort) {
+      // If the "devtools.debugger.unix-domain-socket" pref is set, we use a unix socket.
+      // If not, we use a regular TCP socket.
+      try {
+        pathOrPort = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket");
+      } catch (e) {
+        pathOrPort = Services.prefs.getIntPref("devtools.debugger.remote-port");
+      }
+    }
+
+    this._showNotifications = Services.prefs.getBoolPref(gPrefShowNotifications);
+
+    try {
+      this.debugger.openListener(pathOrPort);
+    } catch (e if e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+      dump("Unable to start debugger server (" + pathOrPort + "): " + e + "\n");
+    }
+  },
+
+  stop: function() {
+    this.debugger.closeListener(true);
+  },
+
+  // nsIObserver
+
+  observe: function (subject, topic, data) {
+    if (topic == "xpcom-shutdown")
+      this.uninit();
+    if (topic == "debugger-server-started")
+      this._onDebuggerStarted(data);
+    if (topic == "debugger-server-stopped")
+      this._onDebuggerStopped();
+  },
+
+  _onDebuggerStarted: function(portOrPath) {
+    if (!this._showNotifications)
+      return;
+    let title = l10n.GetStringFromName("debuggerStartedAlert.title");
+    let port = Number(portOrPath);
+    let detail;
+    if (port) {
+      detail = l10n.formatStringFromName("debuggerStartedAlert.detailPort", [portOrPath], 1);
+    } else {
+      detail = l10n.formatStringFromName("debuggerStartedAlert.detailPath", [portOrPath], 1);
+    }
+    Alerts.showAlertNotification(null, title, detail, false, "", function(){});
+  },
+
+  _onDebuggerStopped: function() {
+    if (!this._showNotifications)
+      return;
+    let title = l10n.GetStringFromName("debuggerStopped.title");
+    Alerts.showAlertNotification(null, title, null, false, "", function(){});
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DebuggerServerController]);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/components/DevToolsComponents.manifest
@@ -0,0 +1,7 @@
+# CommandLineHandler.js
+component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} CommandLineHandler.js
+contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
+
+# DebuggerServerController.js
+component {f6e8e269-ae4a-4c4a-bf80-fb4164fb072d} DebuggerServerController.js
+contract @mozilla.org/devtools/DebuggerServerController;1 {f6e8e269-ae4a-4c4a-bf80-fb4164fb072d}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/components/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_COMPONENTS += [
+    'CommandLineHandler.js',
+    'DebuggerServerController.js',
+    'DevToolsComponents.manifest',
+]
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -7,16 +7,19 @@ const { classes: Cc, interfaces: Ci, uti
 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Disable logging for faster test runs. Set this pref to true if you want to
 // debug a test in your try runs. Both the debugger server and frontend will
 // be affected by this pref.
 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
+// Disable notifications (to avoid "unknown window" errors)
+Services.prefs.setBoolPref("devtools.debugger.show-server-notifications", false);
+
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { require } = devtools;
 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
@@ -33,16 +36,17 @@ SimpleTest.registerCleanupFunction(() =>
 });
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 registerCleanupFunction(function() {
   info("finish() was called, cleaning up...");
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+  Services.prefs.clearUserPref("devtools.debugger.show-server-notifications");
 
   // Properly shut down the server to avoid memory leaks.
   DebuggerServer.destroy();
 
   // Debugger tests use a lot of memory, so force a GC to help fragmentation.
   info("Forcing GC after debugger test.");
   Cu.forceGC();
 });
deleted file mode 100644
--- a/browser/devtools/devtools-clhandler.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-const kDebuggerPrefs = [
-  "devtools.debugger.remote-enabled",
-  "devtools.debugger.chrome-enabled",
-  "devtools.chrome.enabled"
-];
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-
-function devtoolsCommandlineHandler() {
-}
-devtoolsCommandlineHandler.prototype = {
-  handle: function(cmdLine) {
-    let consoleFlag = cmdLine.handleFlag("jsconsole", false);
-    let debuggerFlag = cmdLine.handleFlag("jsdebugger", false);
-    if (consoleFlag) {
-      this.handleConsoleFlag(cmdLine);
-    }
-    if (debuggerFlag) {
-      this.handleDebuggerFlag(cmdLine);
-    }
-  },
-
-  handleConsoleFlag: function(cmdLine) {
-    let window = Services.wm.getMostRecentWindow("devtools:webconsole");
-    if (!window) {
-      let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
-      // Load the browser devtools main module as the loader's main module.
-      Cu.import("resource:///modules/devtools/gDevTools.jsm");
-      let hudservice = devtools.require("devtools/webconsole/hudservice");
-      let console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
-      hudservice.toggleBrowserConsole().then(null, console.error);
-    } else {
-      window.focus(); // the Browser Console was already open
-    }
-
-    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
-      cmdLine.preventDefault = true;
-    }
-  },
-
-  handleDebuggerFlag: function(cmdLine) {
-    let remoteDebuggingEnabled = false;
-    try {
-      remoteDebuggingEnabled = kDebuggerPrefs.every((pref) => Services.prefs.getBoolPref(pref));
-    } catch (ex) {
-      Cu.reportError(ex);
-      return;
-    }
-    if (remoteDebuggingEnabled) {
-      Cu.import("resource:///modules/devtools/ToolboxProcess.jsm");
-      BrowserToolboxProcess.init();
-    } else {
-      let errorMsg = "Could not run chrome debugger! You need the following prefs " +
-                     "to be set to true: " + kDebuggerPrefs.join(", ");
-      Cu.reportError(errorMsg);
-      // Dump as well, as we're doing this from a commandline, make sure people don't miss it:
-      dump(errorMsg + "\n");
-    }
-
-    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
-      cmdLine.preventDefault = true;
-    }
-  },
-
-  helpInfo : "  -jsconsole         Open the Browser Console.\n" +
-             "  -jsdebugger        Open the Browser Toolbox.\n",
-
-  classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([devtoolsCommandlineHandler]);
deleted file mode 100644
--- a/browser/devtools/devtools-clhandler.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-clhandler.js
-contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
--- a/browser/devtools/framework/ToolboxProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -122,23 +122,17 @@ BrowserToolboxProcess.prototype = {
       this.loader.main("devtools/server/main");
       this.debuggerServer = this.loader.DebuggerServer;
       dumpn("Created a separate loader instance for the DebuggerServer.");
 
       // Forward interesting events.
       this.debuggerServer.on("connectionchange", this.emit.bind(this));
     }
 
-    if (!this.debuggerServer.initialized) {
-      this.debuggerServer.init();
-      this.debuggerServer.addBrowserActors();
-      dumpn("initialized and added the browser actors for the DebuggerServer.");
-    }
-
-    this.debuggerServer.openListener(Prefs.chromeDebuggingPort);
+    this.debuggerServer.controller.start(Prefs.chromeDebuggingPort);
 
     dumpn("Finished initializing the chrome toolbox server.");
     dumpn("Started listening on port: " + Prefs.chromeDebuggingPort);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -434,28 +434,23 @@ let gDevToolsBrowser = {
       win.DeveloperToolbar.show(false);
     }
 
     // Enable App Manager?
     let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled");
     toggleCmd("Tools:DevAppMgr", appMgrEnabled);
 
     // Enable Browser Toolbox?
-    let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
-    let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
-    let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled &&
+    let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled") &&
                         Services.prefs.getBoolPref("devtools.debugger.chrome-enabled");
-    toggleCmd("Tools:BrowserToolbox", remoteEnabled);
+    toggleCmd("Tools:BrowserToolbox", chromeEnabled);
 
     // Enable Error Console?
     let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
     toggleCmd("Tools:ErrorConsole", consoleEnabled);
-
-    // Enable DevTools connection screen, if the preference allows this.
-    toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled);
   },
 
   observe: function(subject, topic, prefName) {
     if (prefName.endsWith("enabled")) {
       for (let win of this._trackedBrowserWindows) {
         this.updateCommandAvailability(win);
       }
     }
--- a/browser/devtools/framework/test/browser_dynamic_tool_enabling.js
+++ b/browser/devtools/framework/test/browser_dynamic_tool_enabling.js
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that toggling prefs immediately (de)activates the relevant menuitem
 
 let gItemsToTest = {
   "menu_devToolbar": "devtools.toolbar.enabled",
   "menu_devAppMgr": "devtools.appmanager.enabled",
-  "menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled", "devtools.debugger.chrome-enabled"],
+  "menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.chrome-enabled"],
   "javascriptConsole": "devtools.errorconsole.enabled",
-  "menu_devtools_connect": "devtools.debugger.remote-enabled",
+  "menu_devtools_connect": [],
 };
 
 function expectedAttributeValueFromPrefs(prefs) {
   return prefs.every((pref) => Services.prefs.getBoolPref(pref)) ?
          "" : "true";
 }
 
 function checkItem(el, prefs) {
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -8,16 +8,20 @@ let tempScope = {};
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 Components.utils.import("resource://gre/modules/Promise.jsm", tempScope);
 let promise = tempScope.Promise;
 
 let {devtools} = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
+let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
+// Disable notifications (to avoid "unknown window" errors)
+Services.prefs.setBoolPref("devtools.debugger.show-server-notifications", false);
+
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
 /**
  * Open a new tab at a URL and call a callback on load
  */
@@ -49,16 +53,17 @@ function addTab(aURL, aCallback)
   browser.addEventListener("load", onTabLoad, true);
   return deferred.promise;
 }
 
 registerCleanupFunction(function tearDown() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
+  Services.prefs.clearUserPref("devtools.debugger.show-server-notifications");
 });
 
 function synthesizeKeyFromKeyTag(aKeyId, document) {
   let key = document.getElementById(aKeyId);
   isnot(key, null, "Successfully retrieved the <key> node");
 
   let modifiersAttr = key.getAttribute("modifiers");
 
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -92,17 +92,17 @@
                     tooltiptext="&options.disableJavaScript.tooltip;"/>
           <hbox class="hidden-labels-box">
             <checkbox label="&options.enableChrome.label4;"
                       tooltiptext="&options.enableChrome.tooltip2;"
                       data-pref="devtools.chrome.enabled"/>
           </hbox>
           <hbox class="hidden-labels-box">
             <checkbox label="&options.enableRemote.label3;"
-                      tooltiptext="&options.enableRemote.tooltip;"
+                      tooltiptext="&options.enableRemote.tooltip2;"
                       data-pref="devtools.debugger.remote-enabled"/>
           </hbox>
           <label class="options-citation-label"
                  value="&options.context.triggersPageRefresh;"/>
         </vbox>
       </vbox>
     </hbox>
   </hbox>
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 DIRS += [
     'app-manager',
     'canvasdebugger',
     'commandline',
+    'components',
     'debugger',
     'eyedropper',
     'fontinspector',
     'framework',
     'inspector',
     'layoutview',
     'markupview',
     'netmonitor',
@@ -24,14 +25,9 @@ DIRS += [
     'sourceeditor',
     'styleeditor',
     'styleinspector',
     'tilt',
     'webaudioeditor',
     'webconsole',
 ]
 
-EXTRA_COMPONENTS += [
-    'devtools-clhandler.js',
-    'devtools-clhandler.manifest',
-]
-
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/devtools/profiler/test/browser_profiler_remote.js
+++ b/browser/devtools/profiler/test/browser_profiler_remote.js
@@ -12,17 +12,16 @@ Cu.import("resource://gre/modules/devtoo
 let DebuggerClient = temp.DebuggerClient;
 let debuggerSocketConnect = temp.debuggerSocketConnect;
 
 Cu.import("resource:///modules/devtools/profiler/controller.js", temp);
 let ProfilerController = temp.ProfilerController;
 
 function test() {
   waitForExplicitFinish();
-  Services.prefs.setBoolPref(REMOTE_ENABLED, true);
 
   loadTab(URL, function onTabLoad(tab, browser) {
     DebuggerServer.init(function () true);
     DebuggerServer.addBrowserActors();
     is(DebuggerServer._socketConnections, 0);
 
     DebuggerServer.openListener(2929);
     is(DebuggerServer._socketConnections, 1);
--- a/browser/devtools/profiler/test/head.js
+++ b/browser/devtools/profiler/test/head.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let temp = {};
 
 const PROFILER_ENABLED = "devtools.profiler.enabled";
-const REMOTE_ENABLED = "devtools.debugger.remote-enabled";
 const SHOW_PLATFORM_DATA = "devtools.profiler.ui.show-platform-data";
+const SHOW_NOTIFICATIONS = "devtools.debugger.show-server-notifications";
 const PROFILE_IDLE = 0;
 const PROFILE_RUNNING = 1;
 const PROFILE_COMPLETED = 2;
 
 Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
 let gDevTools = temp.gDevTools;
 
 Cu.import("resource://gre/modules/devtools/Loader.jsm", temp);
@@ -26,18 +26,18 @@ Services.scriptloader.loadSubScript(test
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
 registerCleanupFunction(function () {
   helpers = null;
   Services.prefs.clearUserPref(PROFILER_ENABLED);
-  Services.prefs.clearUserPref(REMOTE_ENABLED);
   Services.prefs.clearUserPref(SHOW_PLATFORM_DATA);
+  Services.prefs.clearUserPref(SHOW_NOTIFICATIONS);
   DebuggerServer.destroy();
 
   // These tests use a lot of memory due to GL contexts, so force a GC to help
   // fragmentation.
   info("Forcing GC after profiler test.");
   Cu.forceGC();
 });
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -361,18 +361,16 @@
 @BINPATH@/browser/components/nsBrowserContentHandler.js
 @BINPATH@/browser/components/nsBrowserGlue.js
 @BINPATH@/browser/components/nsSetDefaultBrowser.manifest
 @BINPATH@/browser/components/nsSetDefaultBrowser.js
 @BINPATH@/browser/components/BrowserDownloads.manifest
 @BINPATH@/browser/components/DownloadsStartup.js
 @BINPATH@/browser/components/DownloadsUI.js
 @BINPATH@/browser/components/BrowserPlaces.manifest
-@BINPATH@/browser/components/devtools-clhandler.manifest
-@BINPATH@/browser/components/devtools-clhandler.js
 @BINPATH@/browser/components/Experiments.manifest
 @BINPATH@/browser/components/ExperimentsService.js
 @BINPATH@/components/Downloads.manifest
 @BINPATH@/components/DownloadLegacy.js
 @BINPATH@/components/BrowserPageThumbs.manifest
 @BINPATH@/components/crashmonitor.manifest
 @BINPATH@/components/nsCrashMonitor.js
 @BINPATH@/components/SiteSpecificUserAgent.js
@@ -570,21 +568,30 @@
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
 
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
 
+
 #ifdef MOZ_DEBUG
 @BINPATH@/components/TestInterfaceJS.js
 @BINPATH@/components/TestInterfaceJS.manifest
 #endif
 
+; DevTools browser
+@BINPATH@/browser/components/DevToolsComponents.manifest
+@BINPATH@/browser/components/DebuggerServerController.js
+@BINPATH@/browser/components/CommandLineHandler.js
+; DevTools toolkit
+@BINPATH@/components/DevToolsComponents.manifest
+@BINPATH@/components/DevToolsAppStartup.js
+
 ; Modules
 @BINPATH@/browser/modules/*
 @BINPATH@/modules/*
 
 ; Safe Browsing
 #ifdef MOZ_URL_CLASSIFIER
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -1346,28 +1346,24 @@ listenDesc=Open a remote debug port
 # LOCALIZATION NOTE (listenManual2) A longer description of the 'listen'
 # command.
 listenManual2=%1$S can allow remote debugging over a TCP/IP connection. For security reasons this is turned off by default, but can be enabled using this command.
 
 # LOCALIZATION NOTE (listenPortDesc) A very short string used to describe the
 # function of 'port' parameter to the 'listen' command.
 listenPortDesc=The TCP port to listen on
 
-# LOCALIZATION NOTE (listenDisabledOutput) Text of a message output during the
+# LOCALIZATION NOTE (listenFailed) Text of a failure message during the
 # execution of the 'listen' command.
-listenDisabledOutput=Listen is disabled by the devtools.debugger.remote-enabled preference
+listenFailed=Listen failed.
 
 # LOCALIZATION NOTE (listenInitOutput) Text of a message output during the
 # execution of the 'listen' command. %1$S is a port number
 listenInitOutput=Listening on port %1$S
 
-# LOCALIZATION NOTE (listenNoInitOutput) Text of a message output during the
-# execution of the 'listen' command.
-listenNoInitOutput=DebuggerServer not initialized
-
 # LOCALIZATION NOTE (mediaDesc, mediaEmulateDesc, mediaEmulateManual,
 # mediaEmulateType, mediaResetDesc, mediaResetManual) These strings describe
 # the 'media' commands and all available parameters.
 mediaDesc=CSS media type emulation
 mediaEmulateDesc=Emulate a specified CSS media type
 mediaEmulateManual=View the document as if rendered on a device supporting the given media type, with the relevant CSS rules applied.
 mediaEmulateType=The media type to emulate
 mediaResetDesc=Stop emulating a CSS media type
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -62,17 +62,17 @@
   -  boolean preference in about:config, in the options panel. -->
 <!ENTITY options.enableChrome.label4    "Enable chrome and addon debugging">
 <!ENTITY options.enableChrome.tooltip2  "Turning this option on will allow you to use various developer tools in browser context and debug addons from the Add-On Manager">
 
 <!-- LOCALIZATION NOTE (options.enableRemote.label3): This is the label for the
   -  checkbox that toggles remote debugging, i.e. devtools.debugger.remote-enabled
   -  boolean preference in about:config, in the options panel. -->
 <!ENTITY options.enableRemote.label3    "Enable remote debugging">
-<!ENTITY options.enableRemote.tooltip   "Turning this option on will allow the developer tools to debug remote Firefox instance like Firefox OS">
+<!ENTITY options.enableRemote.tooltip2  "Turning this option on will start a debugger server and allow other instances of Firefox to debug this instance">
 
 <!-- LOCALIZATION NOTE (options.disableJavaScript.label,
   -  options.disableJavaScript.tooltip): This is the options panel label and
   -  tooltip for the checkbox that toggles JavaScript on or off. -->
 <!ENTITY options.disableJavaScript.label     "Disable JavaScript *">
 <!ENTITY options.disableJavaScript.tooltip   "Turning this option on will disable JavaScript for the current tab. If the tab or the toolbox is closed then this setting will be forgotten.">
 
 <!-- LOCALIZATION NOTE (options.disableCache.label,
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -563,17 +563,20 @@ pref("toolkit.asyncshutdown.timeout.cras
 // Enable deprecation warnings.
 pref("devtools.errorconsole.deprecation_warnings", true);
 
 // Disable debugging chrome
 pref("devtools.chrome.enabled", false);
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
-// Disable remote debugging connections
+// Show notifications when server starts/stops
+pref("devtools.debugger.show-server-notifications", true);
+// Run a server debugger. Changing this pref dynamically will
+// stop / start the server debugger.
 pref("devtools.debugger.remote-enabled", false);
 pref("devtools.debugger.remote-port", 6000);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 // Display a prompt when a new connection starts to accept/reject it
 pref("devtools.debugger.prompt-connection", true);
 // Block tools from seeing / interacting with certified apps
 pref("devtools.debugger.forbid-certified-apps", true);
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -510,17 +510,16 @@ class MochitestOptions(optparse.OptionPa
         if options.manifestFile and options.testManifest:
             self.error("Unable to support both --manifest and --test-manifest/--run-only-tests at the same time")
 
         if options.webapprtContent and options.webapprtChrome:
             self.error("Only one of --webapprt-content and --webapprt-chrome may be given.")
 
         if options.jsdebugger:
             options.extraPrefs += [
-                "devtools.debugger.remote-enabled=true",
                 "devtools.debugger.chrome-enabled=true",
                 "devtools.chrome.enabled=true",
                 "devtools.debugger.prompt-connection=false"
             ]
             options.autorun = False
 
         if options.debugOnFailure and not options.jsdebugger:
           self.error("--debug-on-failure should be used together with --jsdebugger.")
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/DevToolsAppStartup.js
@@ -0,0 +1,133 @@
+/* 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/. */
+
+/**
+ * This component handles the -start-debugger-server command line
+ * option. It also starts and stops the debugger server when the
+ * devtools.debugger.remote-enable pref changes.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
+const UNIX_SOCKET_PREF = "devtools.debugger.unix-domain-socket";
+const REMOTE_PORT_PREF = "devtools.debugger.remote-port";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+    "DebuggerServer",
+    "resource://gre/modules/devtools/dbg-server.jsm");
+
+function DevToolsAppStartup() {
+}
+
+DevToolsAppStartup.prototype = {
+  classID: Components.ID("{9ba9bbe7-5866-46f1-bea6-3299066b7933}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, Ci.nsIObserver]),
+
+  get dbgPortOrPath() {
+    if (!this._dbgPortOrPath) {
+      // set default dbgPortOrPath value from preferences:
+      try {
+        this._dbgPortOrPath = Services.prefs.getCharPref(UNIX_SOCKET_PREF);
+      } catch(e) {
+        try {
+          this._dbgPortOrPath = Services.prefs.getIntPref(REMOTE_PORT_PREF);
+        } catch(e) {}
+      }
+    }
+    return this._dbgPortOrPath;
+  },
+
+  set dbgPortOrPath(value) {
+    this._dbgPortOrPath = value;
+  },
+
+  // nsICommandLineHandler
+
+  get helpInfo() {
+    let str = "";
+
+    // Starting the debugger is handled on the app side (not in /toolkit/).
+    // If the app didn't expose a debugger controller component, we don't
+    // support the -start-debugger-server option.
+
+    if (DebuggerServer.controller) {
+      str += "  -start-debugger-server [<port or unix domain socket path>]";
+      if (this.dbgPortOrPath) {
+        str += " (default: " + this.dbgPortOrPath + ")";
+      }
+      str += "\n";
+    }
+
+    return str;
+  },
+
+  handle: function(cmdLine) {
+    if (!DebuggerServer.controller) {
+      // This app doesn't expose a debugger controller.
+      // We can't handle the -start-debugger-server option
+      // or the remote-enable pref.
+      return;
+    }
+
+    let startDebuggerServerBecauseCmdLine = false;
+
+    try {
+      // Returns null if the argument was not specified. Throws
+      // NS_ERROR_INVALID_ARG if there is no parameter specified (because
+      // it was the last argument or the next argument starts with '-').
+      // However, someone could still explicitly pass an empty argument.
+      let param = cmdLine.handleFlagWithParam("start-debugger-server", false);
+      if (param) {
+        startDebuggerServerBecauseCmdLine = true;
+        this.dbgPortOrPath = param;
+      }
+    } catch(e) {
+      startDebuggerServerBecauseCmdLine = true;
+    }
+
+    // App has started and we handled the command line options (if any).
+    // Time to start the debugger if needed and observe the remote-enable
+    // pref.
+
+    if (startDebuggerServerBecauseCmdLine ||
+        Services.prefs.getBoolPref(REMOTE_ENABLED_PREF)) {
+      if (this.dbgPortOrPath) {
+        DebuggerServer.controller.start(this.dbgPortOrPath);
+      } else {
+        dump("Can't start debugger: no port or path specified\n");
+      }
+    }
+
+    Services.prefs.addObserver(REMOTE_ENABLED_PREF, this, false);
+    Services.prefs.addObserver(UNIX_SOCKET_PREF, this, false);
+    Services.prefs.addObserver(REMOTE_PORT_PREF, this, false);
+  },
+
+  // nsIObserver
+
+  observe: function (subject, topic, data) {
+    if (topic == "nsPref:changed") {
+      switch (data) {
+        case REMOTE_ENABLED_PREF:
+          if (Services.prefs.getBoolPref(data)) {
+            DebuggerServer.controller.start(this.dbgPortOrPath);
+          } else {
+            DebuggerServer.controller.stop();
+          }
+        break;
+        case UNIX_SOCKET_PREF:
+        case REMOTE_PORT_PREF:
+          // reset dbgPortOrPath value
+          this.dbgPortOrPath = null;
+        break;
+      }
+    }
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DevToolsAppStartup]);
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/DevToolsComponents.manifest
@@ -0,0 +1,3 @@
+component {9ba9bbe7-5866-46f1-bea6-3299066b7933} DevToolsAppStartup.js
+contract @mozilla.org/toolkit/devtools-app-startup;1 {9ba9bbe7-5866-46f1-bea6-3299066b7933}
+category command-line-handler DevToolsAppStartup @mozilla.org/toolkit/devtools-app-startup;1
--- a/toolkit/devtools/apps/tests/debugger-protocol-helper.js
+++ b/toolkit/devtools/apps/tests/debugger-protocol-helper.js
@@ -9,41 +9,26 @@ const { DebuggerServer } = Cu.import("re
 const { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
 const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm");
 const { Services } = Cu.import("resource://gre/modules/Services.jsm");
 
 let gClient, gActor;
 
 function connect(onDone) {
-
-  if (Services.appinfo.name == "B2G") {
-    // On b2g, we try to exercice the code that launches the production debugger server
-    let settingsService = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
-    settingsService.createLock().set("devtools.debugger.remote-enabled", true, null);
-    // We can't use `set` callback as it is fired before shell.js code listening for this setting
-    // is actually called. Same thing applies to mozsettings-changed obs notification.
-    // So listen to a custom event until bug 942756 lands
-    let observer = {
-      observe: function (subject, topic, data) {
-        Services.obs.removeObserver(observer, "debugger-server-started");
-        let transport = debuggerSocketConnect("127.0.0.1", 6000);
-        startClient(transport, onDone);
-      }
-    };
-    Services.obs.addObserver(observer, "debugger-server-started", false);
-  } else {
-    // Initialize a loopback remote protocol connection
-    DebuggerServer.init(function () { return true; });
-    // We need to register browser actors to have `listTabs` working
-    // and also have a root actor
-    DebuggerServer.addBrowserActors();
-    let transport = DebuggerServer.connectPipe();
-    startClient(transport, onDone);
-  }
+  Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
+  let observer = {
+    observe: function (subject, topic, data) {
+      Services.obs.removeObserver(observer, "debugger-server-started");
+      let transport = debuggerSocketConnect("127.0.0.1", 6000);
+      startClient(transport, onDone);
+    }
+  };
+  Services.obs.addObserver(observer, "debugger-server-started", false);
+  DebuggerServer.controller.start(6000);
 }
 
 function startClient(transport, onDone) {
   // Setup client and actor used in all tests
   gClient = new DebuggerClient(transport);
   gClient.connect(function onConnect() {
     gClient.listTabs(function onListTabs(aResponse) {
       gActor = aResponse.webappsActor;
--- a/toolkit/devtools/gcli/commands/listen.js
+++ b/toolkit/devtools/gcli/commands/listen.js
@@ -25,25 +25,18 @@ exports.items = [
         type: "number",
         get defaultValue() {
           return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
         },
         description: gcli.lookup("listenPortDesc"),
       }
     ],
     exec: function(args, context) {
-      if (!DebuggerServer.initialized) {
-        DebuggerServer.init();
-        DebuggerServer.addBrowserActors();
-      }
-      var reply = DebuggerServer.openListener(args.port);
-      if (!reply) {
-        throw new Error(gcli.lookup("listenDisabledOutput"));
+      DebuggerServer.controller.start(args.port);
+
+      if (!DebuggerServer._listener) {
+        return gcli.lookup("listenFailed");
       }
 
-      if (DebuggerServer.initialized) {
-        return gcli.lookupFormat("listenInitOutput", [ "" + args.port ]);
-      }
-
-      return gcli.lookup("listenNoInitOutput");
+      return gcli.lookupFormat("listenInitOutput", [ '' + args.port ]);
     },
   }
 ];
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -11,10 +11,15 @@ PARALLEL_DIRS += [
     'sourcemap',
     'webconsole',
     'apps',
     'styleinspector',
     'acorn',
     'pretty-fast'
 ]
 
+EXTRA_COMPONENTS += [
+    'DevToolsAppStartup.js',
+    'DevToolsComponents.manifest'
+]
+
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -145,28 +145,40 @@ function ModuleAPI() {
 
 /***
  * Public API
  */
 var DebuggerServer = {
   _listener: null,
   _initialized: false,
   _transportInitialized: false,
+  _controller: null,
   xpcInspector: null,
   // Number of currently open TCP connections.
   _socketConnections: 0,
   // Map of global actor names to actor constructors provided by extensions.
   globalActorFactories: {},
   // Map of tab actor names to actor constructors provided by extensions.
   tabActorFactories: {},
 
   LONG_STRING_LENGTH: 10000,
   LONG_STRING_INITIAL_LENGTH: 1000,
   LONG_STRING_READ_LENGTH: 65 * 1024,
 
+  get controller() {
+    if (!this._controller) {
+      let cid = "@mozilla.org/devtools/DebuggerServerController;1";
+      if (cid in Cc) {
+        this._controller = Cc[cid].createInstance(Ci.nsIDebuggerServerController);
+        this._controller.init(this);
+      }
+    }
+    return this._controller;
+  },
+
   /**
    * A handler function that prompts the user to accept or decline the incoming
    * connection.
    */
   _allowConnection: null,
 
   /**
    * The windowtype of the chrome window to use for actors that use the global
@@ -243,16 +255,23 @@ var DebuggerServer = {
     this._allowConnection = aAllowConnectionCallback ?
                             aAllowConnectionCallback :
                             this._defaultAllowConnection;
   },
 
   get initialized() this._initialized,
 
   /**
+   * Returns true if at least one connection is active.
+   */
+  isSocketConnected: function DS_isSocketConnected() {
+    return this._connections && Object.keys(this._connections).length > 0;
+  },
+
+  /**
    * Performs cleanup tasks before shutting down the debugger server. Such tasks
    * include clearing any actor constructors added at runtime. This method
    * should be called whenever a debugger server is no longer useful, to avoid
    * memory leaks. After this method returns, the debugger server must be
    * initialized again before use.
    */
   destroy: function DS_destroy() {
     if (!this._initialized) {
@@ -421,19 +440,16 @@ var DebuggerServer = {
   /**
    * Listens on the given port or socket file for remote debugger connections.
    *
    * @param aPortOrPath int, string
    *        If given an integer, the port to listen on.
    *        Otherwise, the path to the unix socket domain file to listen on.
    */
   openListener: function DS_openListener(aPortOrPath) {
-    if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
-      return false;
-    }
     this._checkInit();
 
     // Return early if the server is already listening.
     if (this._listener) {
       return true;
     }
 
     let flags = Ci.nsIServerSocket.KeepWhenOffline;
@@ -457,16 +473,17 @@ var DebuggerServer = {
       socket.asyncListen(this);
       this._listener = socket;
     } catch (e) {
       dumpn("Could not start debugging listener on '" + aPortOrPath + "': " + e);
       throw Cr.NS_ERROR_NOT_AVAILABLE;
     }
     this._socketConnections++;
 
+    Services.obs.notifyObservers(null, "debugger-server-started", aPortOrPath);
     return true;
   },
 
   /**
    * Close a previously-opened TCP listener.
    *
    * @param aForce boolean [optional]
    *        If set to true, then the socket will be closed, regardless of the
@@ -478,16 +495,17 @@ var DebuggerServer = {
     }
 
     // Only close the listener when the last connection is closed, or if the
     // aForce flag is passed.
     if (--this._socketConnections == 0 || aForce) {
       this._listener.close();
       this._listener = null;
       this._socketConnections = 0;
+      Services.obs.notifyObservers(null, "debugger-server-stopped", null);
     }
 
     return true;
   },
 
   /**
    * Creates a new connection to the local debugger speaking over a fake
    * transport. This connection results in straightforward calls to the onPacket
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -4,16 +4,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/.
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 XPIDL_SOURCES += [
+    'nsIDebuggerServerController.idl',
     'nsIJSInspector.idl',
 ]
 
 XPIDL_MODULE = 'jsinspector'
 
 SOURCES += [
     'nsJSInspector.cpp',
 ]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/nsIDebuggerServerController.idl
@@ -0,0 +1,13 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(8ac35a92-0741-4ec8-9444-774b9ba19a63)]
+interface nsIDebuggerServerController: nsISupports
+{
+  void init(in jsval debugger);
+  void start(in string portOrPath);
+  void stop();
+};
--- a/toolkit/devtools/server/tests/unit/head_dbg.js
+++ b/toolkit/devtools/server/tests/unit/head_dbg.js
@@ -10,18 +10,16 @@ const Cr = Components.results;
 const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
 const Services = devtools.require("Services");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 // Always log packets when running tests. runxpcshelltests.py will throw
 // the output away anyway, unless you give it the --verbose flag.
 Services.prefs.setBoolPref("devtools.debugger.log", true);
-// Enable remote debugging for the relevant tests.
-Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
 function tryImport(url) {
   try {
     Cu.import(url);
   } catch (e) {
     dump("Error importing " + url + "\n");
     dump(DevToolsUtils.safeErrorString(e) + "\n");
     throw e;
--- a/toolkit/locales/en-US/chrome/global/devtools/debugger.properties
+++ b/toolkit/locales/en-US/chrome/global/devtools/debugger.properties
@@ -17,8 +17,19 @@ remoteIncomingPromptTitle=Incoming Conne
 # LOCALIZATION NOTE (remoteIncomingPromptMessage): The message displayed on the
 # dialog that prompts the user to allow the incoming connection.
 remoteIncomingPromptMessage=An incoming request to permit remote debugging connection was detected. A remote client can take complete control over your browser! Allow connection?
 
 # LOCALIZATION NOTE (remoteIncomingPromptDisable): The label displayed on the
 # third button in the incoming connection dialog that lets the user disable the
 # remote debugger server.
 remoteIncomingPromptDisable=Disable
+
+# LOCALIZATION NOTE (debuggerStarted.*): The message is displayed in a system
+# notification box when the debugger is started. %S is the port or path the
+# server is listening to.
+debuggerStartedAlert.title=Debugger Server now running
+debuggerStartedAlert.detailPort=Listening on port %S
+debuggerStartedAlert.detailPath=Listening on %S
+
+# LOCALIZATION NOTE (debuggerStopped.title): The message is displayed in a system
+# notification box when the debugger is stopped.
+debuggerStopped.title=Debugger Server stopped
--- a/webapprt/prefs.js
+++ b/webapprt/prefs.js
@@ -71,17 +71,16 @@ pref("dom.payment.provider.0.type", "moz
 pref("dom.payment.provider.0.requestMethod", "GET");
 #endif
 
 // Enable window resize and move
 pref("dom.always_allow_move_resize_window", true);
 
 pref("plugin.allowed_types", "application/x-shockwave-flash,application/futuresplash");
 
-pref("devtools.debugger.remote-enabled", true);
 pref("devtools.debugger.force-local", true);
 
 // The default for this pref reflects whether the build is capable of IPC.
 // (Turning it on in a no-IPC build will have no effect.)
 #ifdef XP_MACOSX
 // i386 ipc preferences
 pref("dom.ipc.plugins.enabled.i386", false);
 pref("dom.ipc.plugins.enabled.i386.flash player.plugin", true);