diff options
Diffstat (limited to 'b2g/chrome/content/devtools/debugger.js')
-rw-r--r-- | b2g/chrome/content/devtools/debugger.js | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/b2g/chrome/content/devtools/debugger.js b/b2g/chrome/content/devtools/debugger.js new file mode 100644 index 000000000..11987a839 --- /dev/null +++ b/b2g/chrome/content/devtools/debugger.js @@ -0,0 +1,397 @@ +/* -*- 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, "devtools", function() { + const { devtools } = + Cu.import("resource://devtools/shared/Loader.jsm", {}); + return devtools; +}); + +XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function() { + const { DebuggerServer } = devtools.require("devtools/server/main"); + return DebuggerServer; +}); + +XPCOMUtils.defineLazyGetter(this, "B2GTabList", function() { + const { B2GTabList } = + devtools.require("resource://gre/modules/DebuggerActors.js"); + return B2GTabList; +}); + +// Load the discovery module eagerly, so that it can set a device name at +// startup. This does not cause discovery to start listening for packets, as +// that only happens once DevTools is enabled. +devtools.require("devtools/shared/discovery/discovery"); + +var RemoteDebugger = { + _listening: false, + + /** + * Prompt the user to accept or decline the incoming connection. + * + * @param session object + * The session object will contain at least the following fields: + * { + * authentication, + * client: { + * host, + * port + * }, + * server: { + * host, + * port + * } + * } + * Specific authentication modes may include additional fields. Check + * the different |allowConnection| methods in + * devtools/shared/security/auth.js. + * @return An AuthenticationResult value. + * A promise that will be resolved to the above is also allowed. + */ + allowConnection(session) { + if (this._promptingForAllow) { + // Don't stack connection prompts if one is already open + return DebuggerServer.AuthenticationResult.DENY; + } + this._listen(); + + this._promptingForAllow = new Promise(resolve => { + this._handleAllowResult = detail => { + this._handleAllowResult = null; + this._promptingForAllow = null; + // Newer Gaia supplies |authResult|, which is one of the + // AuthenticationResult values. + if (detail.authResult) { + resolve(detail.authResult); + } else if (detail.value) { + resolve(DebuggerServer.AuthenticationResult.ALLOW); + } else { + resolve(DebuggerServer.AuthenticationResult.DENY); + } + }; + + shell.sendChromeEvent({ + type: "remote-debugger-prompt", + session + }); + }); + + return this._promptingForAllow; + }, + + /** + * During OOB_CERT authentication, the user must transfer some data through some + * out of band mechanism from the client to the server to authenticate the + * devices. + * + * This implementation instructs Gaia to continually capture images which are + * passed back here and run through a QR decoder. + * + * @return An object containing: + * * sha256: hash(ClientCert) + * * k : K(random 128-bit number) + * A promise that will be resolved to the above is also allowed. + */ + receiveOOB() { + if (this._receivingOOB) { + return this._receivingOOB; + } + this._listen(); + + const QR = devtools.require("devtools/shared/qrcode/index"); + this._receivingOOB = new Promise((resolve, reject) => { + this._handleAuthEvent = detail => { + debug(detail.action); + if (detail.action === "abort") { + this._handleAuthEvent = null; + this._receivingOOB = null; + reject(); + return; + } + + if (detail.action !== "capture") { + return; + } + + let url = detail.url; + QR.decodeFromURI(url).then(data => { + debug("Got auth data: " + data); + let oob = JSON.parse(data); + + shell.sendChromeEvent({ + type: "devtools-auth", + action: "stop" + }); + + this._handleAuthEvent = null; + this._receivingOOB = null; + resolve(oob); + }).catch(() => { + debug("No auth data, requesting new capture"); + shell.sendChromeEvent({ + type: "devtools-auth", + action: "capture" + }); + }); + }; + + // Show QR scanning dialog, get an initial capture + shell.sendChromeEvent({ + type: "devtools-auth", + action: "start" + }); + }); + + return this._receivingOOB; + }, + + _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" && this._handleAllowResult) { + this._handleAllowResult(detail); + } + if (detail.type === "devtools-auth" && this._handleAuthEvent) { + this._handleAuthEvent(detail); + } + }, + + initServer: function() { + if (DebuggerServer.initialized) { + return; + } + + // Ask for remote connections. + DebuggerServer.init(); + + // /!\ 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); + + // Allow debugging of chrome for any process + if (!restrictPrivileges) { + DebuggerServer.allowChromeProcess = true; + } + + /** + * 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 parameters = { + tabList: new B2GTabList(connection), + // Use an explicit global actor list to prevent exposing + // unexpected actors + globalActorFactories: restrictPrivileges ? { + webappsActor: DebuggerServer.globalActorFactories.webappsActor, + deviceActor: DebuggerServer.globalActorFactories.deviceActor, + settingsActor: DebuggerServer.globalActorFactories.settingsActor + } : DebuggerServer.globalActorFactories + }; + let { RootActor } = devtools.require("devtools/server/actors/root"); + let root = new RootActor(connection, parameters); + root.applicationType = "operating-system"; + return root; + }; + + if (isGonk) { + DebuggerServer.on("connectionchange", function() { + AdbController.updateState(); + }); + } + } +}; + +RemoteDebugger.allowConnection = + RemoteDebugger.allowConnection.bind(RemoteDebugger); +RemoteDebugger.receiveOOB = + RemoteDebugger.receiveOOB.bind(RemoteDebugger); + +var 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); + let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT"); + let authenticator = new AuthenticatorType.Server(); + authenticator.allowConnection = RemoteDebugger.allowConnection; + this._listener = DebuggerServer.createListener(); + this._listener.portOrPath = portOrPath; + this._listener.authenticator = authenticator; + this._listener.open(); + // 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); + } + } + +}; + +var WiFiRemoteDebugger = { + + start: function() { + if (this._listener) { + return; + } + + RemoteDebugger.initServer(); + + try { + debug("Starting WiFi debugger"); + let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT"); + let authenticator = new AuthenticatorType.Server(); + authenticator.allowConnection = RemoteDebugger.allowConnection; + authenticator.receiveOOB = RemoteDebugger.receiveOOB; + this._listener = DebuggerServer.createListener(); + this._listener.portOrPath = -1 /* any available port */; + this._listener.authenticator = authenticator; + this._listener.discoverable = true; + this._listener.encryption = true; + this._listener.open(); + let port = this._listener.port; + debug("Started WiFi debugger on " + port); + } catch (e) { + debug("Unable to start WiFi debugger server: " + e); + } + }, + + stop: function() { + if (!this._listener) { + return; + } + + try { + 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"); + } + + isGonk && AdbController.setRemoteDebuggerState(value != "disabled"); + }); + + 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"); + } + }); +})(); |