/* -*- 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"); } }); })();