Bug 1039493 - Break out B2G DevTools files. r=fabrice
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 18 Aug 2014 15:40:00 -0400
changeset 200219 63aa6e5dd802c679d2fc50f039126074304cc316
parent 200218 3ed68f04121af8da39df9114ed86f10674ffab48
child 200220 c0d776bda527d55d542213a50eb98503c2ab8058
push id9844
push userryanvm@gmail.com
push dateTue, 19 Aug 2014 12:27:20 +0000
treeherderb2g-inbound@941284822c7c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1039493
milestone34.0a1
Bug 1039493 - Break out B2G DevTools files. r=fabrice
b2g/chrome/content/devtools.js
b2g/chrome/content/devtools/adb.js
b2g/chrome/content/devtools/debugger.js
b2g/chrome/content/devtools/hud.js
b2g/chrome/content/settings.js
b2g/chrome/content/shell.html
b2g/chrome/content/shell.js
b2g/chrome/jar.mn
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/devtools/adb.js
@@ -0,0 +1,251 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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";
+
+// This file is only loaded on Gonk to manage ADB state
+
+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(
+        {'debugger.remote-mode': 'disabled'});
+    }
+  },
+
+  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 = USBRemoteDebugger.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));
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -0,0 +1,275 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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";
+
+XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function() {
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+  return DebuggerServer;
+});
+
+XPCOMUtils.defineLazyGetter(this, "devtools", function() {
+  const { devtools } =
+    Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+  return devtools;
+});
+
+XPCOMUtils.defineLazyGetter(this, "discovery", function() {
+  return devtools.require("devtools/toolkit/discovery/discovery");
+});
+
+let RemoteDebugger = {
+  _promptDone: false,
+  _promptAnswer: false,
+  _listening: false,
+
+  prompt: function() {
+    this._listen();
+
+    this._promptDone = false;
+
+    shell.sendChromeEvent({
+      "type": "remote-debugger-prompt"
+    });
+
+    while(!this._promptDone) {
+      Services.tm.currentThread.processNextEvent(true);
+    }
+
+    return this._promptAnswer;
+  },
+
+  _listen: function() {
+    if (this._listening) {
+      return;
+    }
+
+    this.handleEvent = this.handleEvent.bind(this);
+    let content = shell.contentBrowser.contentWindow;
+    content.addEventListener("mozContentEvent", this, false, true);
+    this._listening = true;
+  },
+
+  handleEvent: function(event) {
+    let detail = event.detail;
+    if (detail.type !== "remote-debugger-prompt") {
+      return;
+    }
+    this._promptAnswer = detail.value;
+    this._promptDone = true;
+  },
+
+  initServer: function() {
+    if (DebuggerServer.initialized) {
+      return;
+    }
+
+    // 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 { RootActor } = devtools.require("devtools/server/actors/root");
+      let root = new RootActor(connection, parameters);
+      root.applicationType = "operating-system";
+      return root;
+    };
+
+#ifdef MOZ_WIDGET_GONK
+    DebuggerServer.on("connectionchange", function() {
+      AdbController.updateState();
+    });
+#endif
+  }
+};
+
+let USBRemoteDebugger = {
+
+  get isDebugging() {
+    if (!this._listener) {
+      return false;
+    }
+
+    return DebuggerServer._connections &&
+           Object.keys(DebuggerServer._connections).length > 0;
+  },
+
+  start: function() {
+    if (this._listener) {
+      return;
+    }
+
+    RemoteDebugger.initServer();
+
+    let portOrPath =
+      Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
+      "/data/local/debugger-socket";
+
+    try {
+      debug("Starting USB debugger on " + portOrPath);
+      this._listener = DebuggerServer.openListener(portOrPath);
+      // 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);
+    } catch (e) {
+      debug("Unable to start USB debugger server: " + e);
+    }
+  },
+
+  stop: function() {
+    if (!this._listener) {
+      return;
+    }
+
+    try {
+      this._listener.close();
+      this._listener = null;
+    } catch (e) {
+      debug("Unable to stop USB debugger server: " + e);
+    }
+  }
+
+};
+
+let WiFiRemoteDebugger = {
+
+  start: function() {
+    if (this._listener) {
+      return;
+    }
+
+    RemoteDebugger.initServer();
+
+    try {
+      debug("Starting WiFi debugger");
+      this._listener = DebuggerServer.openListener(-1);
+      let port = this._listener.port;
+      debug("Started WiFi debugger on " + port);
+      discovery.addService("devtools", { port: port });
+    } catch (e) {
+      debug("Unable to start WiFi debugger server: " + e);
+    }
+  },
+
+  stop: function() {
+    if (!this._listener) {
+      return;
+    }
+
+    try {
+      discovery.removeService("devtools");
+      this._listener.close();
+      this._listener = null;
+    } catch (e) {
+      debug("Unable to stop WiFi debugger server: " + e);
+    }
+  }
+
+};
+
+(function() {
+  // Track these separately here so we can determine the correct value for the
+  // pref "devtools.debugger.remote-enabled", which is true when either mode of
+  // using DevTools is enabled.
+  let devtoolsUSB = false;
+  let devtoolsWiFi = false;
+
+  // 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) {
+    devtoolsUSB = value;
+    Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+                               devtoolsUSB || devtoolsWiFi);
+    // This preference is consulted during startup
+    Services.prefs.savePrefFile(null);
+    try {
+      value ? USBRemoteDebugger.start() : USBRemoteDebugger.stop();
+    } catch(e) {
+      dump("Error while initializing USB devtools: " +
+           e + "\n" + e.stack + "\n");
+    }
+  });
+
+  SettingsListener.observe("debugger.remote-mode", "disabled", function(value) {
+    if (["disabled", "adb-only", "adb-devtools"].indexOf(value) == -1) {
+      dump("Illegal value for debugger.remote-mode: " + value + "\n");
+      return;
+    }
+
+    devtoolsUSB = value == "adb-devtools";
+    Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+                               devtoolsUSB || devtoolsWiFi);
+    // This preference is consulted during startup
+    Services.prefs.savePrefFile(null);
+
+    try {
+      (value == "adb-devtools") ? USBRemoteDebugger.start()
+                                : USBRemoteDebugger.stop();
+    } catch(e) {
+      dump("Error while initializing USB devtools: " +
+           e + "\n" + e.stack + "\n");
+    }
+
+#ifdef MOZ_WIDGET_GONK
+    AdbController.setRemoteDebuggerState(value != "disabled");
+#endif
+  });
+
+  SettingsListener.observe("devtools.remote.wifi.enabled", false,
+                           function(value) {
+    devtoolsWiFi = value;
+    Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+                               devtoolsUSB || devtoolsWiFi);
+    // Allow remote debugging on non-local interfaces when WiFi debug is enabled
+    // TODO: Bug 1034411: Lock down to WiFi interface, instead of all interfaces
+    Services.prefs.setBoolPref("devtools.debugger.force-local", !value);
+    // This preference is consulted during startup
+    Services.prefs.savePrefFile(null);
+
+    try {
+      value ? WiFiRemoteDebugger.start() : WiFiRemoteDebugger.stop();
+    } catch(e) {
+      dump("Error while initializing WiFi devtools: " +
+           e + "\n" + e.stack + "\n");
+    }
+  });
+})();
rename from b2g/chrome/content/devtools.js
rename to b2g/chrome/content/devtools/hud.js
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -1,14 +1,16 @@
 /* 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';
 
+// settings.js loads this file when the HUD setting is enabled.
+
 const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD';
 
 XPCOMUtils.defineLazyGetter(this, 'devtools', function() {
   const {devtools} = Cu.import('resource://gre/modules/devtools/Loader.jsm', {});
   return devtools;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() {
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -1,15 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 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;"
+"use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
@@ -171,349 +171,34 @@ Components.utils.import('resource://gre/
       'deviceinfo.hardware': hardware_info,
       'deviceinfo.firmware_revision': firmware_revision,
       'deviceinfo.product_model': product_model
     }
     lock.set(setting);
   }
 })();
 
-// =================== DevTools ====================
+// =================== DevTools HUD ====================
 
 let developerHUD;
 SettingsListener.observe('devtools.overlay', false, (value) => {
   if (value) {
     if (!developerHUD) {
       let scope = {};
-      Services.scriptloader.loadSubScript('chrome://b2g/content/devtools.js', scope);
+      Services.scriptloader.loadSubScript('chrome://b2g/content/devtools/hud.js', scope);
       developerHUD = scope.developerHUD;
     }
     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(
-        {'debugger.remote-mode': 'disabled'});
-    }
-  },
-
-  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 = USBRemoteDebugger.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
-
-(function() {
-  // Track these separately here so we can determine the correct value for the
-  // pref "devtools.debugger.remote-enabled", which is true when either mode of
-  // using DevTools is enabled.
-  let devtoolsUSB = false;
-  let devtoolsWiFi = false;
-
-  // 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) {
-    devtoolsUSB = value;
-    Services.prefs.setBoolPref('devtools.debugger.remote-enabled',
-                               devtoolsUSB || devtoolsWiFi);
-    // This preference is consulted during startup
-    Services.prefs.savePrefFile(null);
-    try {
-      value ? USBRemoteDebugger.start() : USBRemoteDebugger.stop();
-    } catch(e) {
-      dump("Error while initializing USB devtools: "
-           + e + "\n" + e.stack + "\n");
-    }
-  });
-
-  SettingsListener.observe('debugger.remote-mode', 'disabled', function(value) {
-    if (['disabled', 'adb-only', 'adb-devtools'].indexOf(value) == -1) {
-      dump('Illegal value for debugger.remote-mode: ' + value + '\n');
-      return;
-    }
-
-    devtoolsUSB = value == 'adb-devtools';
-    Services.prefs.setBoolPref('devtools.debugger.remote-enabled',
-                               devtoolsUSB || devtoolsWiFi);
-    // This preference is consulted during startup
-    Services.prefs.savePrefFile(null);
-
-    try {
-      (value == 'adb-devtools') ? USBRemoteDebugger.start()
-                                : USBRemoteDebugger.stop();
-    } catch(e) {
-      dump("Error while initializing USB devtools: "
-           + e + "\n" + e.stack + "\n");
-    }
-
-#ifdef MOZ_WIDGET_GONK
-    AdbController.setRemoteDebuggerState(value != 'disabled');
-#endif
-  });
-
-  SettingsListener.observe('devtools.remote.wifi.enabled', false,
-                           function(value) {
-    devtoolsWiFi = value;
-    Services.prefs.setBoolPref('devtools.debugger.remote-enabled',
-                               devtoolsUSB || devtoolsWiFi);
-    // Allow remote debugging on non-local interfaces when WiFi debug is enabled
-    // TODO: Bug 1034411: Lock down to WiFi interface, instead of all interfaces
-    Services.prefs.setBoolPref('devtools.debugger.force-local', !value);
-    // This preference is consulted during startup
-    Services.prefs.savePrefFile(null);
-
-    try {
-      value ? WiFiRemoteDebugger.start() : WiFiRemoteDebugger.stop();
-    } catch(e) {
-      dump("Error while initializing WiFi devtools: "
-           + e + "\n" + e.stack + "\n");
-    }
-  });
-})();
-
 // =================== 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,17 +27,24 @@
   <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
+  <!-- this file is only loaded on Gonk to manage ADB state -->
+  <script type="application/javascript;version=1.8"
+          src="chrome://b2g/content/devtools/adb.js"> </script>
 #endif
+  <!-- manages DevTools server state -->
+  <script type="application/javascript;version=1.8"
+          src="chrome://b2g/content/devtools/debugger.js"> </script>
 </head>
   <body id="container">
 #ifdef FXOS_SIMULATOR
 #ifndef MOZ_MULET
     <!--
      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
@@ -46,31 +46,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, 'devtools', function() {
-  const { devtools } =
-    Cu.import('resource://gre/modules/devtools/Loader.jsm', {});
-  return devtools;
-});
-
-XPCOMUtils.defineLazyGetter(this, 'discovery', function() {
-  return devtools.require('devtools/toolkit/discovery/discovery');
-});
-
 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");
@@ -725,19 +710,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 'inputmethod-update-layouts':
         KeyboardHelper.handleEvent(detail);
         break;
       case 'do-command':
         DoCommandHelper.handleEvent(detail.cmd);
@@ -855,182 +837,16 @@ let IndexedDBPromptHelper = {
 
     setTimeout(function() {
       observer.observe(null, responseTopic,
                        Ci.nsIPermissionManager.DENY_ACTION);
     }, 0);
   }
 }
 
-let RemoteDebugger = {
-  _promptDone: false,
-  _promptAnswer: 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;
-  },
-
-  initServer: function() {
-    if (DebuggerServer.initialized) {
-      return;
-    }
-
-    // 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 { RootActor } = devtools.require("devtools/server/actors/root");
-      let root = new RootActor(connection, parameters);
-      root.applicationType = "operating-system";
-      return root;
-    };
-
-#ifdef MOZ_WIDGET_GONK
-    DebuggerServer.on("connectionchange", function() {
-      AdbController.updateState();
-    });
-#endif
-  }
-};
-
-let USBRemoteDebugger = {
-
-  get isDebugging() {
-    if (!this._listener) {
-      return false;
-    }
-
-    return DebuggerServer._connections &&
-           Object.keys(DebuggerServer._connections).length > 0;
-  },
-
-  start: function() {
-    if (this._listener) {
-      return;
-    }
-
-    RemoteDebugger.initServer();
-
-    let portOrPath =
-      Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
-      "/data/local/debugger-socket";
-
-    try {
-      debug("Starting USB debugger on " + portOrPath);
-      this._listener = DebuggerServer.openListener(portOrPath);
-      // 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);
-    } catch (e) {
-      debug('Unable to start USB debugger server: ' + e);
-    }
-  },
-
-  stop: function() {
-    if (!this._listener) {
-      return;
-    }
-
-    try {
-      this._listener.close();
-      this._listener = null;
-    } catch (e) {
-      debug('Unable to stop USB debugger server: ' + e);
-    }
-  }
-
-};
-
-let WiFiRemoteDebugger = {
-
-  start: function() {
-    if (this._listener) {
-      return;
-    }
-
-    RemoteDebugger.initServer();
-
-    try {
-      debug("Starting WiFi debugger");
-      this._listener = DebuggerServer.openListener(-1);
-      let port = this._listener.port;
-      debug("Started WiFi debugger on " + port);
-      discovery.addService("devtools", { port: port });
-    } catch (e) {
-      debug('Unable to start WiFi debugger server: ' + e);
-    }
-  },
-
-  stop: function() {
-    if (!this._listener) {
-      return;
-    }
-
-    try {
-      discovery.removeService("devtools");
-      this._listener.close();
-      this._listener = null;
-    } catch (e) {
-      debug('Unable to stop WiFi debugger server: ' + e);
-    }
-  }
-
-};
-
 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
@@ -8,17 +8,21 @@ chrome.jar:
 % content branding %content/branding/
 % content b2g %content/
 
   content/arrow.svg                     (content/arrow.svg)
 * content/settings.js                   (content/settings.js)
 * content/shell.html                    (content/shell.html)
 * content/shell.js                      (content/shell.js)
   content/shell.css                     (content/shell.css)
-  content/devtools.js                   (content/devtools.js)
+#ifdef MOZ_WIDGET_GONK
+  content/devtools/adb.js               (content/devtools/adb.js)
+#endif
+* content/devtools/debugger.js          (content/devtools/debugger.js)
+  content/devtools/hud.js               (content/devtools/hud.js)
 #ifdef FXOS_SIMULATOR
   content/desktop.css                   (content/desktop.css)
   content/images/desktop/home-black.png (content/images/desktop/home-black.png)
   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)