diff options
Diffstat (limited to 'remote/components')
-rw-r--r-- | remote/components/Marionette.sys.mjs | 308 | ||||
-rw-r--r-- | remote/components/RemoteAgent.sys.mjs | 523 | ||||
-rw-r--r-- | remote/components/components.conf | 29 | ||||
-rw-r--r-- | remote/components/moz.build | 17 | ||||
-rw-r--r-- | remote/components/nsIMarionette.idl | 17 | ||||
-rw-r--r-- | remote/components/nsIRemoteAgent.idl | 36 |
6 files changed, 930 insertions, 0 deletions
diff --git a/remote/components/Marionette.sys.mjs b/remote/components/Marionette.sys.mjs new file mode 100644 index 0000000000..9be05dafae --- /dev/null +++ b/remote/components/Marionette.sys.mjs @@ -0,0 +1,308 @@ +/* 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/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Preferences: "resource://gre/modules/Preferences.sys.mjs", + + Deferred: "chrome://remote/content/shared/Sync.sys.mjs", + EnvironmentPrefs: "chrome://remote/content/marionette/prefs.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", + MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs", + RecommendedPreferences: + "chrome://remote/content/shared/RecommendedPreferences.sys.mjs", + TCPListener: "chrome://remote/content/marionette/server.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +XPCOMUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder()); + +const NOTIFY_LISTENING = "marionette-listening"; + +// Complements -marionette flag for starting the Marionette server. +// We also set this if Marionette is running in order to start the server +// again after a Firefox restart. +const ENV_ENABLED = "MOZ_MARIONETTE"; + +// Besides starting based on existing prefs in a profile and a command +// line flag, we also support inheriting prefs out of an env var, and to +// start Marionette that way. +// +// This allows marionette prefs to persist when we do a restart into +// a different profile in order to test things like Firefox refresh. +// The environment variable itself, if present, is interpreted as a +// JSON structure, with the keys mapping to preference names in the +// "marionette." branch, and the values to the values of those prefs. So +// something like {"port": 4444} would result in the marionette.port +// pref being set to 4444. +const ENV_PRESERVE_PREFS = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS"; + +// Map of Marionette-specific preferences that should be set via +// RecommendedPreferences. +const RECOMMENDED_PREFS = new Map([ + // Automatically unload beforeunload alerts + ["dom.disable_beforeunload", true], +]); + +const isRemote = + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + +class MarionetteParentProcess { + #browserStartupFinished; + + constructor() { + this.server = null; + this._activePortPath; + + this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}"); + this.helpInfo = " --marionette Enable remote control server.\n"; + + // Initially set the enabled state based on the environment variable. + this.enabled = Services.env.exists(ENV_ENABLED); + + Services.ppmm.addMessageListener("Marionette:IsRunning", this); + + this.#browserStartupFinished = lazy.Deferred(); + } + + /** + * A promise that resolves when the initial application window has been opened. + * + * @returns {Promise} + * Promise that resolves when the initial application window is open. + */ + get browserStartupFinished() { + return this.#browserStartupFinished.promise; + } + + get enabled() { + return this._enabled; + } + + set enabled(value) { + // Return early if Marionette is already marked as being enabled. + // There is also no possibility to disable Marionette once it got enabled. + if (this._enabled || !value) { + return; + } + + this._enabled = value; + lazy.logger.info(`Marionette enabled`); + } + + get running() { + return !!this.server && this.server.alive; + } + + receiveMessage({ name }) { + switch (name) { + case "Marionette:IsRunning": + return this.running; + + default: + lazy.logger.warn("Unknown IPC message to parent process: " + name); + return null; + } + } + + handle(cmdLine) { + // `handle` is called too late in certain cases (eg safe mode, see comment + // above "command-line-startup"). So the marionette command line argument + // will always be processed in `observe`. + // However it still needs to be consumed by the command-line-handler API, + // to avoid issues on macos. + // TODO: remove after Bug 1724251 is fixed. + cmdLine.handleFlag("marionette", false); + } + + async observe(subject, topic) { + if (this.enabled) { + lazy.logger.trace(`Received observer notification ${topic}`); + } + + switch (topic) { + case "profile-after-change": + Services.obs.addObserver(this, "command-line-startup"); + break; + + // In safe mode the command line handlers are getting parsed after the + // safe mode dialog has been closed. To allow Marionette to start + // earlier, use the CLI startup observer notification for + // special-cased handlers, which gets fired before the dialog appears. + case "command-line-startup": + Services.obs.removeObserver(this, topic); + + this.enabled = subject.handleFlag("marionette", false); + + if (this.enabled) { + // Marionette needs to be initialized before any window is shown. + Services.obs.addObserver(this, "final-ui-startup"); + + // We want to suppress the modal dialog that's shown + // when starting up in safe-mode to enable testing. + if (Services.appinfo.inSafeMode) { + Services.obs.addObserver(this, "domwindowopened"); + } + + lazy.RecommendedPreferences.applyPreferences(RECOMMENDED_PREFS); + + // Only set preferences to preserve in a new profile + // when Marionette is enabled. + for (let [pref, value] of lazy.EnvironmentPrefs.from( + ENV_PRESERVE_PREFS + )) { + lazy.Preferences.set(pref, value); + } + } + break; + + case "domwindowopened": + Services.obs.removeObserver(this, topic); + this.suppressSafeModeDialog(subject); + break; + + case "final-ui-startup": + Services.obs.removeObserver(this, topic); + + Services.obs.addObserver(this, "browser-idle-startup-tasks-finished"); + Services.obs.addObserver(this, "mail-idle-startup-tasks-finished"); + Services.obs.addObserver(this, "quit-application"); + + await this.init(); + break; + + // Used to wait until the initial application window has been opened. + case "browser-idle-startup-tasks-finished": + case "mail-idle-startup-tasks-finished": + Services.obs.removeObserver( + this, + "browser-idle-startup-tasks-finished" + ); + Services.obs.removeObserver(this, "mail-idle-startup-tasks-finished"); + this.#browserStartupFinished.resolve(); + break; + + case "quit-application": + Services.obs.removeObserver(this, topic); + await this.uninit(); + break; + } + } + + suppressSafeModeDialog(win) { + win.addEventListener( + "load", + () => { + let dialog = win.document.getElementById("safeModeDialog"); + if (dialog) { + // accept the dialog to start in safe-mode + lazy.logger.trace("Safe mode detected, supressing dialog"); + win.setTimeout(() => { + dialog.getButton("accept").click(); + }); + } + }, + { once: true } + ); + } + + async init() { + if (!this.enabled || this.running) { + lazy.logger.debug( + `Init aborted (enabled=${this.enabled}, running=${this.running})` + ); + return; + } + + try { + this.server = new lazy.TCPListener(lazy.MarionettePrefs.port); + this.server.start(); + } catch (e) { + lazy.logger.fatal("Marionette server failed to start", e); + await this.uninit(); + Services.startup.quit(Ci.nsIAppStartup.eForceQuit); + return; + } + + Services.env.set(ENV_ENABLED, "1"); + Services.obs.notifyObservers(this, NOTIFY_LISTENING, true); + lazy.logger.debug("Marionette is listening"); + + // Write Marionette port to MarionetteActivePort file within the profile. + this._activePortPath = PathUtils.join( + PathUtils.profileDir, + "MarionetteActivePort" + ); + + const data = `${this.server.port}`; + try { + await IOUtils.write(this._activePortPath, lazy.textEncoder.encode(data)); + } catch (e) { + lazy.logger.warn( + `Failed to create ${this._activePortPath} (${e.message})` + ); + } + } + + async uninit() { + if (this.running) { + this.server.stop(); + Services.obs.notifyObservers(this, NOTIFY_LISTENING); + lazy.logger.debug("Marionette stopped listening"); + + try { + await IOUtils.remove(this._activePortPath); + } catch (e) { + lazy.logger.warn( + `Failed to remove ${this._activePortPath} (${e.message})` + ); + } + } + } + + get QueryInterface() { + return ChromeUtils.generateQI([ + "nsICommandLineHandler", + "nsIMarionette", + "nsIObserver", + ]); + } +} + +class MarionetteContentProcess { + constructor() { + this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}"); + } + + get running() { + let reply = Services.cpmm.sendSyncMessage("Marionette:IsRunning"); + if (!reply.length) { + lazy.logger.warn("No reply from parent process"); + return false; + } + return reply[0]; + } + + get QueryInterface() { + return ChromeUtils.generateQI(["nsIMarionette"]); + } +} + +export var Marionette; +if (isRemote) { + Marionette = new MarionetteContentProcess(); +} else { + Marionette = new MarionetteParentProcess(); +} + +// This is used by the XPCOM codepath which expects a constructor +export const MarionetteFactory = function() { + return Marionette; +}; diff --git a/remote/components/RemoteAgent.sys.mjs b/remote/components/RemoteAgent.sys.mjs new file mode 100644 index 0000000000..ca3e8da880 --- /dev/null +++ b/remote/components/RemoteAgent.sys.mjs @@ -0,0 +1,523 @@ +/* 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/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CDP: "chrome://remote/content/cdp/CDP.sys.mjs", + Deferred: "chrome://remote/content/shared/Sync.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", + WebDriverBiDi: "chrome://remote/content/webdriver-bidi/WebDriverBiDi.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + HttpServer: "chrome://remote/content/server/HTTPD.jsm", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +XPCOMUtils.defineLazyGetter(lazy, "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_HOST = "localhost"; +const DEFAULT_PORT = 9222; + +const isRemote = + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + +class RemoteAgentParentProcess { + #allowHosts; + #allowOrigins; + #browserStartupFinished; + #classID; + #enabled; + #host; + #port; + #server; + + #cdp; + #webDriverBiDi; + + constructor() { + this.#allowHosts = null; + this.#allowOrigins = null; + this.#browserStartupFinished = lazy.Deferred(); + this.#classID = Components.ID("{8f685a9d-8181-46d6-a71d-869289099c6d}"); + this.#enabled = false; + + // Configuration for httpd.js + this.#host = DEFAULT_HOST; + this.#port = DEFAULT_PORT; + this.#server = null; + + // Supported protocols + this.#cdp = null; + this.#webDriverBiDi = null; + + Services.ppmm.addMessageListener("RemoteAgent:IsRunning", this); + } + + get allowHosts() { + if (this.#allowHosts !== null) { + return this.#allowHosts; + } + + if (this.#server) { + // If the server is bound to a hostname, not an IP address, return it as + // allowed host. + const hostUri = Services.io.newURI(`https://${this.#host}`); + if (!this.#isIPAddress(hostUri)) { + return [RemoteAgent.host]; + } + + // Following Bug 1220810 localhost is guaranteed to resolve to a loopback + // address (127.0.0.1 or ::1) unless network.proxy.allow_hijacking_localhost + // is set to true, which should not be the case. + const loopbackAddresses = ["127.0.0.1", "[::1]"]; + + // If the server is bound to an IP address and this IP address is a localhost + // loopback address, return localhost as allowed host. + if (loopbackAddresses.includes(this.#host)) { + return ["localhost"]; + } + } + + // Otherwise return an empty array. + return []; + } + + get allowOrigins() { + return this.#allowOrigins; + } + + /** + * A promise that resolves when the initial application window has been opened. + * + * @returns {Promise} + * Promise that resolves when the initial application window is open. + */ + get browserStartupFinished() { + return this.#browserStartupFinished.promise; + } + + get cdp() { + return this.#cdp; + } + + get debuggerAddress() { + if (!this.#server) { + return ""; + } + + return `${this.#host}:${this.#port}`; + } + + get enabled() { + return this.#enabled; + } + + get host() { + return this.#host; + } + + get port() { + return this.#port; + } + + get running() { + return !!this.#server && !this.#server.isStopped(); + } + + get scheme() { + return this.#server?.identity.primaryScheme; + } + + get server() { + return this.#server; + } + + get webDriverBiDi() { + return this.#webDriverBiDi; + } + + /** + * Check if the provided URI's host is an IP address. + * + * @param {nsIURI} uri + * The URI to check. + * @return {boolean} + */ + #isIPAddress(uri) { + try { + // getBaseDomain throws an explicit error if the uri host is an IP address. + Services.eTLD.getBaseDomain(uri); + } catch (e) { + return e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS; + } + return false; + } + + 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(port) { + 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.running) { + return; + } + + // Try to resolve localhost to an IPv4 and / or IPv6 address so that the + // server can be started on a given IP. Only fallback to use localhost if + // the hostname cannot be resolved. + // + // Note: This doesn't force httpd.js to use the dual stack support. + let isIPv4Host = false; + try { + const addresses = await this.#resolveHostname(DEFAULT_HOST); + lazy.logger.trace( + `Available local IP addresses: ${addresses.join(", ")}` + ); + + // Prefer IPv4 over IPv6 addresses. + const addressesIPv4 = addresses.filter(value => !value.includes(":")); + isIPv4Host = !!addressesIPv4.length; + if (isIPv4Host) { + this.#host = addressesIPv4[0]; + } else { + this.#host = addresses.length ? addresses[0] : DEFAULT_HOST; + } + } catch (e) { + this.#host = DEFAULT_HOST; + + lazy.logger.debug( + `Failed to resolve hostname "localhost" to IP address: ${e.message}` + ); + } + + // nsIServerSocket uses -1 for atomic port allocation + if (port === 0) { + port = -1; + } + + try { + // Bug 1783938: httpd.js refuses connections when started on a IPv4 + // address. As workaround start on localhost and add another identity + // for that IP address. + this.#server = new lazy.HttpServer(); + const host = isIPv4Host ? DEFAULT_HOST : this.#host; + this.server._start(port, host); + this.#port = this.server._port; + + if (isIPv4Host) { + this.server.identity.add("http", this.#host, this.#port); + } + + Services.obs.notifyObservers(null, "remote-listening", true); + + await Promise.all([this.#webDriverBiDi?.start(), this.#cdp?.start()]); + } catch (e) { + await this.#stop(); + lazy.logger.error(`Unable to start remote agent: ${e.message}`, e); + } + } + + /** + * Resolves a hostname to one or more IP addresses. + * + * @param {string} hostname + * + * @returns {Array<string>} + */ + #resolveHostname(hostname) { + return new Promise((resolve, reject) => { + let originalRequest; + + const onLookupCompleteListener = { + onLookupComplete(request, record, status) { + if (request === originalRequest) { + if (!Components.isSuccessCode(status)) { + reject({ message: ChromeUtils.getXPCOMErrorName(status) }); + return; + } + + record.QueryInterface(Ci.nsIDNSAddrRecord); + + const addresses = []; + while (record.hasMore()) { + let addr = record.getNextAddrAsString(); + if (addr.includes(":") && !addr.startsWith("[")) { + // Make sure that the IPv6 address is wrapped with brackets. + addr = `[${addr}]`; + } + if (!addresses.includes(addr)) { + // Sometimes there are duplicate records with the same IP. + addresses.push(addr); + } + } + + resolve(addresses); + } + }, + }; + + try { + originalRequest = Services.dns.asyncResolve( + hostname, + Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, + Ci.nsIDNSService.RESOLVE_BYPASS_CACHE, + null, + onLookupCompleteListener, + null, //Services.tm.mainThread, + {} /* defaultOriginAttributes */ + ); + } catch (e) { + reject({ message: e.message }); + } + }); + } + + async #stop() { + if (!this.running) { + return; + } + + try { + // Stop the CDP support before stopping the server. + // Otherwise the HTTP server will fail to stop. + await this.#cdp?.stop(); + await this.#webDriverBiDi?.stop(); + + await this.#server.stop(); + this.#server = null; + Services.obs.notifyObservers(null, "remote-listening"); + } catch (e) { + // this function must never fail + lazy.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; + } + + handleAllowHostsFlag(cmdLine) { + try { + const hosts = cmdLine.handleFlagWithParam("remote-allow-hosts", false); + return hosts.split(","); + } catch (e) { + return null; + } + } + + handleAllowOriginsFlag(cmdLine) { + try { + const origins = cmdLine.handleFlagWithParam( + "remote-allow-origins", + false + ); + return origins.split(","); + } catch (e) { + return null; + } + } + + async observe(subject, topic) { + if (this.#enabled) { + lazy.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, "final-ui-startup"); + + this.#allowHosts = this.handleAllowHostsFlag(subject); + this.#allowOrigins = this.handleAllowOriginsFlag(subject); + + Services.obs.addObserver(this, "browser-idle-startup-tasks-finished"); + Services.obs.addObserver(this, "mail-idle-startup-tasks-finished"); + 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 ( + (lazy.activeProtocols & WEBDRIVER_BIDI_ACTIVE) === + WEBDRIVER_BIDI_ACTIVE + ) { + this.#webDriverBiDi = new lazy.WebDriverBiDi(this); + if (this.#enabled) { + lazy.logger.debug("WebDriver BiDi enabled"); + } + } + + if ((lazy.activeProtocols & CDP_ACTIVE) === CDP_ACTIVE) { + this.#cdp = new lazy.CDP(this); + if (this.#enabled) { + lazy.logger.debug("CDP enabled"); + } + } + } + break; + + case "final-ui-startup": + Services.obs.removeObserver(this, topic); + + try { + await this.#listen(this.#port); + } catch (e) { + throw Error(`Unable to start remote agent: ${e}`); + } + + break; + + // Used to wait until the initial application window has been opened. + case "browser-idle-startup-tasks-finished": + case "mail-idle-startup-tasks-finished": + Services.obs.removeObserver( + this, + "browser-idle-startup-tasks-finished" + ); + Services.obs.removeObserver(this, "mail-idle-startup-tasks-finished"); + this.#browserStartupFinished.resolve(); + break; + + // Listen for application shutdown to also shutdown the Remote Agent + // and a possible running instance of httpd.js. + case "quit-application": + Services.obs.removeObserver(this, topic); + this.#stop(); + break; + } + } + + receiveMessage({ name }) { + switch (name) { + case "RemoteAgent:IsRunning": + return this.running; + + default: + lazy.logger.warn("Unknown IPC message to parent process: " + name); + return null; + } + } + + // XPCOM + + get classID() { + return this.#classID; + } + + get helpInfo() { + return ` --remote-debugging-port [<port>] Start the Firefox Remote Agent, + which is a low-level remote debugging interface used for WebDriver + BiDi and CDP. Defaults to port 9222. + --remote-allow-hosts <hosts> Values of the Host header to allow for incoming requests. + Please read security guidelines at https://firefox-source-docs.mozilla.org/remote/Security.html + --remote-allow-origins <origins> Values of the Origin header to allow for incoming requests. + Please read security guidelines at https://firefox-source-docs.mozilla.org/remote/Security.html\n`; + } + + get QueryInterface() { + return ChromeUtils.generateQI([ + "nsICommandLineHandler", + "nsIObserver", + "nsIRemoteAgent", + ]); + } +} + +class RemoteAgentContentProcess { + #classID; + + constructor() { + this.#classID = Components.ID("{8f685a9d-8181-46d6-a71d-869289099c6d}"); + } + + get running() { + let reply = Services.cpmm.sendSyncMessage("RemoteAgent:IsRunning"); + if (!reply.length) { + lazy.logger.warn("No reply from parent process"); + return false; + } + return reply[0]; + } + + get QueryInterface() { + return ChromeUtils.generateQI(["nsIRemoteAgent"]); + } +} + +export var RemoteAgent; +if (isRemote) { + RemoteAgent = new RemoteAgentContentProcess(); +} else { + RemoteAgent = new RemoteAgentParentProcess(); +} + +// This is used by the XPCOM codepath which expects a constructor +export var RemoteAgentFactory = function() { + return RemoteAgent; +}; diff --git a/remote/components/components.conf b/remote/components/components.conf new file mode 100644 index 0000000000..e518af3dd5 --- /dev/null +++ b/remote/components/components.conf @@ -0,0 +1,29 @@ +# 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/. + +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", + }, + 'esModule': "chrome://remote/content/components/RemoteAgent.sys.mjs", + "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", + }, + 'esModule': "chrome://remote/content/components/Marionette.sys.mjs", + "constructor": "MarionetteFactory", + }, +] diff --git a/remote/components/moz.build b/remote/components/moz.build new file mode 100644 index 0000000000..99b56e2add --- /dev/null +++ b/remote/components/moz.build @@ -0,0 +1,17 @@ +# 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/. + +XPIDL_MODULE = "remote" + +XPIDL_SOURCES += [ + "nsIMarionette.idl", + "nsIRemoteAgent.idl", +] + +XPCOM_MANIFESTS += ["components.conf"] + +with Files("Marionette.sys.mjs"): + BUG_COMPONENT = ("Testing", "Marionette") +with Files("nsIMarionette.idl"): + BUG_COMPONENT = ("Testing", "Marionette") diff --git a/remote/components/nsIMarionette.idl b/remote/components/nsIMarionette.idl new file mode 100644 index 0000000000..c10bf4b17b --- /dev/null +++ b/remote/components/nsIMarionette.idl @@ -0,0 +1,17 @@ +/* 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 "nsISupports.idl" + +%{C++ +#define NS_MARIONETTE_CONTRACTID "@mozilla.org/remote/marionette;1" +%} + +/** Interface for accessing the Marionette server instance. */ +[scriptable, uuid(13fa7d76-f976-4711-a00c-29ac9c1881e1)] +interface nsIMarionette : nsISupports +{ + /** Indicates whether Marionette is running. */ + readonly attribute boolean running; +}; diff --git a/remote/components/nsIRemoteAgent.idl b/remote/components/nsIRemoteAgent.idl new file mode 100644 index 0000000000..89d637bed2 --- /dev/null +++ b/remote/components/nsIRemoteAgent.idl @@ -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 "nsISupports.idl" + +/** + * The Gecko remote agent is an RPC subsystem that exposes + * browser-internal interfaces and services to the surrounding + * system. + * + * Consumers, whether remote or browser-local, can interface with + * the browser through an assorted set of services ranging from + * document introspection and script evaluation, to instrumentation, + * user interaction simulation, and event subscription. + */ +[scriptable, uuid(8f685a9d-8181-46d6-a71d-869289099c6d)] +interface nsIRemoteAgent : nsISupports +{ + /** + * Address of the HTTP server under which the remote agent is reachable. + */ + readonly attribute AString debuggerAddress; + + /** + * Indicates whether the Remote Agent is running. + */ + readonly attribute boolean running; +}; + +%{C++ +#define NS_REMOTEAGENT_CONTRACTID "@mozilla.org/remote/agent;1" +#define NS_REMOTEAGENT_CID \ + { 0x8f685a9d, 0x8181, 0x46d6, \ + { 0xa7, 0x1d, x86, x92, x89, x09, x9c, x6d } } +%} |