Bug 942756 - Unify debugger server startup: devtools and browser code. r=ochameau
☠☠ backed out by bd55f7f8b48c ☠ ☠
authorPaul Rouget <paul@mozilla.com>
Fri, 18 Apr 2014 10:45:00 -0400
changeset 180109 43feed75916e326c5d74d1bf40f9d8112dee8c60
parent 180108 ebe3125cd74f09496251f0d85e85400f0d1b8f11
child 180110 824aec2863f55c98fd9d78954f541418737f7f07
push idunknown
push userunknown
push dateunknown
reviewersochameau
bugs942756
milestone31.0a1
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
@@ -562,17 +562,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
@@ -147,28 +147,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
@@ -245,16 +257,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) {
@@ -423,19 +442,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;
@@ -459,16 +475,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
@@ -480,16 +497,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
@@ -11,18 +11,16 @@ const { devtools } = Cu.import("resource
 const Services = devtools.require("Services");
 const { ActorPool, createExtraActors, appendExtraActors } = devtools.require("devtools/server/actors/common");
 const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
 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);