mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-21 09:49:14 +00:00
Backed out 2 changesets (bug 1720676) for causing remote failures CLOSED TREE
Backed out changeset 3c2599c00332 (bug 1720676) Backed out changeset b58937c59f63 (bug 1720676)
This commit is contained in:
parent
9009ea58e0
commit
a5ebee57bb
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1950,6 +1950,7 @@ dependencies = [
|
||||
"processtools",
|
||||
"profiler_helper",
|
||||
"qcms",
|
||||
"remote",
|
||||
"rlbox_lucet_sandbox",
|
||||
"rsdparsa_capi",
|
||||
"rusqlite",
|
||||
@ -4051,6 +4052,19 @@ version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remote"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"libc",
|
||||
"log",
|
||||
"nserror",
|
||||
"nsstring",
|
||||
"thiserror",
|
||||
"xpcom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
@ -197,6 +197,8 @@
|
||||
|
||||
; WebDriver (Marionette, Remote Agent) remote protocols
|
||||
#ifdef ENABLE_WEBDRIVER
|
||||
@RESPATH@/components/marionette.manifest
|
||||
@RESPATH@/components/marionette.js
|
||||
@RESPATH@/chrome/remote@JAREXT@
|
||||
@RESPATH@/chrome/remote.manifest
|
||||
#endif
|
||||
|
@ -208,6 +208,8 @@
|
||||
|
||||
; WebDriver (Marionette, Remote Agent) remote protocols
|
||||
#ifdef ENABLE_WEBDRIVER
|
||||
@BINPATH@/components/marionette.manifest
|
||||
@BINPATH@/components/marionette.js
|
||||
@BINPATH@/chrome/remote@JAREXT@
|
||||
@BINPATH@/chrome/remote.manifest
|
||||
#endif
|
||||
|
@ -11,6 +11,8 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
|
||||
JSONHandler: "chrome://remote/content/cdp/JSONHandler.jsm",
|
||||
RecommendedPreferences:
|
||||
"chrome://remote/content/shared/RecommendedPreferences.jsm",
|
||||
@ -83,7 +85,11 @@ class CDP {
|
||||
|
||||
await this.targetList.watchForTargets();
|
||||
|
||||
Cu.printStderr(`DevTools listening on ${this.address}\n`);
|
||||
Services.obs.notifyObservers(
|
||||
null,
|
||||
"remote-listening",
|
||||
`DevTools listening on ${this.address}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,7 @@ const { Preferences } = ChromeUtils.import(
|
||||
);
|
||||
|
||||
// To fully test the Remote Agent's capabilities an instance of the interface
|
||||
// also needs to be used.
|
||||
// needs to be used. This refers to the Rust specific implementation.
|
||||
const remoteAgentInstance = Cc["@mozilla.org/remote/agent;1"].createInstance(
|
||||
Ci.nsIRemoteAgent
|
||||
);
|
||||
@ -49,23 +49,28 @@ add_agent_task(async function listening() {
|
||||
is(remoteAgentInstance.listening, true, "Agent is listening");
|
||||
});
|
||||
|
||||
add_agent_task(async function remoteListeningNotification() {
|
||||
let active;
|
||||
add_agent_task(async function listen() {
|
||||
const port = getNonAtomicFreePort();
|
||||
|
||||
let boundURL;
|
||||
function observer(subject, topic, data) {
|
||||
const prefix = "DevTools listening on ";
|
||||
if (!data.startsWith(prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
|
||||
active = data;
|
||||
boundURL = Services.io.newURI(data.split(prefix)[1]);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(observer, "remote-listening");
|
||||
|
||||
await RemoteAgent.listen("http://localhost:" + port);
|
||||
is(active, "true", "remote-listening observer notified enabled state");
|
||||
|
||||
Services.obs.addObserver(observer, "remote-listening");
|
||||
await RemoteAgent.close();
|
||||
is(active, null, "remote-listening observer notified disabled state");
|
||||
isnot(boundURL, undefined, "remote-listening observer notified");
|
||||
is(
|
||||
boundURL.port,
|
||||
port,
|
||||
`expected default port ${port}, got ${boundURL.port}`
|
||||
);
|
||||
});
|
||||
|
||||
// TODO(ato): https://bugzil.la/1590829
|
||||
|
@ -42,9 +42,11 @@ const add_plain_task = add_task.bind(this);
|
||||
// Start RemoteAgent lazily and reuse it for all the tests in the suite.
|
||||
// Starting and stopping RemoteAgent for every test would trigger race conditions
|
||||
// in httpd.js. See Bug 1609162.
|
||||
let remoteAgentStarted = false;
|
||||
async function startRemoteAgent() {
|
||||
if (!RemoteAgent.listening) {
|
||||
if (!remoteAgentStarted) {
|
||||
await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
|
||||
remoteAgentStarted = true;
|
||||
info("Remote agent started");
|
||||
}
|
||||
}
|
||||
|
@ -34,28 +34,27 @@ XPCOMUtils.defineLazyGetter(this, "activeProtocols", () => {
|
||||
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.server = null;
|
||||
|
||||
this._enabled = false;
|
||||
this._port = DEFAULT_PORT;
|
||||
this._server = null;
|
||||
if ((activeProtocols & WEBDRIVER_BIDI_ACTIVE) === WEBDRIVER_BIDI_ACTIVE) {
|
||||
this.webDriverBiDi = new WebDriverBiDi(this);
|
||||
logger.debug("WebDriver BiDi enabled");
|
||||
} else {
|
||||
this.webDriverBiDi = null;
|
||||
}
|
||||
|
||||
this._cdp = null;
|
||||
this._webDriverBiDi = null;
|
||||
}
|
||||
|
||||
get cdp() {
|
||||
return this._cdp;
|
||||
if ((activeProtocols & CDP_ACTIVE) === CDP_ACTIVE) {
|
||||
this.cdp = new CDP(this);
|
||||
logger.debug("CDP enabled");
|
||||
} else {
|
||||
this.cdp = null;
|
||||
}
|
||||
}
|
||||
|
||||
get debuggerAddress() {
|
||||
@ -66,10 +65,6 @@ class RemoteAgentClass {
|
||||
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.
|
||||
@ -90,15 +85,7 @@ class RemoteAgentClass {
|
||||
return this.server?.identity.primaryScheme;
|
||||
}
|
||||
|
||||
get server() {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
get webDriverBiDi() {
|
||||
return this._webDriverBiDi;
|
||||
}
|
||||
|
||||
async listen(url) {
|
||||
listen(url) {
|
||||
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
||||
throw Components.Exception(
|
||||
"May only be instantiated in parent process",
|
||||
@ -107,7 +94,7 @@ class RemoteAgentClass {
|
||||
}
|
||||
|
||||
if (this.listening) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!(url instanceof Ci.nsIURI)) {
|
||||
@ -127,11 +114,14 @@ class RemoteAgentClass {
|
||||
port = -1;
|
||||
}
|
||||
|
||||
try {
|
||||
this._server = new HttpServer();
|
||||
this.server._start(port, host);
|
||||
this.server = new HttpServer();
|
||||
|
||||
Services.obs.notifyObservers(null, "remote-listening", true);
|
||||
return this.asyncListen(host, port);
|
||||
}
|
||||
|
||||
async asyncListen(host, port) {
|
||||
try {
|
||||
this.server._start(port, host);
|
||||
|
||||
await this.cdp?.start();
|
||||
await this.webDriverBiDi?.start();
|
||||
@ -141,134 +131,30 @@ class RemoteAgentClass {
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (!this.listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
close() {
|
||||
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");
|
||||
if (this.listening) {
|
||||
return this.server.stop();
|
||||
}
|
||||
} 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);
|
||||
} finally {
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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);
|
||||
|
||||
// Listen for application shutdown to also shutdown the Remote Agent
|
||||
Services.obs.addObserver(this, "quit-application");
|
||||
|
||||
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;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// XPCOM
|
||||
|
||||
get QueryInterface() {
|
||||
return ChromeUtils.generateQI([
|
||||
"nsICommandLineHandler",
|
||||
"nsIObserver",
|
||||
"nsIRemoteAgent",
|
||||
]);
|
||||
return ChromeUtils.generateQI(["nsIRemoteAgent"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,27 +3,17 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Classes = [
|
||||
# Remote Agent
|
||||
{
|
||||
"cid": "{8f685a9d-8181-46d6-a71d-869289099c6d}",
|
||||
"contract_ids": ["@mozilla.org/remote/agent;1"],
|
||||
"categories": {
|
||||
"command-line-handler": "m-remote",
|
||||
"profile-after-change": "RemoteAgent",
|
||||
},
|
||||
"jsm": "chrome://remote/content/components/RemoteAgent.jsm",
|
||||
"constructor": "RemoteAgentFactory",
|
||||
},
|
||||
|
||||
# Marionette
|
||||
{
|
||||
"cid": "{786a1369-dca5-4adc-8486-33d23c88010a}",
|
||||
"contract_ids": ["@mozilla.org/remote/marionette;1"],
|
||||
"categories": {
|
||||
"command-line-handler": "m-marionette",
|
||||
"profile-after-change": "Marionette",
|
||||
},
|
||||
"jsm": "chrome://remote/content/components/Marionette.jsm",
|
||||
"constructor": "MarionetteFactory",
|
||||
"cid": "{0d1bb02e-ac91-4904-b61d-97da83ebf6fb}",
|
||||
"contract_ids": ["@mozilla.org/commandlinehandler/general-startup;1?type=remote"],
|
||||
"categories": {"command-line-handler": "m-remote"},
|
||||
"headers": ["RemoteAgentHandler.h"],
|
||||
"constructor": "GetRemoteAgentHandler",
|
||||
},
|
||||
]
|
||||
|
@ -4,14 +4,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Marionette", "MarionetteFactory"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
|
||||
|
||||
EnvironmentPrefs: "chrome://remote/content/marionette/prefs.js",
|
||||
Log: "chrome://remote/content/shared/Log.jsm",
|
||||
MarionettePrefs: "chrome://remote/content/marionette/prefs.js",
|
||||
@ -69,9 +69,6 @@ class MarionetteParentProcess {
|
||||
constructor() {
|
||||
this.server = null;
|
||||
|
||||
this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
|
||||
this.helpInfo = " --marionette Enable remote control server.\n";
|
||||
|
||||
// holds reference to ChromeWindow
|
||||
// used to run GFX sanity tests on Windows
|
||||
this.gfxWindow = null;
|
||||
@ -303,10 +300,6 @@ class MarionetteParentProcess {
|
||||
}
|
||||
|
||||
class MarionetteContentProcess {
|
||||
constructor() {
|
||||
this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
|
||||
}
|
||||
|
||||
get running() {
|
||||
let reply = Services.cpmm.sendSyncMessage("Marionette:IsRunning");
|
||||
if (reply.length == 0) {
|
||||
@ -321,14 +314,37 @@ class MarionetteContentProcess {
|
||||
}
|
||||
}
|
||||
|
||||
var Marionette;
|
||||
if (isRemote) {
|
||||
Marionette = new MarionetteContentProcess();
|
||||
} else {
|
||||
Marionette = new MarionetteParentProcess();
|
||||
}
|
||||
const MarionetteFactory = {
|
||||
instance_: null,
|
||||
|
||||
// This is used by the XPCOM codepath which expects a constructor
|
||||
const MarionetteFactory = function() {
|
||||
return Marionette;
|
||||
createInstance(outer, iid) {
|
||||
if (outer) {
|
||||
throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
|
||||
}
|
||||
|
||||
if (!this.instance_) {
|
||||
if (isRemote) {
|
||||
this.instance_ = new MarionetteContentProcess();
|
||||
} else {
|
||||
this.instance_ = new MarionetteParentProcess();
|
||||
}
|
||||
}
|
||||
|
||||
return this.instance_.QueryInterface(iid);
|
||||
},
|
||||
};
|
||||
|
||||
function Marionette() {}
|
||||
|
||||
Marionette.prototype = {
|
||||
classDescription: "Marionette component",
|
||||
classID: Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}"),
|
||||
contractID: "@mozilla.org/remote/marionette;1",
|
||||
|
||||
/* eslint-disable-next-line camelcase */
|
||||
_xpcom_factory: MarionetteFactory,
|
||||
|
||||
helpInfo: " --marionette Enable remote control server.\n",
|
||||
};
|
||||
|
||||
this.NSGetFactory = ComponentUtils.generateNSGetFactory([Marionette]);
|
4
remote/components/marionette.manifest
Normal file
4
remote/components/marionette.manifest
Normal file
@ -0,0 +1,4 @@
|
||||
component {786a1369-dca5-4adc-8486-33d23c88010a} marionette.js
|
||||
contract @mozilla.org/remote/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
|
||||
category command-line-handler b-marionette @mozilla.org/remote/marionette;1
|
||||
category profile-after-change Marionette @mozilla.org/remote/marionette;1
|
@ -2,6 +2,15 @@
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
"rust",
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
"marionette.js",
|
||||
"marionette.manifest",
|
||||
]
|
||||
|
||||
XPIDL_MODULE = "remote"
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
@ -11,7 +20,7 @@ XPIDL_SOURCES += [
|
||||
|
||||
XPCOM_MANIFESTS += ["components.conf"]
|
||||
|
||||
with Files("Marionette.jsm"):
|
||||
with Files("marionette.*"):
|
||||
BUG_COMPONENT = ("Testing", "Marionette")
|
||||
with Files("nsIMarionette.idl"):
|
||||
BUG_COMPONENT = ("Testing", "Marionette")
|
||||
|
12
remote/components/rust/Cargo.toml
Normal file
12
remote/components/rust/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "remote"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
http = "0.2"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
thiserror = "1"
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
36
remote/components/rust/RemoteAgentHandler.cpp
Normal file
36
remote/components/rust/RemoteAgentHandler.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsICommandLineHandler.h"
|
||||
#include "nsISupportsUtils.h"
|
||||
|
||||
#include "RemoteAgentHandler.h"
|
||||
|
||||
// anonymous namespace prevents outside C++ code
|
||||
// from improperly accessing these implementation details
|
||||
namespace {
|
||||
extern "C" {
|
||||
// implemented in Rust, see handler.rs
|
||||
void new_remote_agent_handler(nsICommandLineHandler** result);
|
||||
}
|
||||
|
||||
static mozilla::StaticRefPtr<nsICommandLineHandler> sHandler;
|
||||
} // namespace
|
||||
|
||||
already_AddRefed<nsICommandLineHandler> GetRemoteAgentHandler() {
|
||||
nsCOMPtr<nsICommandLineHandler> handler;
|
||||
|
||||
if (sHandler) {
|
||||
handler = sHandler;
|
||||
} else {
|
||||
new_remote_agent_handler(getter_AddRefs(handler));
|
||||
sHandler = handler;
|
||||
mozilla::ClearOnShutdown(&sHandler);
|
||||
}
|
||||
|
||||
return handler.forget();
|
||||
}
|
12
remote/components/rust/RemoteAgentHandler.h
Normal file
12
remote/components/rust/RemoteAgentHandler.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_remote_startup_RemoteAgentHandler_h
|
||||
#define mozilla_remote_startup_RemoteAgentHandler_h
|
||||
|
||||
#include "nsICommandLineHandler.h"
|
||||
|
||||
already_AddRefed<nsICommandLineHandler> GetRemoteAgentHandler();
|
||||
|
||||
#endif // mozilla_remote_startup_RemoteAgentHandler_h
|
7
remote/components/rust/moz.build
Normal file
7
remote/components/rust/moz.build
Normal file
@ -0,0 +1,7 @@
|
||||
# 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/.
|
||||
|
||||
EXPORTS += ["RemoteAgentHandler.h"]
|
||||
UNIFIED_SOURCES += ["RemoteAgentHandler.cpp"]
|
||||
FINAL_LIBRARY = "xul"
|
61
remote/components/rust/src/error.rs
Normal file
61
remote/components/rust/src/error.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// 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 std::num;
|
||||
|
||||
use http;
|
||||
use nserror::{
|
||||
nsresult, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG, NS_ERROR_LAUNCHED_CHILD_PROCESS,
|
||||
NS_ERROR_NOT_AVAILABLE,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RemoteAgentError {
|
||||
#[error("expected address syntax [<host>]:<port>: {0}")]
|
||||
AddressSpec(#[from] http::uri::InvalidUri),
|
||||
|
||||
#[error("may only be instantiated in parent process")]
|
||||
ChildProcess,
|
||||
|
||||
#[error("invalid port: {0}")]
|
||||
InvalidPort(#[from] num::ParseIntError),
|
||||
|
||||
#[error("listener restricted to loopback devices")]
|
||||
LoopbackRestricted,
|
||||
|
||||
#[error("missing port number")]
|
||||
MissingPort,
|
||||
|
||||
#[error("unavailable")]
|
||||
Unavailable,
|
||||
|
||||
#[error("error result {0}")]
|
||||
XpCom(#[source] nsresult),
|
||||
}
|
||||
|
||||
impl From<RemoteAgentError> for nsresult {
|
||||
fn from(err: RemoteAgentError) -> nsresult {
|
||||
use RemoteAgentError::*;
|
||||
match err {
|
||||
AddressSpec(_) | InvalidPort(_) => NS_ERROR_INVALID_ARG,
|
||||
ChildProcess => NS_ERROR_LAUNCHED_CHILD_PROCESS,
|
||||
LoopbackRestricted => NS_ERROR_ILLEGAL_VALUE,
|
||||
MissingPort => NS_ERROR_INVALID_ARG,
|
||||
Unavailable => NS_ERROR_NOT_AVAILABLE,
|
||||
XpCom(result) => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nsresult> for RemoteAgentError {
|
||||
fn from(result: nsresult) -> Self {
|
||||
use RemoteAgentError::*;
|
||||
match result {
|
||||
NS_ERROR_NOT_AVAILABLE => Unavailable,
|
||||
NS_ERROR_LAUNCHED_CHILD_PROCESS => ChildProcess,
|
||||
x => RemoteAgentError::XpCom(x),
|
||||
}
|
||||
}
|
||||
}
|
242
remote/components/rust/src/handler.rs
Normal file
242
remote/components/rust/src/handler.rs
Normal file
@ -0,0 +1,242 @@
|
||||
// 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 std::cell::RefCell;
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
use log::*;
|
||||
use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG, NS_OK};
|
||||
use nsstring::{nsACString, nsCString, nsString};
|
||||
use xpcom::interfaces::{nsICommandLine, nsICommandLineHandler, nsIObserverService, nsISupports};
|
||||
use xpcom::{xpcom, xpcom_method, RefPtr};
|
||||
|
||||
use crate::{
|
||||
RemoteAgent,
|
||||
RemoteAgentError::{self, *},
|
||||
RemoteAgentResult, DEFAULT_HOST, DEFAULT_PORT,
|
||||
};
|
||||
|
||||
macro_rules! fatalln {
|
||||
($($arg:tt)*) => ({
|
||||
let p = prog().unwrap_or("gecko".to_string());
|
||||
eprintln!("{}: {}", p, format_args!($($arg)*));
|
||||
panic!();
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_remote_agent_handler(result: *mut *const nsICommandLineHandler) {
|
||||
if let Ok(handler) = RemoteAgentHandler::new() {
|
||||
RefPtr::new(handler.coerce::<nsICommandLineHandler>()).forget(&mut *result);
|
||||
} else {
|
||||
*result = std::ptr::null();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[xpimplements(nsICommandLineHandler)]
|
||||
#[xpimplements(nsIObserver)]
|
||||
#[refcnt = "atomic"]
|
||||
struct InitRemoteAgentHandler {
|
||||
agent: RemoteAgent,
|
||||
observer: RefPtr<nsIObserverService>,
|
||||
address: RefCell<String>,
|
||||
}
|
||||
|
||||
impl RemoteAgentHandler {
|
||||
pub fn new() -> Result<RefPtr<Self>, RemoteAgentError> {
|
||||
let agent = RemoteAgent::get()?;
|
||||
let observer = xpcom::services::get_ObserverService().ok_or(Unavailable)?;
|
||||
Ok(Self::allocate(InitRemoteAgentHandler {
|
||||
agent,
|
||||
observer,
|
||||
address: RefCell::new(String::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
xpcom_method!(handle => Handle(command_line: *const nsICommandLine));
|
||||
fn handle(&self, command_line: &nsICommandLine) -> Result<(), nsresult> {
|
||||
match self.handle_inner(&command_line) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => fatalln!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_inner(&self, command_line: &nsICommandLine) -> RemoteAgentResult<()> {
|
||||
let flags = CommandLine::new(command_line);
|
||||
|
||||
let remote_debugging_port = if flags.present("remote-debugging-port") {
|
||||
Some(flags.opt_u16("remote-debugging-port")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let addr = match remote_debugging_port {
|
||||
Some(Some(port)) => format!("{}:{}", DEFAULT_HOST, port),
|
||||
Some(None) => format!("{}:{}", DEFAULT_HOST, DEFAULT_PORT),
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
*self.address.borrow_mut() = addr.to_string();
|
||||
|
||||
// When remote-startup-requested fires, it takes care of
|
||||
// asking the remote agent to listen for incoming connections.
|
||||
// Because the remote agent starts asynchronously, we wait
|
||||
// until we receive remote-listening before we declare to the
|
||||
// world that we are ready to accept connections.
|
||||
self.add_observer("remote-listening")?;
|
||||
self.add_observer("remote-startup-requested")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_observer(&self, topic: &str) -> RemoteAgentResult<()> {
|
||||
let topic = CString::new(topic).unwrap();
|
||||
unsafe {
|
||||
self.observer
|
||||
.AddObserver(self.coerce(), topic.as_ptr(), false)
|
||||
}
|
||||
.to_result()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
xpcom_method!(help_info => GetHelpInfo() -> nsACString);
|
||||
fn help_info(&self) -> Result<nsCString, nsresult> {
|
||||
let help = format!(
|
||||
r#" --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 {}:{}.
|
||||
"#,
|
||||
DEFAULT_HOST, DEFAULT_PORT
|
||||
);
|
||||
Ok(nsCString::from(help))
|
||||
}
|
||||
|
||||
xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: string, data: wstring));
|
||||
fn observe(
|
||||
&self,
|
||||
_subject: *const nsISupports,
|
||||
topic: string,
|
||||
data: wstring,
|
||||
) -> Result<(), nsresult> {
|
||||
let topic = unsafe { CStr::from_ptr(topic) }.to_str().unwrap();
|
||||
|
||||
match topic {
|
||||
"remote-startup-requested" => {
|
||||
if let Err(err) = self.agent.listen(&self.address.borrow()) {
|
||||
fatalln!("unable to start remote agent: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
"remote-listening" => {
|
||||
let output = unsafe { wstring_to_cstring(data) }.map_err(|_| NS_ERROR_FAILURE)?;
|
||||
eprintln!("{}", output.to_string_lossy());
|
||||
}
|
||||
|
||||
s => warn!("unknown system notification: {}", s),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Rust wrapper for nsICommandLine.
|
||||
struct CommandLine<'a> {
|
||||
inner: &'a nsICommandLine,
|
||||
}
|
||||
|
||||
impl<'a> CommandLine<'a> {
|
||||
const CASE_SENSITIVE: bool = true;
|
||||
|
||||
fn new(inner: &'a nsICommandLine) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
fn position(&self, name: &str) -> i32 {
|
||||
let flag = nsString::from(name);
|
||||
let mut result: i32 = 0;
|
||||
unsafe {
|
||||
self.inner
|
||||
.FindFlag(&*flag, Self::CASE_SENSITIVE, &mut result)
|
||||
}
|
||||
.to_result()
|
||||
.map_err(|err| error!("FindFlag: {}", err))
|
||||
.unwrap();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn present(&self, name: &str) -> bool {
|
||||
self.position(name) >= 0
|
||||
}
|
||||
|
||||
// nsICommandLine.handleFlagWithParam has the following possible return values:
|
||||
//
|
||||
// - an AString value representing the argument value if it exists
|
||||
// - NS_ERROR_INVALID_ARG if the flag was defined, but without a value
|
||||
// - a null pointer if the flag was not defined
|
||||
// - possibly any other NS exception
|
||||
//
|
||||
// This means we need to treat NS_ERROR_INVALID_ARG with special care
|
||||
// because --remote-debugging-port can be used both with and without a value.
|
||||
fn opt_str(&self, name: &str) -> RemoteAgentResult<Option<String>> {
|
||||
if self.present(name) {
|
||||
let flag = nsString::from(name);
|
||||
let mut val = nsString::new();
|
||||
let result = unsafe {
|
||||
self.inner
|
||||
.HandleFlagWithParam(&*flag, Self::CASE_SENSITIVE, &mut *val)
|
||||
}
|
||||
.to_result();
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(Some(val.to_string())),
|
||||
Err(NS_ERROR_INVALID_ARG) => Ok(None),
|
||||
Err(err) => Err(RemoteAgentError::XpCom(err)),
|
||||
}
|
||||
} else {
|
||||
Err(RemoteAgentError::XpCom(NS_ERROR_ILLEGAL_VALUE))
|
||||
}
|
||||
}
|
||||
|
||||
fn opt_u16(&self, name: &str) -> RemoteAgentResult<Option<u16>> {
|
||||
Ok(if let Some(s) = self.opt_str(name)? {
|
||||
Some(s.parse()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn prog() -> Option<String> {
|
||||
std::env::current_exe()
|
||||
.ok()?
|
||||
.file_name()?
|
||||
.to_str()?
|
||||
.to_owned()
|
||||
.into()
|
||||
}
|
||||
|
||||
// Arcane XPIDL types for raw character pointers
|
||||
// to ASCII (7-bit) and UTF-16 strings, respectively.
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Guide/Internal_strings#IDL
|
||||
#[allow(non_camel_case_types)]
|
||||
type string = *const c_char;
|
||||
#[allow(non_camel_case_types)]
|
||||
type wstring = *const i16;
|
||||
|
||||
// Convert wstring to a CString (via nsCString's UTF-16 to UTF-8 conversion).
|
||||
// But first, say three Hail Marys.
|
||||
unsafe fn wstring_to_cstring(ws: wstring) -> Result<CString, NulError> {
|
||||
let mut len: usize = 0;
|
||||
while (*(ws.offset(len as isize))) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
let ss = slice::from_raw_parts(ws as *const u16, len);
|
||||
let mut s = nsCString::new();
|
||||
s.assign_utf16_to_utf8(ss);
|
||||
CString::new(s.as_str_unchecked())
|
||||
}
|
18
remote/components/rust/src/lib.rs
Normal file
18
remote/components/rust/src/lib.rs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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/.
|
||||
|
||||
extern crate http;
|
||||
extern crate libc;
|
||||
extern crate log;
|
||||
extern crate nserror;
|
||||
extern crate nsstring;
|
||||
extern crate thiserror;
|
||||
extern crate xpcom;
|
||||
|
||||
mod error;
|
||||
mod handler;
|
||||
mod remote_agent;
|
||||
|
||||
pub use crate::error::RemoteAgentError;
|
||||
pub use crate::remote_agent::{RemoteAgent, RemoteAgentResult, DEFAULT_HOST, DEFAULT_PORT};
|
75
remote/components/rust/src/remote_agent.rs
Normal file
75
remote/components/rust/src/remote_agent.rs
Normal file
@ -0,0 +1,75 @@
|
||||
// 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/.
|
||||
|
||||
//! Rust interface for the Gecko remote agent.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use log::*;
|
||||
use nserror::NS_ERROR_ILLEGAL_VALUE;
|
||||
use nsstring::{nsAString, nsString};
|
||||
use xpcom::interfaces::nsIRemoteAgent;
|
||||
use xpcom::RefPtr;
|
||||
|
||||
use crate::error::RemoteAgentError::{self, *};
|
||||
|
||||
pub const DEFAULT_HOST: &'static str = "localhost";
|
||||
pub const DEFAULT_PORT: u16 = 9222;
|
||||
|
||||
pub type RemoteAgentResult<T> = Result<T, RemoteAgentError>;
|
||||
|
||||
pub struct RemoteAgent {
|
||||
inner: RefPtr<nsIRemoteAgent>,
|
||||
}
|
||||
|
||||
impl RemoteAgent {
|
||||
pub fn get() -> RemoteAgentResult<RemoteAgent> {
|
||||
let inner = xpcom::services::get_RemoteAgent().ok_or(Unavailable)?;
|
||||
Ok(RemoteAgent { inner })
|
||||
}
|
||||
|
||||
pub fn listen(&self, spec: &str) -> RemoteAgentResult<()> {
|
||||
let addr = http::uri::Authority::from_str(spec)?;
|
||||
let host = if addr.host().is_empty() {
|
||||
DEFAULT_HOST
|
||||
} else {
|
||||
addr.host()
|
||||
}
|
||||
.to_string();
|
||||
let port = addr.port_u16().unwrap_or(DEFAULT_PORT);
|
||||
|
||||
let url = nsString::from(&format!("http://{}:{}/", host, port));
|
||||
unsafe { self.inner.Listen(&*url as &nsAString) }
|
||||
.to_result()
|
||||
.map_err(|err| {
|
||||
// TODO(ato): https://bugzil.la/1600139
|
||||
match err {
|
||||
NS_ERROR_ILLEGAL_VALUE => RemoteAgentError::LoopbackRestricted,
|
||||
nsresult => RemoteAgentError::from(nsresult),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn listening(&self) -> RemoteAgentResult<bool> {
|
||||
let mut listening = false;
|
||||
unsafe { self.inner.GetListening(&mut listening) }.to_result()?;
|
||||
Ok(listening)
|
||||
}
|
||||
|
||||
pub fn close(&self) -> RemoteAgentResult<()> {
|
||||
unsafe { self.inner.Close() }.to_result()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RemoteAgent {
|
||||
fn drop(&mut self) {
|
||||
// it should always be safe to call nsIRemoteAgent.close()
|
||||
if let Err(e) = self.close() {
|
||||
error!("unable to close remote agent listener: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ Component files include the likes of components.conf,
|
||||
RemoteAgent.manifest, moz.build files, and jar.mn.
|
||||
All the JS modules (files ending with `.jsm`) are symlinked into
|
||||
the build and can be changed without rebuilding.
|
||||
The Remote Agent’s startup code found under remote/components/rust/
|
||||
is written in Rust and requires rebuilds when changed.
|
||||
|
||||
You may also opt out of building all the WebDriver specific components
|
||||
([Marionette], and the Remote Agent) by setting the following flag in
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
remote.jar:
|
||||
% content remote %content/
|
||||
content/components/Marionette.jsm (components/Marionette.jsm)
|
||||
content/components/RemoteAgent.jsm (components/RemoteAgent.jsm)
|
||||
|
||||
# transport layer (http / websocket)
|
||||
|
@ -168,7 +168,7 @@ The practical details of working on the Marionette code is outlined
|
||||
in [Contributing.md], but generally you do not have to re-build
|
||||
Firefox when changing code. Any change to remote/marionette/*.js
|
||||
will be picked up on restarting Firefox. The only notable exception
|
||||
is remote/components/Marionette.jsm, which does require
|
||||
is remote/components/marionette.js, which does require
|
||||
a re-build.
|
||||
|
||||
[XPCOM]: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM
|
||||
|
@ -11,6 +11,8 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.jsm",
|
||||
Log: "chrome://remote/content/shared/Log.jsm",
|
||||
WebDriverNewSessionHandler:
|
||||
@ -149,7 +151,11 @@ class WebDriverBiDi {
|
||||
new WebDriverNewSessionHandler(this)
|
||||
);
|
||||
|
||||
Cu.printStderr(`WebDriver BiDi listening on ${this.address}\n`);
|
||||
Services.obs.notifyObservers(
|
||||
null,
|
||||
"remote-listening",
|
||||
`WebDriver BiDi listening on ${this.address}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,6 +29,7 @@ libfuzzer = ["gkrust-shared/libfuzzer", "gecko-fuzz-targets"]
|
||||
webrtc = ["gkrust-shared/webrtc"]
|
||||
wasm_library_sandboxing = ["gkrust-shared/wasm_library_sandboxing"]
|
||||
webgpu = ["gkrust-shared/webgpu"]
|
||||
remote_agent = ["gkrust-shared/remote"]
|
||||
glean_disable_upload = ["gkrust-shared/glean_disable_upload"]
|
||||
glean_with_gecko = ["gkrust-shared/glean_with_gecko"]
|
||||
with_dbus = ["gkrust-shared/with_dbus"]
|
||||
|
@ -29,6 +29,7 @@ libfuzzer = ["gkrust-shared/libfuzzer"]
|
||||
webrtc = ["gkrust-shared/webrtc"]
|
||||
wasm_library_sandboxing = ["gkrust-shared/wasm_library_sandboxing"]
|
||||
webgpu = ["gkrust-shared/webgpu"]
|
||||
remote_agent = ["gkrust-shared/remote"]
|
||||
glean_disable_upload = ["gkrust-shared/glean_disable_upload"]
|
||||
glean_with_gecko = ["gkrust-shared/glean_with_gecko"]
|
||||
with_dbus = ["gkrust-shared/with_dbus"]
|
||||
|
@ -71,6 +71,9 @@ if CONFIG['LIBFUZZER']:
|
||||
if CONFIG['MOZ_WEBRTC']:
|
||||
gkrust_features += ['webrtc']
|
||||
|
||||
if CONFIG['ENABLE_WEBDRIVER']:
|
||||
gkrust_features += ['remote_agent']
|
||||
|
||||
# We need to tell Glean it is being built with Gecko.
|
||||
gkrust_features += ['glean_with_gecko']
|
||||
|
||||
|
@ -46,6 +46,7 @@ neqo_glue = { path = "../../../../netwerk/socket/neqo_glue" }
|
||||
rlbox_lucet_sandbox = { version = "0.1.0", optional = true }
|
||||
wgpu_bindings = { path = "../../../../gfx/wgpu_bindings", optional = true }
|
||||
mapped_hyph = { git = "https://github.com/jfkthame/mapped_hyph.git", rev = "746743227485a83123784df0c53227ab466612ed" }
|
||||
remote = { path = "../../../../remote/components/rust", optional = true }
|
||||
fog_control = { path = "../../../components/glean" }
|
||||
app_services_logger = { path = "../../../../services/common/app_services_logger" }
|
||||
http_sfv = { path = "../../../../netwerk/base/http-sfv" }
|
||||
@ -96,6 +97,7 @@ libfuzzer = []
|
||||
webrtc = ["mdns_service"]
|
||||
wasm_library_sandboxing = ["rlbox_lucet_sandbox"]
|
||||
webgpu = ["wgpu_bindings"]
|
||||
remote_agent = ["remote"]
|
||||
glean_disable_upload = ["fog_control/disable_upload"]
|
||||
glean_with_gecko = ["fog_control/with_gecko"]
|
||||
oxidized_breakpad = ["rust_minidump_writer_linux"]
|
||||
|
@ -77,6 +77,9 @@ extern crate fluent_ffi;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
extern crate viaduct;
|
||||
|
||||
#[cfg(feature = "remote")]
|
||||
extern crate remote;
|
||||
|
||||
extern crate gecko_logger;
|
||||
|
||||
#[cfg(feature = "oxidized_breakpad")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user