1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 import { WebSocketConnection } from "chrome://remote/content/shared/WebSocketConnection.sys.mjs";
11 ChromeUtils.defineESModuleGetters(lazy, {
12 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
13 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
14 Log: "chrome://remote/content/shared/Log.sys.mjs",
15 RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
18 XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
19 lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
22 export class WebDriverBiDiConnection extends WebSocketConnection {
24 * @param {WebSocket} webSocket
25 * The WebSocket server connection to wrap.
26 * @param {Connection} httpdConnection
27 * Reference to the httpd.js's connection needed for clean-up.
29 constructor(webSocket, httpdConnection) {
30 super(webSocket, httpdConnection);
32 // Each connection has only a single associated WebDriver session.
37 * Register a new WebDriver Session to forward the messages to.
39 * @param {Session} session
40 * The WebDriverSession to register.
42 registerSession(session) {
44 throw new lazy.error.UnknownError(
45 "A WebDriver session has already been set"
49 this.session = session;
51 `Connection ${this.id} attached to session ${session.id}`
56 * Unregister the already set WebDriver session.
58 * @param {Session} session
59 * The WebDriverSession to register.
66 this.session.removeConnection(this);
71 * Send an error back to the WebDriver BiDi client.
74 * Id of the packet which lead to an error.
76 * Error object with `status`, `message` and `stack` attributes.
79 const webDriverError = lazy.error.wrap(err);
83 error: webDriverError.status,
84 message: webDriverError.message,
85 stacktrace: webDriverError.stack,
90 * Send an event coming from a module to the WebDriver BiDi client.
92 * @param {String} method
93 * The event name. This is composed by a module name, a dot character
94 * followed by the event name, e.g. `log.entryAdded`.
95 * @param {Object} params
96 * A JSON-serializable object, which is the payload of this event.
98 sendEvent(method, params) {
99 this.send({ method, params });
103 * Send the result of a call to a module's method back to the
104 * WebDriver BiDi client.
107 * The request id being sent by the client to call the module's method.
108 * @param {Object} result
109 * A JSON-serializable object, which is the actual result.
111 sendResult(id, result) {
112 result = typeof result !== "undefined" ? result : {};
113 this.send({ id, result });
119 * Called by the `transport` when the connection is closed.
122 this.unregisterSession();
128 * Receive a packet from the WebSocket layer.
130 * This packet is sent by a WebDriver BiDi client and is meant to execute
131 * a particular method on a given module.
133 * @param Object packet
134 * JSON-serializable object sent by the client
136 async onPacket(packet) {
137 super.onPacket(packet);
139 const { id, method, params } = packet;
142 // First check for mandatory field in the command packet
143 lazy.assert.positiveInteger(id, "id: unsigned integer value expected");
144 lazy.assert.string(method, "method: string value expected");
145 lazy.assert.object(params, "params: object value expected");
147 // Extract the module and the command name out of `method` attribute
148 const { module, command } = splitMethod(method);
151 // Handle static commands first
152 if (module === "session" && command === "new") {
153 // TODO: Needs capability matching code
154 result = await lazy.RemoteAgent.webDriverBiDi.createSession(
158 } else if (module === "session" && command === "status") {
159 result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
161 lazy.assert.session(this.session);
163 // Bug 1741854 - Workaround to deny internal methods to be called
164 if (command.startsWith("_")) {
165 throw new lazy.error.UnknownCommandError(method);
168 // Finally, instruct the session to execute the command
169 result = await this.session.execute(module, command, params);
172 this.sendResult(id, result);
174 this.sendError(packet.id, e);
180 * Splits a WebDriver BiDi method into module and command components.
182 * @param {String} method
183 * Name of the method to split, e.g. "session.subscribe".
185 * @returns {Object<String, String>}
186 * Object with the module ("session") and command ("subscribe")
189 export function splitMethod(method) {
190 const parts = method.split(".");
192 if (parts.length != 2 || !parts[0].length || !parts[1].length) {
193 throw new TypeError(`Invalid method format: '${method}'`);