Bug 942756 - Unify debugger server startup: b2g code. r=fabrice, r=ochameau
☠☠ backed out by bd55f7f8b48c ☠ ☠
authorPaul Rouget <paul@mozilla.com>
Tue, 22 Apr 2014 06:28:00 -0400
changeset 198608 f7b85c252914c7b0855fff96262b48965f943f52
parent 198607 4e540736b330280c0d4420e74eee60ec1e3d43c9
child 198609 d234447173a8b4fbfc94f2215c1c73613b0ed636
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, ochameau
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: b2g code. r=fabrice, r=ochameau
b2g/app/b2g.js
b2g/chrome/content/adbController.js
b2g/chrome/content/settings.js
b2g/chrome/content/shell.html
b2g/chrome/content/shell.js
b2g/chrome/jar.mn
b2g/components/B2GComponents.manifest
b2g/components/DebuggerServerController.js
b2g/components/moz.build
b2g/installer/package-manifest.in
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -842,19 +842,23 @@ pref("dom.inter-app-communication-api.en
 // Allow ADB to run for this many hours before disabling
 // (only applies when marionette is disabled)
 // 0 disables the timer.
 pref("b2g.adb.timeout-hours", 12);
 
 // InputMethod so we can do soft keyboards
 pref("dom.mozInputMethod.enabled", true);
 
+#ifdef MOZ_WIDGET_GONK
 // Absolute path to the devtool unix domain socket file used
 // to communicate with a usb cable via adb forward
 pref("devtools.debugger.unix-domain-socket", "/data/local/debugger-socket");
+#else
+pref("devtools.debugger.remote-port", 6000);
+#endif
 
 // enable Skia/GL (OpenGL-accelerated 2D drawing) for large enough 2d canvases,
 // falling back to Skia/software for smaller canvases
 #ifdef MOZ_WIDGET_GONK
 pref("gfx.canvas.azure.backends", "skia");
 pref("gfx.canvas.azure.accelerated", true);
 #endif
 
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/adbController.js
@@ -0,0 +1,267 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict;"
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+    "DebuggerServer",
+    "resource://gre/modules/devtools/dbg-server.jsm");
+
+let adbController = {
+  DEBUG: false,
+  locked: undefined,
+  remoteDebuggerEnabled: undefined,
+  lockEnabled: undefined,
+  disableAdbTimer: null,
+  disableAdbTimeoutHours: 12,
+  umsActive: false,
+
+  init: function() {
+    this.updateState = this.updateState.bind(this);
+    Services.obs.addObserver(this.updateState,
+                             "debugger-connection-changed",
+                             false);
+    SettingsListener.observe('lockscreen.locked', undefined,
+                             v => this.setLockscreenState(v));
+    SettingsListener.observe('lockscreen.enabled', undefined,
+                             v => this.setLockscreenEnabled(v));
+    SettingsListener.observe('debugger.remote-mode', 'disabled',
+                             v => {
+                               this.setRemoteDebuggerState(v != 'disabled');
+                             });
+  },
+
+  debug: function(str) {
+    dump("AdbController: " + str + "\n");
+  },
+
+  setLockscreenEnabled: function(value) {
+    this.lockEnabled = value;
+    if (this.DEBUG) {
+      this.debug("setLockscreenEnabled = " + this.lockEnabled);
+    }
+    this.updateState();
+  },
+
+  setLockscreenState: function(value) {
+    this.locked = value;
+    if (this.DEBUG) {
+      this.debug("setLockscreenState = " + this.locked);
+    }
+    this.updateState();
+  },
+
+  setRemoteDebuggerState: function(value) {
+    this.remoteDebuggerEnabled = value;
+    if (this.DEBUG) {
+      this.debug("setRemoteDebuggerState = " + this.remoteDebuggerEnabled);
+    }
+    this.updateState();
+  },
+
+  startDisableAdbTimer: function() {
+    if (this.disableAdbTimer) {
+      this.disableAdbTimer.cancel();
+    } else {
+      this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      try {
+        this.disableAdbTimeoutHours =
+          Services.prefs.getIntPref("b2g.adb.timeout-hours");
+      } catch (e) {
+        // This happens if the pref doesn't exist, in which case
+        // disableAdbTimeoutHours will still be set to the default.
+      }
+    }
+    if (this.disableAdbTimeoutHours <= 0) {
+      if (this.DEBUG) {
+        this.debug("Timer to disable ADB not started due to zero timeout");
+      }
+      return;
+    }
+
+    if (this.DEBUG) {
+      this.debug("Starting timer to disable ADB in " +
+                 this.disableAdbTimeoutHours + " hours");
+    }
+    let timeoutMilliseconds = this.disableAdbTimeoutHours * 60 * 60 * 1000;
+    this.disableAdbTimer.initWithCallback(this, timeoutMilliseconds,
+                                          Ci.nsITimer.TYPE_ONE_SHOT);
+  },
+
+  stopDisableAdbTimer: function() {
+    if (this.DEBUG) {
+      this.debug("Stopping timer to disable ADB");
+    }
+    if (this.disableAdbTimer) {
+      this.disableAdbTimer.cancel();
+      this.disableAdbTimer = null;
+    }
+  },
+
+  notify: function(aTimer) {
+    if (aTimer == this.disableAdbTimer) {
+      this.disableAdbTimer = null;
+      // The following dump will be the last thing that shows up in logcat,
+      // and will at least give the user a clue about why logcat was
+      // disconnected, if the user happens to be using logcat.
+      dump("AdbController: ADB timer expired - disabling ADB\n");
+      navigator.mozSettings.createLock().set(
+        {'devtools.debugger.remote-enabled': false});
+    }
+  },
+
+  updateState: function() {
+    this.umsActive = false;
+    this.storages = navigator.getDeviceStorages('sdcard');
+    this.updateStorageState(0);
+  },
+
+  updateStorageState: function(storageIndex) {
+    if (storageIndex >= this.storages.length) {
+      // We've iterated through all of the storage objects, now we can
+      // really do updateStateInternal.
+      this.updateStateInternal();
+      return;
+    }
+    let storage = this.storages[storageIndex];
+    if (this.DEBUG) {
+      this.debug("Checking availability of storage: '" +
+                 storage.storageName);
+    }
+
+    let req = storage.available();
+    req.onsuccess = function(e) {
+      if (this.DEBUG) {
+        this.debug("Storage: '" + storage.storageName + "' is '" +
+                   e.target.result);
+      }
+      if (e.target.result == 'shared') {
+        // We've found a storage area that's being shared with the PC.
+        // We can stop looking now.
+        this.umsActive = true;
+        this.updateStateInternal();
+        return;
+      }
+      this.updateStorageState(storageIndex + 1);
+    }.bind(this);
+    req.onerror = function(e) {
+      dump("AdbController: error querying storage availability for '" +
+           this.storages[storageIndex].storageName + "' (ignoring)\n");
+      this.updateStorageState(storageIndex + 1);
+    }.bind(this);
+  },
+
+  updateStateInternal: function() {
+    if (this.DEBUG) {
+      this.debug("updateStateInternal: called");
+    }
+
+    if (this.remoteDebuggerEnabled === undefined ||
+        this.lockEnabled === undefined ||
+        this.locked === undefined) {
+      // Part of initializing the settings database will cause the observers
+      // to trigger. We want to wait until both have been initialized before
+      // we start changing ther adb state. Without this then we can wind up
+      // toggling adb off and back on again (or on and back off again).
+      //
+      // For completeness, one scenario which toggles adb is using the unagi.
+      // The unagi has adb enabled by default (prior to b2g starting). If you
+      // have the phone lock disabled and remote debugging enabled, then we'll
+      // receive an unlock event and an rde event. However at the time we
+      // receive the unlock event we haven't yet received the rde event, so
+      // we turn adb off momentarily, which disconnects a logcat that might
+      // be running. Changing the defaults (in AdbController) just moves the
+      // problem to a different phone, which has adb disabled by default and
+      // we wind up turning on adb for a short period when we shouldn't.
+      //
+      // By waiting until both values are properly initialized, we avoid
+      // turning adb on or off accidentally.
+      if (this.DEBUG) {
+        this.debug("updateState: Waiting for all vars to be initialized");
+      }
+      return;
+    }
+
+    // Check if we have a remote debugging session going on. If so, we won't
+    // disable adb even if the screen is locked.
+    let isDebugging = DebuggerServer.isSocketConnected();
+    if (this.DEBUG) {
+      this.debug("isDebugging=" + isDebugging);
+    }
+
+    // If USB Mass Storage, USB tethering, or a debug session is active,
+    // then we don't want to disable adb in an automatic fashion (i.e.
+    // when the screen locks or due to timeout).
+    let sysUsbConfig = libcutils.property_get("sys.usb.config");
+    let rndisActive = (sysUsbConfig.split(",").indexOf("rndis") >= 0);
+    let usbFuncActive = rndisActive || this.umsActive || isDebugging;
+
+    let enableAdb = this.remoteDebuggerEnabled &&
+      (!(this.lockEnabled && this.locked) || usbFuncActive);
+
+    let useDisableAdbTimer = true;
+    try {
+      if (Services.prefs.getBoolPref("marionette.defaultPrefs.enabled")) {
+        // Marionette is enabled. Marionette requires that adb be on (and also
+        // requires that remote debugging be off). The fact that marionette
+        // is enabled also implies that we're doing a non-production build, so
+        // we want adb enabled all of the time.
+        enableAdb = true;
+        useDisableAdbTimer = false;
+      }
+    } catch (e) {
+      // This means that the pref doesn't exist. Which is fine. We just leave
+      // enableAdb alone.
+    }
+    if (this.DEBUG) {
+      this.debug("updateState: enableAdb = " + enableAdb +
+                 " remoteDebuggerEnabled = " + this.remoteDebuggerEnabled +
+                 " lockEnabled = " + this.lockEnabled +
+                 " locked = " + this.locked +
+                 " usbFuncActive = " + usbFuncActive);
+    }
+
+    // Configure adb.
+    let currentConfig = libcutils.property_get("persist.sys.usb.config");
+    let configFuncs = currentConfig.split(",");
+    let adbIndex = configFuncs.indexOf("adb");
+
+    if (enableAdb) {
+      // Add adb to the list of functions, if not already present
+      if (adbIndex < 0) {
+        configFuncs.push("adb");
+      }
+    } else {
+      // Remove adb from the list of functions, if present
+      if (adbIndex >= 0) {
+        configFuncs.splice(adbIndex, 1);
+      }
+    }
+    let newConfig = configFuncs.join(",");
+    if (newConfig != currentConfig) {
+      if (this.DEBUG) {
+        this.debug("updateState: currentConfig = " + currentConfig);
+        this.debug("updateState:     newConfig = " + newConfig);
+      }
+      try {
+        libcutils.property_set("persist.sys.usb.config", newConfig);
+      } catch(e) {
+        dump("Error configuring adb: " + e);
+      }
+    }
+    if (useDisableAdbTimer) {
+      if (enableAdb && !usbFuncActive) {
+        this.startDisableAdbTimer();
+      } else {
+        this.stopDisableAdbTimer();
+      }
+    }
+  }
+};
+
+adbController.init();
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -183,302 +183,16 @@ SettingsListener.observe('devtools.overl
     developerHUD.init();
   } else {
     if (developerHUD) {
       developerHUD.uninit();
     }
   }
 });
 
