summaryrefslogtreecommitdiffstats
path: root/remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs')
-rw-r--r--remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs214
1 files changed, 214 insertions, 0 deletions
diff --git a/remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs b/remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs
new file mode 100644
index 0000000000..96482222b3
--- /dev/null
+++ b/remote/webdriver-bidi/WebDriverBiDiConnection.sys.mjs
@@ -0,0 +1,214 @@
+/* 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";
+
+import { WebSocketConnection } from "chrome://remote/content/shared/WebSocketConnection.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
+ error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
+ Log: "chrome://remote/content/shared/Log.sys.mjs",
+ RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
+ lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
+);
+
+export class WebDriverBiDiConnection extends WebSocketConnection {
+ /**
+ * @param {WebSocket} webSocket
+ * The WebSocket server connection to wrap.
+ * @param {Connection} httpdConnection
+ * Reference to the httpd.js's connection needed for clean-up.
+ */
+ constructor(webSocket, httpdConnection) {
+ super(webSocket, httpdConnection);
+
+ // Each connection has only a single associated WebDriver session.
+ this.session = null;
+ }
+
+ /**
+ * Register a new WebDriver Session to forward the messages to.
+ *
+ * @param {Session} session
+ * The WebDriverSession to register.
+ */
+ registerSession(session) {
+ if (this.session) {
+ throw new lazy.error.UnknownError(
+ "A WebDriver session has already been set"
+ );
+ }
+
+ this.session = session;
+ lazy.logger.debug(
+ `Connection ${this.id} attached to session ${session.id}`
+ );
+ }
+
+ /**
+ * Unregister the already set WebDriver session.
+ */
+ unregisterSession() {
+ if (!this.session) {
+ return;
+ }
+
+ this.session.removeConnection(this);
+ this.session = null;
+ }
+
+ /**
+ * Send an error back to the WebDriver BiDi client.
+ *
+ * @param {number} id
+ * Id of the packet which lead to an error.
+ * @param {Error} err
+ * Error object with `status`, `message` and `stack` attributes.
+ */
+ sendError(id, err) {
+ const webDriverError = lazy.error.wrap(err);
+
+ this.send({
+ id,
+ error: webDriverError.status,
+ message: webDriverError.message,
+ stacktrace: webDriverError.stack,
+ });
+ }
+
+ /**
+ * Send an event coming from a module to the WebDriver BiDi client.
+ *
+ * @param {string} method
+ * The event name. This is composed by a module name, a dot character
+ * followed by the event name, e.g. `log.entryAdded`.
+ * @param {object} params
+ * A JSON-serializable object, which is the payload of this event.
+ */
+ sendEvent(method, params) {
+ this.send({ method, params });
+
+ if (Services.profiler?.IsActive()) {
+ ChromeUtils.addProfilerMarker(
+ "BiDi: Event",
+ { category: "Remote-Protocol" },
+ method
+ );
+ }
+ }
+
+ /**
+ * Send the result of a call to a module's method back to the
+ * WebDriver BiDi client.
+ *
+ * @param {number} id
+ * The request id being sent by the client to call the module's method.
+ * @param {object} result
+ * A JSON-serializable object, which is the actual result.
+ */
+ sendResult(id, result) {
+ result = typeof result !== "undefined" ? result : {};
+ this.send({ id, result });
+ }
+
+ // Transport hooks
+
+ /**
+ * Called by the `transport` when the connection is closed.
+ */
+ onClosed() {
+ this.unregisterSession();
+
+ super.onClosed();
+ }
+
+ /**
+ * Receive a packet from the WebSocket layer.
+ *
+ * This packet is sent by a WebDriver BiDi client and is meant to execute
+ * a particular method on a given module.
+ *
+ * @param {object} packet
+ * JSON-serializable object sent by the client
+ */
+ async onPacket(packet) {
+ super.onPacket(packet);
+
+ const { id, method, params } = packet;
+ const startTime = Cu.now();
+
+ try {
+ // First check for mandatory field in the command packet
+ lazy.assert.positiveInteger(id, "id: unsigned integer value expected");
+ lazy.assert.string(method, "method: string value expected");
+ lazy.assert.object(params, "params: object value expected");
+
+ // Extract the module and the command name out of `method` attribute
+ const { module, command } = splitMethod(method);
+ let result;
+
+ // Handle static commands first
+ if (module === "session" && command === "new") {
+ // TODO: Needs capability matching code
+ result = await lazy.RemoteAgent.webDriverBiDi.createSession(
+ params,
+ this
+ );
+ } else if (module === "session" && command === "status") {
+ result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
+ } else {
+ lazy.assert.session(this.session);
+
+ // Bug 1741854 - Workaround to deny internal methods to be called
+ if (command.startsWith("_")) {
+ throw new lazy.error.UnknownCommandError(method);
+ }
+
+ // Finally, instruct the session to execute the command
+ result = await this.session.execute(module, command, params);
+ }
+
+ this.sendResult(id, result);
+ } catch (e) {
+ this.sendError(id, e);
+ }
+
+ if (Services.profiler?.IsActive()) {
+ ChromeUtils.addProfilerMarker(
+ "BiDi: Command",
+ { startTime, category: "Remote-Protocol" },
+ `${method} (${id})`
+ );
+ }
+ }
+}
+
+/**
+ * Splits a WebDriver BiDi method into module and command components.
+ *
+ * @param {string} method
+ * Name of the method to split, e.g. "session.subscribe".
+ *
+ * @returns {Object<string, string>}
+ * Object with the module ("session") and command ("subscribe")
+ * as properties.
+ */
+export function splitMethod(method) {
+ const parts = method.split(".");
+
+ if (parts.length != 2 || !parts[0].length || !parts[1].length) {
+ throw new TypeError(`Invalid method format: '${method}'`);
+ }
+
+ return {
+ module: parts[0],
+ command: parts[1],
+ };
+}