Bug 1039493 - Break out B2G DevTools files. r=fabrice
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 18 Aug 2014 15:40:00 -0400
changeset 200310 63aa6e5dd802c679d2fc50f039126074304cc316
parent 200309 3ed68f04121af8da39df9114ed86f10674ffab48
child 200311 c0d776bda527d55d542213a50eb98503c2ab8058
push id27340
push userryanvm@gmail.com
push dateTue, 19 Aug 2014 19:54:03 +0000
treeherdermozilla-central@f49acde26893 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1039493
milestone34.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 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)