-// =================== Debugger / ADB ====================
-
-#ifdef MOZ_WIDGET_GONK
-let AdbController = {
-  DEBUG: false,
-  locked: undefined,
-  remoteDebuggerEnabled: undefined,
-  lockEnabled: undefined,
-  disableAdbTimer: null,
-  disableAdbTimeoutHours: 12,
-  umsActive: false,
-
-  debug: function(str) {
-    dump("AdbController: " + str + "\n");
-  },
-
-  setLockscreenEnabled: function(value) {
-    this.lockEnabled = value;
-    if (this.DEBUG) {
-      this.debug("setLockscreenEnabled = " + this.lockEnabled);
-    }
-    this.updateState();
-  },
-
-  setLockscreenState: function(value) {
-    this.locked = value;
-    if (this.DEBUG) {
-      this.debug("setLockscreenState = " + this.locked);
-    }
-    this.updateState();
-  },
-
-  setRemoteDebuggerState: function(value) {
-    this.remoteDebuggerEnabled = value;
-    if (this.DEBUG) {
-      this.debug("setRemoteDebuggerState = " + this.remoteDebuggerEnabled);
-    }
-    this.updateState();
-  },
-
-  startDisableAdbTimer: function() {
-    if (this.disableAdbTimer) {
-      this.disableAdbTimer.cancel();
-    } else {
-      this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-      try {
-        this.disableAdbTimeoutHours =
-          Services.prefs.getIntPref("b2g.adb.timeout-hours");
-      } catch (e) {
-        // This happens if the pref doesn't exist, in which case
-        // disableAdbTimeoutHours will still be set to the default.
-      }
-    }
-    if (this.disableAdbTimeoutHours <= 0) {
-      if (this.DEBUG) {
-        this.debug("Timer to disable ADB not started due to zero timeout");
-      }
-      return;
-    }
-
-    if (this.DEBUG) {
-      this.debug("Starting timer to disable ADB in " +
-                 this.disableAdbTimeoutHours + " hours");
-    }
-    let timeoutMilliseconds = this.disableAdbTimeoutHours * 60 * 60 * 1000;
-    this.disableAdbTimer.initWithCallback(this, timeoutMilliseconds,
-                                          Ci.nsITimer.TYPE_ONE_SHOT);
-  },
-
-  stopDisableAdbTimer: function() {
-    if (this.DEBUG) {
-      this.debug("Stopping timer to disable ADB");
-    }
-    if (this.disableAdbTimer) {
-      this.disableAdbTimer.cancel();
-      this.disableAdbTimer = null;
-    }
-  },
-
-  notify: function(aTimer) {
-    if (aTimer == this.disableAdbTimer) {
-      this.disableAdbTimer = null;
-      // The following dump will be the last thing that shows up in logcat,
-      // and will at least give the user a clue about why logcat was
-      // disconnected, if the user happens to be using logcat.
-      dump("AdbController: ADB timer expired - disabling ADB\n");
-      navigator.mozSettings.createLock().set(
-        {'devtools.debugger.remote-enabled': false});
-    }
-  },
-
-  updateState: function() {
-    this.umsActive = false;
-    this.storages = navigator.getDeviceStorages('sdcard');
-    this.updateStorageState(0);
-  },
-
-  updateStorageState: function(storageIndex) {
-    if (storageIndex >= this.storages.length) {
-      // We've iterated through all of the storage objects, now we can
-      // really do updateStateInternal.
-      this.updateStateInternal();
-      return;
-    }
-    let storage = this.storages[storageIndex];
-    if (this.DEBUG) {
-      this.debug("Checking availability of storage: '" +
-                 storage.storageName);
-    }
-
-    let req = storage.available();
-    req.onsuccess = function(e) {
-      if (this.DEBUG) {
-        this.debug("Storage: '" + storage.storageName + "' is '" +
-                   e.target.result);
-      }
-      if (e.target.result == 'shared') {
-        // We've found a storage area that's being shared with the PC.
-        // We can stop looking now.
-        this.umsActive = true;
-        this.updateStateInternal();
-        return;
-      }
-      this.updateStorageState(storageIndex + 1);
-    }.bind(this);
-    req.onerror = function(e) {
-      dump("AdbController: error querying storage availability for '" +
-           this.storages[storageIndex].storageName + "' (ignoring)\n");
-      this.updateStorageState(storageIndex + 1);
-    }.bind(this);
-  },
-
-  updateStateInternal: function() {
-    if (this.DEBUG) {
-      this.debug("updateStateInternal: called");
-    }
-
-    if (this.remoteDebuggerEnabled === undefined ||
-        this.lockEnabled === undefined ||
-        this.locked === undefined) {
-      // Part of initializing the settings database will cause the observers
-      // to trigger. We want to wait until both have been initialized before
-      // we start changing ther adb state. Without this then we can wind up
-      // toggling adb off and back on again (or on and back off again).
-      //
-      // For completeness, one scenario which toggles adb is using the unagi.
-      // The unagi has adb enabled by default (prior to b2g starting). If you
-      // have the phone lock disabled and remote debugging enabled, then we'll
-      // receive an unlock event and an rde event. However at the time we
-      // receive the unlock event we haven't yet received the rde event, so
-      // we turn adb off momentarily, which disconnects a logcat that might
-      // be running. Changing the defaults (in AdbController) just moves the
-      // problem to a different phone, which has adb disabled by default and
-      // we wind up turning on adb for a short period when we shouldn't.
-      //
-      // By waiting until both values are properly initialized, we avoid
-      // turning adb on or off accidentally.
-      if (this.DEBUG) {
-        this.debug("updateState: Waiting for all vars to be initialized");
-      }
-      return;
-    }
-
-    // Check if we have a remote debugging session going on. If so, we won't
-    // disable adb even if the screen is locked.
-    let isDebugging = RemoteDebugger.isDebugging;
-    if (this.DEBUG) {
-      this.debug("isDebugging=" + isDebugging);
-    }
-
-    // If USB Mass Storage, USB tethering, or a debug session is active,
-    // then we don't want to disable adb in an automatic fashion (i.e.
-    // when the screen locks or due to timeout).
-    let sysUsbConfig = libcutils.property_get("sys.usb.config");
-    let rndisActive = (sysUsbConfig.split(",").indexOf("rndis") >= 0);
-    let usbFuncActive = rndisActive || this.umsActive || isDebugging;
-
-    let enableAdb = this.remoteDebuggerEnabled &&
-      (!(this.lockEnabled && this.locked) || usbFuncActive);
-
-    let useDisableAdbTimer = true;
-    try {
-      if (Services.prefs.getBoolPref("marionette.defaultPrefs.enabled")) {
-        // Marionette is enabled. Marionette requires that adb be on (and also
-        // requires that remote debugging be off). The fact that marionette
-        // is enabled also implies that we're doing a non-production build, so
-        // we want adb enabled all of the time.
-        enableAdb = true;
-        useDisableAdbTimer = false;
-      }
-    } catch (e) {
-      // This means that the pref doesn't exist. Which is fine. We just leave
-      // enableAdb alone.
-    }
-    if (this.DEBUG) {
-      this.debug("updateState: enableAdb = " + enableAdb +
-                 " remoteDebuggerEnabled = " + this.remoteDebuggerEnabled +
-                 " lockEnabled = " + this.lockEnabled +
-                 " locked = " + this.locked +
-                 " usbFuncActive = " + usbFuncActive);
-    }
-
-    // Configure adb.
-    let currentConfig = libcutils.property_get("persist.sys.usb.config");
-    let configFuncs = currentConfig.split(",");
-    let adbIndex = configFuncs.indexOf("adb");
-
-    if (enableAdb) {
-      // Add adb to the list of functions, if not already present
-      if (adbIndex < 0) {
-        configFuncs.push("adb");
-      }
-    } else {
-      // Remove adb from the list of functions, if present
-      if (adbIndex >= 0) {
-        configFuncs.splice(adbIndex, 1);
-      }
-    }
-    let newConfig = configFuncs.join(",");
-    if (newConfig != currentConfig) {
-      if (this.DEBUG) {
-        this.debug("updateState: currentConfig = " + currentConfig);
-        this.debug("updateState:     newConfig = " + newConfig);
-      }
-      try {
-        libcutils.property_set("persist.sys.usb.config", newConfig);
-      } catch(e) {
-        dump("Error configuring adb: " + e);
-      }
-    }
-    if (useDisableAdbTimer) {
-      if (enableAdb && !usbFuncActive) {
-        this.startDisableAdbTimer();
-      } else {
-        this.stopDisableAdbTimer();
-      }
-    }
-  }
-};
-
-SettingsListener.observe("lockscreen.locked", false,
-                         AdbController.setLockscreenState.bind(AdbController));
-SettingsListener.observe("lockscreen.enabled", false,
-                         AdbController.setLockscreenEnabled.bind(AdbController));
-#endif
-
-// Keep the old setting to not break people that won't have updated
-// gaia and gecko.
-SettingsListener.observe('devtools.debugger.remote-enabled', false, function(value) {
-  Services.prefs.setBoolPref('devtools.debugger.remote-enabled', value);
-  // This preference is consulted during startup
-  Services.prefs.savePrefFile(null);
-  try {
-    value ? RemoteDebugger.start() : RemoteDebugger.stop();
-  } catch(e) {
-    dump("Error while initializing devtools: " + e + "\n" + e.stack + "\n");
-  }
-
-#ifdef MOZ_WIDGET_GONK
-  AdbController.setRemoteDebuggerState(value);
-#endif
-});
-
-SettingsListener.observe('debugger.remote-mode', false, function(value) {
-  if (['disabled', 'adb-only', 'adb-devtools'].indexOf(value) == -1) {
-    dump('Illegal value for debugger.remote-mode: ' + value + '\n');
-    return;
-  }
-
-  Services.prefs.setBoolPref('devtools.debugger.remote-enabled',
-                             value == 'adb-devtools');
-  // This preference is consulted during startup
-  Services.prefs.savePrefFile(null);
-
-  try {
-    (value == 'adb-devtools') ? RemoteDebugger.start()
-                              : RemoteDebugger.stop();
-  } catch(e) {
-    dump("Error while initializing devtools: " + e + "\n" + e.stack + "\n");
-  }
-
-#ifdef MOZ_WIDGET_GONK
-  AdbController.setRemoteDebuggerState(value != 'disabled');
-#endif
-});
-
 // =================== Device Storage ====================
 SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
   if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {
     // We clear the pref because it used to be erroneously written as a bool
     // and we need to clear it before we can change it to have the correct type.
     Services.prefs.clearUserPref('device.storage.writable.name');
   }
   Services.prefs.setCharPref('device.storage.writable.name', value);
