mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-03 18:47:53 +00:00
54f4f0e437
Differential Revision: https://phabricator.services.mozilla.com/D121768
293 lines
7.9 KiB
JavaScript
293 lines
7.9 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", "RemoteAgentFactory"];
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
Preferences: "resource://gre/modules/Preferences.jsm",
|
|
Services: "resource://gre/modules/Services.jsm",
|
|
|
|
CDP: "chrome://remote/content/cdp/CDP.jsm",
|
|
HttpServer: "chrome://remote/content/server/HTTPD.jsm",
|
|
Log: "chrome://remote/content/shared/Log.jsm",
|
|
WebDriverBiDi: "chrome://remote/content/webdriver-bidi/WebDriverBiDi.jsm",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "activeProtocols", () => {
|
|
const protocols = Services.prefs.getIntPref("remote.active-protocols");
|
|
if (protocols < 1 || protocols > 3) {
|
|
throw Error(`Invalid remote protocol identifier: ${protocols}`);
|
|
}
|
|
|
|
return protocols;
|
|
});
|
|
|
|
const WEBDRIVER_BIDI_ACTIVE = 0x1;
|
|
const CDP_ACTIVE = 0x2;
|
|
|
|
const DEFAULT_PORT = 9222;
|
|
// By default force local connections only
|
|
const LOOPBACKS = ["localhost", "127.0.0.1", "[::1]"];
|
|
const PREF_FORCE_LOCAL = "remote.force-local";
|
|
|
|
class RemoteAgentClass {
|
|
constructor() {
|
|
this.classID = Components.ID("{8f685a9d-8181-46d6-a71d-869289099c6d}");
|
|
this.helpInfo = ` --remote-debugging-port [<port>] Start the Firefox remote agent,
|
|
which is a low-level debugging interface based on the
|
|
CDP protocol. Defaults to listen on localhost:9222.\n`;
|
|
|
|
this._enabled = false;
|
|
this._port = DEFAULT_PORT;
|
|
this._server = null;
|
|
|
|
this._cdp = null;
|
|
this._webDriverBiDi = null;
|
|
}
|
|
|
|
get cdp() {
|
|
return this._cdp;
|
|
}
|
|
|
|
get debuggerAddress() {
|
|
if (!this.server) {
|
|
return "";
|
|
}
|
|
|
|
return `${this.host}:${this.port}`;
|
|
}
|
|
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
|
|
get host() {
|
|
// Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
|
|
// primary identity ("this.server.identity.primaryHost") is lazily set.
|
|
return this.server?._host;
|
|
}
|
|
|
|
get listening() {
|
|
return !!this.server && !this.server.isStopped();
|
|
}
|
|
|
|
get port() {
|
|
// Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
|
|
// primary identity ("this.server.identity.primaryPort") is lazily set.
|
|
return this.server?._port;
|
|
}
|
|
|
|
get scheme() {
|
|
return this.server?.identity.primaryScheme;
|
|
}
|
|
|
|
get server() {
|
|
return this._server;
|
|
}
|
|
|
|
get webDriverBiDi() {
|
|
return this._webDriverBiDi;
|
|
}
|
|
|
|
handle(cmdLine) {
|
|
// remote-debugging-port has to be consumed in nsICommandLineHandler:handle
|
|
// to avoid issues on macos. See Marionette.jsm::handle() for more details.
|
|
// TODO: remove after Bug 1724251 is fixed.
|
|
try {
|
|
cmdLine.handleFlagWithParam("remote-debugging-port", false);
|
|
} catch (e) {
|
|
cmdLine.handleFlag("remote-debugging-port", false);
|
|
}
|
|
}
|
|
|
|
async listen(url) {
|
|
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
|
throw Components.Exception(
|
|
"May only be instantiated in parent process",
|
|
Cr.NS_ERROR_LAUNCHED_CHILD_PROCESS
|
|
);
|
|
}
|
|
|
|
if (this.listening) {
|
|
return;
|
|
}
|
|
|
|
if (!(url instanceof Ci.nsIURI)) {
|
|
url = Services.io.newURI(url);
|
|
}
|
|
|
|
let { host, port } = url;
|
|
if (Preferences.get(PREF_FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
|
|
throw Components.Exception(
|
|
"Restricted to loopback devices",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE
|
|
);
|
|
}
|
|
|
|
// nsIServerSocket uses -1 for atomic port allocation
|
|
if (port === 0) {
|
|
port = -1;
|
|
}
|
|
|
|
try {
|
|
this._server = new HttpServer();
|
|
this.server._start(port, host);
|
|
|
|
Services.obs.notifyObservers(null, "remote-listening", true);
|
|
|
|
await this.cdp?.start();
|
|
await this.webDriverBiDi?.start();
|
|
} catch (e) {
|
|
await this.close();
|
|
logger.error(`Unable to start remote agent: ${e.message}`, e);
|
|
}
|
|
}
|
|
|
|
async close() {
|
|
if (!this.listening) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Stop the CDP support before stopping the server.
|
|
// Otherwise the HTTP server will fail to stop.
|
|
this.cdp?.stop();
|
|
this.webDriverBiDi?.stop();
|
|
|
|
await this.server.stop();
|
|
this._server = null;
|
|
Services.obs.notifyObservers(null, "remote-listening");
|
|
} catch (e) {
|
|
// this function must never fail
|
|
logger.error("unable to stop listener", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the --remote-debugging-port command line argument.
|
|
*
|
|
* @param {nsICommandLine} cmdLine
|
|
* Instance of the command line interface.
|
|
*
|
|
* @return {boolean}
|
|
* Return `true` if the command line argument has been found.
|
|
*/
|
|
handleRemoteDebuggingPortFlag(cmdLine) {
|
|
let enabled = false;
|
|
|
|
try {
|
|
// Catch cases when the argument, and a port have been specified.
|
|
const port = cmdLine.handleFlagWithParam("remote-debugging-port", false);
|
|
if (port !== null) {
|
|
enabled = true;
|
|
|
|
// In case of an invalid port keep the default port
|
|
const parsed = Number(port);
|
|
if (!isNaN(parsed)) {
|
|
this._port = parsed;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// If no port has been given check for the existence of the argument.
|
|
enabled = cmdLine.handleFlag("remote-debugging-port", false);
|
|
}
|
|
|
|
return enabled;
|
|
}
|
|
|
|
async observe(subject, topic) {
|
|
if (this.enabled) {
|
|
logger.trace(`Received observer notification ${topic}`);
|
|
}
|
|
|
|
switch (topic) {
|
|
case "profile-after-change":
|
|
Services.obs.addObserver(this, "command-line-startup");
|
|
break;
|
|
|
|
case "command-line-startup":
|
|
Services.obs.removeObserver(this, topic);
|
|
this._enabled = this.handleRemoteDebuggingPortFlag(subject);
|
|
|
|
if (this.enabled) {
|
|
Services.obs.addObserver(this, "remote-startup-requested");
|
|
}
|
|
|
|
// Ideally we should only enable the Remote Agent when the command
|
|
// line argument has been specified. But to allow Browser Chrome tests
|
|
// to run the Remote Agent and the supported protocols also need to be
|
|
// initialized.
|
|
|
|
// Listen for application shutdown to also shutdown the Remote Agent
|
|
// and a possible running instance of httpd.js.
|
|
Services.obs.addObserver(this, "quit-application");
|
|
|
|
// With Bug 1717899 we will extend the lifetime of the Remote Agent to
|
|
// the whole Firefox session, which will be identical to Marionette. For
|
|
// now prevent logging if the component is not enabled during startup.
|
|
if (
|
|
(activeProtocols & WEBDRIVER_BIDI_ACTIVE) ===
|
|
WEBDRIVER_BIDI_ACTIVE
|
|
) {
|
|
this._webDriverBiDi = new WebDriverBiDi(this);
|
|
if (this.enabled) {
|
|
logger.debug("WebDriver BiDi enabled");
|
|
}
|
|
}
|
|
|
|
if ((activeProtocols & CDP_ACTIVE) === CDP_ACTIVE) {
|
|
this._cdp = new CDP(this);
|
|
if (this.enabled) {
|
|
logger.debug("CDP enabled");
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case "remote-startup-requested":
|
|
Services.obs.removeObserver(this, topic);
|
|
|
|
try {
|
|
let address = Services.io.newURI(`http://localhost:${this._port}`);
|
|
await this.listen(address);
|
|
} catch (e) {
|
|
throw Error(`Unable to start remote agent: ${e}`);
|
|
}
|
|
|
|
break;
|
|
|
|
case "quit-application":
|
|
Services.obs.removeObserver(this, "quit-application");
|
|
|
|
this.close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// XPCOM
|
|
|
|
get QueryInterface() {
|
|
return ChromeUtils.generateQI([
|
|
"nsICommandLineHandler",
|
|
"nsIObserver",
|
|
"nsIRemoteAgent",
|
|
]);
|
|
}
|
|
}
|
|
|
|
var RemoteAgent = new RemoteAgentClass();
|
|
|
|
// This is used by the XPCOM codepath which expects a constructor
|
|
var RemoteAgentFactory = function() {
|
|
return RemoteAgent;
|
|
};
|