gecko-dev/remote/RemoteAgent.jsm
Andreas Tolfsen 5485cb24b1 bug 1551188: remote: print listening address similarly to chrome; r=remote-protocol-reviewers,ochameau
Puppeteer parses stderr looking for the regular expression
^DevTools listening on (ws:\/\/.*)$.  For Puppeteer to be able
to connect to Firefox, we need to change the line we print slightly
to conform with this expression.

The remote agent also uses Log.jsm to print it, but we cannot rely
on logging always being enabled, e.g. if remote.log.level is set
to Warn or above.  For this reason we should use dump().

The patch also instantiates the main target before starting the HTTPD.

Differential Revision: https://phabricator.services.mozilla.com/D30913

--HG--
extra : moz-landing-system : lando
2019-05-13 14:43:21 +00:00

220 lines
6.1 KiB
JavaScript

/* 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";
var EXPORTED_SYMBOLS = ["RemoteAgent"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
FatalError: "chrome://remote/content/Error.jsm",
HttpServer: "chrome://remote/content/server/HTTPD.jsm",
JSONHandler: "chrome://remote/content/JSONHandler.jsm",
Log: "chrome://remote/content/Log.jsm",
NetUtil: "resource://gre/modules/NetUtil.jsm",
Observer: "chrome://remote/content/Observer.jsm",
Preferences: "resource://gre/modules/Preferences.jsm",
RecommendedPreferences: "chrome://remote/content/RecommendedPreferences.jsm",
TabObserver: "chrome://remote/content/WindowManager.jsm",
Targets: "chrome://remote/content/targets/Targets.jsm",
});
XPCOMUtils.defineLazyGetter(this, "log", Log.get);
const ENABLED = "remote.enabled";
const FORCE_LOCAL = "remote.force-local";
const DEFAULT_HOST = "localhost";
const DEFAULT_PORT = 9222;
const LOOPBACKS = ["localhost", "127.0.0.1", "[::1]"];
class RemoteAgentClass {
init() {
if (!Preferences.get(ENABLED, false)) {
throw new Error("Remote agent is disabled by its preference");
}
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
throw new Error("Remote agent can only be instantiated from the parent process");
}
if (this.server) {
return;
}
this.server = new HttpServer();
this.targets = new Targets();
this.server.registerPrefixHandler("/json/", new JSONHandler(this));
this.tabs = new TabObserver({registerExisting: true});
this.tabs.on("open", (eventName, tab) => {
this.targets.connect(tab.linkedBrowser);
});
this.tabs.on("close", (eventName, tab) => {
this.targets.disconnect(tab.linkedBrowser);
});
this.targets.on("connect", (eventName, target) => {
if (!target.path) {
throw new Error(`Target is missing 'path' attribute: ${target}`);
}
this.server.registerPathHandler(target.path, target);
});
this.targets.on("disconnect", (eventName, target) => {
// TODO(ato): removing a handler is currently not possible
});
}
get listening() {
return !!this.server && !this.server._socketClosed;
}
async listen(address) {
if (!(address instanceof Ci.nsIURI)) {
throw new TypeError(`Expected nsIURI: ${address}`);
}
let {host, port} = address;
if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
throw new Error("Restricted to loopback devices");
}
// nsIServerSocket uses -1 for atomic port allocation
if (port === 0) {
port = -1;
}
if (this.listening) {
return;
}
this.init();
await this.tabs.start();
try {
// Immediatly instantiate the main process target in order
// to be accessible via HTTP endpoint on startup
const mainTarget = this.targets.getMainProcessTarget();
this.server._start(port, host);
dump(`DevTools listening on ${mainTarget.wsDebuggerURL}`);
} catch (e) {
throw new Error(`Unable to start remote agent: ${e.message}`, e);
}
Preferences.set(RecommendedPreferences);
}
async close() {
if (this.listening) {
try {
// Disconnect the targets first in order to ensure closing all pending
// connection first. Otherwise Httpd's stop is not going to resolve.
this.targets.clear();
await this.server.stop();
Preferences.reset(Object.keys(RecommendedPreferences));
this.tabs.stop();
} catch (e) {
throw new Error(`Unable to stop agent: ${e.message}`, e);
}
log.info("Stopped listening");
}
}
get scheme() {
if (!this.server) {
return null;
}
return this.server.identity.primaryScheme;
}
get host() {
if (!this.server) {
return null;
}
return this.server.identity.primaryHost;
}
get port() {
if (!this.server) {
return null;
}
return this.server.identity.primaryPort;
}
// nsICommandLineHandler
async handle(cmdLine) {
function flag(name) {
const caseSensitive = true;
try {
return cmdLine.handleFlagWithParam(name, caseSensitive);
} catch (e) {
return cmdLine.handleFlag(name, caseSensitive);
}
}
const remoteDebugger = flag("remote-debugger");
const remoteDebuggingPort = flag("remote-debugging-port");
if (remoteDebugger && remoteDebuggingPort) {
log.fatal("Conflicting flags --remote-debugger and --remote-debugging-port");
cmdLine.preventDefault = true;
return;
}
if (!remoteDebugger && !remoteDebuggingPort) {
return;
}
let host, port;
if (typeof remoteDebugger == "string") {
[host, port] = remoteDebugger.split(":");
} else if (typeof remoteDebuggingPort == "string") {
port = remoteDebuggingPort;
}
let addr;
try {
addr = NetUtil.newURI(`http://${host || DEFAULT_HOST}:${port || DEFAULT_PORT}/`);
} catch (e) {
log.fatal(`Expected address syntax [<host>]:<port>: ${remoteDebugger || remoteDebuggingPort}`);
cmdLine.preventDefault = true;
return;
}
this.init();
await Observer.once("sessionstore-windows-restored");
try {
this.listen(addr);
} catch (e) {
this.close();
throw new FatalError(`Unable to start remote agent on ${addr.spec}: ${e.message}`, e);
}
}
get helpInfo() {
return " --remote-debugger [<host>][:<port>]\n" +
" --remote-debugging-port <port> Start the Firefox remote agent, which is \n" +
" a low-level debugging interface based on the CDP protocol.\n" +
" Defaults to listen on localhost:9222.\n";
}
// XPCOM
get QueryInterface() {
return ChromeUtils.generateQI([Ci.nsICommandLineHandler]);
}
}
var RemoteAgent = new RemoteAgentClass();