--- a/b2g/chrome/content/shell.html
+++ b/b2g/chrome/content/shell.html
@@ -27,16 +27,19 @@
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/desktop.js"> </script>
   <!-- this script handles the screen argument for desktop builds -->
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/screen.js"> </script>
   <!-- this script handles the "runapp" argument for desktop builds -->
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/runapp.js"> </script>
+#else
+  <script type="application/javascript;version=1.8"
+          src="chrome://b2g/content/adbController.js"> </script>
 #endif
 </head>
   <body id="container">
 #ifdef FXOS_SIMULATOR
     <!--
      Some additional buttons are displayed on desktop to fake hardware buttons.
     -->
     <footer id="controls">
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -47,21 +47,16 @@ XPCOMUtils.defineLazyServiceGetter(Servi
 XPCOMUtils.defineLazyServiceGetter(this, 'gSystemMessenger',
                                    '@mozilla.org/system-message-internal;1',
                                    'nsISystemMessagesInternal');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'fm',
                                    '@mozilla.org/focus-manager;1',
                                    'nsIFocusManager');
 
-XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() {
-  Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
-  return DebuggerServer;
-});
-
 XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
          .getService(Ci.nsIMessageListenerManager);
 });
 
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
   Cu.import("resource://gre/modules/systemlibs.js");
