Bug 942756 - Unify debugger server startup: b2g code. r=fabrice r=ochameau
☠☠ backed out by 9a6ce1646135 ☠ ☠
authorPaul Rouget <paul@mozilla.com>
Tue, 22 Apr 2014 06:28:00 +0200
changeset 180404 ac1d4bc60841a9e6b96daa861ed94d376553aac2
parent 180403 d7931085db185c74cecc142b3b219e53964f3a5d
child 180405 a45a8d47cf9c3c78c48b4b89c4cb6b21a6881205
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersfabrice, ochameau
bugs942756
milestone31.0a1
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
@@ -185,302 +185,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