@@ -762,19 +757,16 @@ var CustomEventManager = {
         WebappsHelper.handleEvent(detail);
         break;
       case 'select-choicechange':
         FormsHelper.handleEvent(detail);
         break;
       case 'system-message-listener-ready':
         Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
         break;
-      case 'remote-debugger-prompt':
-        RemoteDebugger.handleEvent(detail);
-        break;
       case 'captive-portal-login-cancel':
         CaptivePortalLoginHelper.handleEvent(detail);
         break;
       case 'inter-app-comm-permission':
         Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
           JSON.stringify({ callerID: detail.chromeEventID,
                            keyword: detail.keyword,
                            manifestURL: detail.manifestURL,
@@ -1072,141 +1064,16 @@ let IndexedDBPromptHelper = {
 
     setTimeout(function() {
       observer.observe(null, responseTopic,
                        Ci.nsIPermissionManager.DENY_ACTION);
     }, 0);
   }
 }
 
-let RemoteDebugger = {
-  _promptDone: false,
-  _promptAnswer: false,
-  _running: false,
-
-  prompt: function debugger_prompt() {
-    this._promptDone = false;
-
-    shell.sendChromeEvent({
-      "type": "remote-debugger-prompt"
-    });
-
-    while(!this._promptDone) {
-      Services.tm.currentThread.processNextEvent(true);
-    }
-
-    return this._promptAnswer;
-  },
-
-  handleEvent: function debugger_handleEvent(detail) {
-    this._promptAnswer = detail.value;
-    this._promptDone = true;
-  },
-
-  get isDebugging() {
-    if (!this._running) {
-      return false;
-    }
-
-    return DebuggerServer._connections &&
-           Object.keys(DebuggerServer._connections).length > 0;
-  },
-
-  // Start the debugger server.
-  start: function debugger_start() {
-    if (this._running) {
-      return;
-    }
-
-    if (!DebuggerServer.initialized) {
-      // Ask for remote connections.
-      DebuggerServer.init(this.prompt.bind(this));
-
-      // /!\ Be careful when adding a new actor, especially global actors.
-      // Any new global actor will be exposed and returned by the root actor.
-
-      // Add Firefox-specific actors, but prevent tab actors to be loaded in
-      // the parent process, unless we enable certified apps debugging.
-      let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
-      DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges);
-
-      /**
-       * Construct a root actor appropriate for use in a server running in B2G.
-       * The returned root actor respects the factories registered with
-       * DebuggerServer.addGlobalActor only if certified apps debugging is on,
-       * otherwise we used an explicit limited list of global actors
-       *
-       * * @param connection DebuggerServerConnection
-       *        The conection to the client.
-       */
-      DebuggerServer.createRootActor = function createRootActor(connection)
-      {
-        let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
-        let parameters = {
-          // We do not expose browser tab actors yet,
-          // but we still have to define tabList.getList(),
-          // otherwise, client won't be able to fetch global actors
-          // from listTabs request!
-          tabList: {
-            getList: function() {
-              return promise.resolve([]);
-            }
-          },
-          // Use an explicit global actor list to prevent exposing
-          // unexpected actors
-          globalActorFactories: restrictPrivileges ? {
-            webappsActor: DebuggerServer.globalActorFactories.webappsActor,
-            deviceActor: DebuggerServer.globalActorFactories.deviceActor,
-          } : DebuggerServer.globalActorFactories
-        };
-        let root = new DebuggerServer.RootActor(connection, parameters);
-        root.applicationType = "operating-system";
-        return root;
-      };
-
-#ifdef MOZ_WIDGET_GONK
-      DebuggerServer.on("connectionchange", function() {
-        AdbController.updateState();
-      });
-#endif
-    }
-
-    let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
-               "/data/local/debugger-socket";
-    try {
-      DebuggerServer.openListener(path);
-      // Temporary event, until bug 942756 lands and offers a way to know
-      // when the server is up and running.
-      Services.obs.notifyObservers(null, 'debugger-server-started', null);
-      this._running = true;
-    } catch (e) {
-      dump('Unable to start debugger server: ' + e + '\n');
-    }
-  },
-
-  stop: function debugger_stop() {
-    if (!this._running) {
-      return;
-    }
-
-    if (!DebuggerServer.initialized) {
-      // Can this really happen if we are running?
-      this._running = false;
-      return;
-    }
-
-    try {
-      DebuggerServer.closeListener();
-    } catch (e) {
-      dump('Unable to stop debugger server: ' + e + '\n');
-    }
-    this._running = false;
-  }
-}
-
 let KeyboardHelper = {
   handleEvent: function keyboard_handleEvent(detail) {
     Keyboard.setLayouts(detail.layouts);
   }
 };
 
 // This is the backend for Gaia's screenshot feature.  Gaia requests a
 // screenshot by sending a mozContentEvent with detail.type set to
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -20,16 +20,19 @@ chrome.jar:
   content/images/desktop/home-white.png (content/images/desktop/home-white.png)
   content/images/desktop/rotate.png     (content/images/desktop/rotate.png)
 #endif
 #ifndef MOZ_WIDGET_GONK
   content/desktop.js                    (content/desktop.js)
   content/screen.js                     (content/screen.js)
   content/runapp.js                     (content/runapp.js)
 #endif
+#ifdef MOZ_WIDGET_GONK
+  content/adbController.js              (content/adbController.js)
+#endif
 * content/content.css                   (content/content.css)
   content/touchcontrols.css             (content/touchcontrols.css)
 
 * content/payment.js                    (content/payment.js)
   content/identity.js                   (content/identity.js)
 
 % override chrome://global/skin/media/videocontrols.css chrome://b2g/content/touchcontrols.css
 % override chrome://global/content/aboutCertError.xhtml chrome://b2g/content/aboutCertError.xhtml
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -82,8 +82,12 @@ contract @mozilla.org/fxaccounts/fxaccou
 component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
 contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
 
 #ifndef MOZ_WIDGET_GONK
 component {c83c02c0-5d43-4e3e-987f-9173b313e880} SimulatorScreen.js
 contract @mozilla.org/simulator-screen;1 {c83c02c0-5d43-4e3e-987f-9173b313e880}
 category profile-after-change SimulatorScreen @mozilla.org/simulator-screen;1
 #endif
+
+# DebuggerServerController.js
+component {9390f6ac-7914-46c6-b9d0-ccc7db550d8c} DebuggerServerController.js
+contract @mozilla.org/devtools/DebuggerServerController;1 {9390f6ac-7914-46c6-b9d0-ccc7db550d8c}
new file mode 100644
--- /dev/null
+++ b/b2g/components/DebuggerServerController.js
@@ -0,0 +1,196 @@
+/* -*- 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+    "Settings",
+    "@mozilla.org/settingsService;1", "nsISettingsService");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+     "SystemAppProxy",
+     "resource://gre/modules/SystemAppProxy.jsm");
+
+function DebuggerServerController() {
+}
+
+DebuggerServerController.prototype = {
+  classID: Components.ID("{9390f6ac-7914-46c6-b9d0-ccc7db550d8c}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDebuggerServerController, Ci.nsIObserver]),
+
+  init: function init(debuggerServer) {
+    this.debugger = debuggerServer;
+    Services.obs.addObserver(this, "mozsettings-changed", false);
+    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 uninit() {
+    this.debugger = null;
+    Services.obs.removeObserver(this, "mozsettings-changed");
+    Services.obs.removeObserver(this, "debugger-server-started");
+    Services.obs.removeObserver(this, "debugger-server-stopped");
+    Services.obs.removeObserver(this, "xpcom-shutdown");
+  },
+
+  // nsIObserver
+
+  observe: function observe(subject, topic, data) {
+    switch (topic) {
+      case "xpcom-shutdown":
+        this.uninit();
+        break;
+      case "debugger-server-started":
+        this._onDebuggerStarted(data);
+        break;
+      case "debugger-server-stopped":
+        this._onDebuggerStopped();
+        break;
+      case "mozsettings-changed":
+        let {key, value} = JSON.parse(data);
+        switch(key) {
+          case "debugger.remote-mode":
+            if (["disabled", "adb-only", "adb-devtools"].indexOf(value) == -1) {
+              dump("Illegal value for debugger.remote-mode: " + value + "\n");
+              return;
+            }
+
+            Services.prefs.setBoolPref("devtools.debugger.remote-enabled", value == "adb-devtools");
+            Services.prefs.savePrefFile(null);
+
+            if (value != "adb-devtools") {
+              // The *pref* "devtools.debugger.remote-enabled" has been set to false (setBoolPref)
+              // above. In theory, it's supposed to automatically stop the debugger, but maybe the
+              // debugger has been started from the command line, so the value was already false,
+              // so the observer is not called because the value didn't change. We need to stop
+              // the debugger manually:
+              this.stop();
+            }
+        }
+    }
+
+  },
+
+  // nsIDebuggerController
+
+  start: function(portOrPath) {
+    if (!portOrPath) {
+      throw new Error("No TCP port or unix socket path specified.");
+    }
+
+    if (!this.debugger.initialized) {
+      // Ask for remote connections.
+      this.debugger.init(Prompt.prompt.bind(Prompt));
+
+      // /!\ Be careful when adding a new actor, especially global actors.
+      // Any new global actor will be exposed and returned by the root actor.
+
+      // Add Firefox-specific actors, but prevent tab actors to be loaded in
+      // the parent process, unless we enable certified apps debugging.
+      let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+      this.debugger.addBrowserActors("navigator:browser", restrictPrivileges);
+
+      /**
+       * Construct a root actor appropriate for use in a server running in B2G.
+       * The returned root actor respects the factories registered with
+       * DebuggerServer.addGlobalActor only if certified apps debugging is on,
+       * otherwise we used an explicit limited list of global actors
+       *
+       * * @param connection DebuggerServerConnection
+       *        The conection to the client.
+       */
+      let debuggerServer = this.debugger;
+      debuggerServer.createRootActor = function createRootActor(connection)
+      {
+        let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+        let parameters = {
+          // We do not expose browser tab actors yet,
+          // but we still have to define tabList.getList(),
+          // otherwise, client won't be able to fetch global actors
+          // from listTabs request!
+          tabList: {
+            getList: function() {
+              return promise.resolve([]);
+            }
+          },
+          // Use an explicit global actor list to prevent exposing
+          // unexpected actors
+          globalActorFactories: restrictPrivileges ? {
+            webappsActor: debuggerServer.globalActorFactories.webappsActor,
+            deviceActor: debuggerServer.globalActorFactories.deviceActor,
+          } : debuggerServer.globalActorFactories
+        };
+        let root = new debuggerServer.RootActor(connection, parameters);
+        root.applicationType = "operating-system";
+        return root;
+      };
+
+    }
+
+    try {
+      this.debugger.openListener(portOrPath);
+    } catch (e) {
+      dump("Unable to start debugger server (" + portOrPath + "): " + e + "\n");
+    }
+
+  },
+
+  stop: function() {
+    this.debugger.destroy();
+  },
+
+  _onDebuggerStarted: function(portOrPath) {
+    dump("Devtools debugger server started: " + portOrPath + "\n");
+    Settings.createLock().set("debugger.remote-mode", "adb-devtools", null);
+  },
+
+
+  _onDebuggerStopped: function() {
+    dump("Devtools debugger server stopped\n");
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DebuggerServerController]);
+
+// =================== Prompt ====================
+
+let Prompt = {
+  _promptDone: false,
+  _promptAnswer: false,
+  _listenerAttached: false,
+
+  prompt: function () {
+    if (!this._listenerAttached) {
+      SystemAppProxy.addEventListener("mozContentEvent", this, false, true);
+      this._listenerAttached = true;
+    }
+
+    this._promptDone = false;
+
+    SystemAppProxy._sendCustomEvent("mozChromeEvent", {
+      "type": "remote-debugger-prompt"
+    });
+
+
+    while(!this._promptDone) {
+      Services.tm.currentThread.processNextEvent(true);
+    }
+
+    return this._promptAnswer;
+  },
+
+  // Content events listener
+
+  handleEvent: function (event) {
+    if (event.detail.type == "remote-debugger-prompt") {
+      this._promptAnswer = event.detail.value;
+      this._promptDone = true;
+    }
+  }
+}
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -7,16 +7,17 @@
 DIRS += ['test']
 
 EXTRA_COMPONENTS += [
     'ActivitiesGlue.js',
     'AlertsService.js',
     'B2GAboutRedirector.js',
     'ContentHandler.js',
     'ContentPermissionPrompt.js',
+    'DebuggerServerController.js',
     'FilePicker.js',
     'HelperAppDialog.js',
     'MailtoProtocolHandler.js',
     'PaymentGlue.js',
     'ProcessGlobal.js',
     'SmsProtocolHandler.js',
     'TelProtocolHandler.js',
     'WebappsUpdateTimer.js',
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -429,16 +429,21 @@
 #ifndef MOZ_WIDGET_GONK
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
 #endif
 
+; DevTools
+@BINPATH@/components/DevToolsComponents.manifest
+@BINPATH@/components/DevToolsAppStartup.js
+@BINPATH@/components/DebuggerServerController.js
+
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
 #endif
 @BINPATH@/components/nsUpdateTimerManager.manifest
 @BINPATH@/components/nsUpdateTimerManager.js
 @BINPATH@/components/pluginGlue.manifest