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/. */
7 var EXPORTED_SYMBOLS = ["splitMethod", "WebDriverBiDiConnection"];
9 const { XPCOMUtils } = ChromeUtils.import(
10 "resource://gre/modules/XPCOMUtils.jsm"
13 XPCOMUtils.defineLazyModuleGetters(this, {
14 assert: "chrome://remote/content/shared/webdriver/Assert.jsm",
15 error: "chrome://remote/content/shared/webdriver/Errors.jsm",
16 Log: "chrome://remote/content/shared/Log.jsm",
17 RemoteAgent: "chrome://remote/content/components/RemoteAgent.jsm",
18 truncate: "chrome://remote/content/shared/Format.jsm",
19 WebSocketConnection: "chrome://remote/content/shared/WebSocketConnection.jsm",
22 XPCOMUtils.defineLazyGetter(this, "logger", () =>
23 Log.get(Log.TYPES.WEBDRIVER_BIDI)
26 class WebDriverBiDiConnection extends WebSocketConnection {
28 * @param {WebSocket} webSocket
29 * The WebSocket server connection to wrap.
30 * @param {Connection} httpdConnection
31 * Reference to the httpd.js's connection needed for clean-up.
33 constructor(webSocket, httpdConnection) {
34 super(webSocket, httpdConnection);
36 // Each connection has only a single associated WebDriver session.
41 * Register a new WebDriver Session to forward the messages to.
43 * @param {Session} session
44 * The WebDriverSession to register.
46 registerSession(session) {
48 throw new error.UnknownError("A WebDriver session has already been set");
51 this.session = session;
52 logger.debug(`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 the JSON-serializable object to the WebDriver BiDi client.
73 * @param {Object} data
74 * The object to be sent.
77 const payload = JSON.stringify(data, null, Log.verbose ? "\t" : null);
78 logger.debug(truncate`${this.session?.id || "no session"} <- ${payload}`);
84 * Send an error back to the WebDriver BiDi client.
87 * Id of the packet which lead to an error.
89 * Error object with `status`, `message` and `stack` attributes.
92 const webDriverError = error.wrap(err);
96 error: webDriverError.status,
97 message: webDriverError.message,
98 stacktrace: webDriverError.stack,
103 * Send an event coming from a module to the WebDriver BiDi client.
105 * @param {String} method
106 * The event name. This is composed by a module name, a dot character
107 * followed by the event name, e.g. `log.entryAdded`.
108 * @param {Object} params
109 * A JSON-serializable object, which is the payload of this event.
111 sendEvent(method, params) {
112 this.send({ method, params });
116 * Send the result of a call to a module's method back to the
117 * WebDriver BiDi client.
120 * The request id being sent by the client to call the module's method.
121 * @param {Object} result
122 * A JSON-serializable object, which is the actual result.
124 sendResult(id, result) {
125 result = typeof result !== "undefined" ? result : {};
126 this.send({ id, result });
132 * Called by the `transport` when the connection is closed.
135 this.unregisterSession();
141 * Receive a packet from the WebSocket layer.
143 * This packet is sent by a WebDriver BiDi client and is meant to execute
144 * a particular method on a given module.
146 * @param Object packet
147 * JSON-serializable object sent by the client
149 async onPacket(packet) {
150 const payload = JSON.stringify(packet, null, Log.verbose ? "\t" : null);
151 logger.debug(truncate`${this.session?.id || "no session"} -> ${payload}`);
153 const { id, method, params } = packet;
156 // First check for mandatory field in the command packet
157 assert.positiveInteger(id, "id: unsigned integer value expected");
158 assert.string(method, "method: string value expected");
159 assert.object(params, "params: object value expected");
161 // Extract the module and the command name out of `method` attribute
162 const { module, command } = splitMethod(method);
165 // Handle static commands first
166 if (module === "session" && command === "new") {
167 // TODO: Needs capability matching code
168 result = await RemoteAgent.webDriverBiDi.createSession(params, this);
169 } else if (module === "session" && command === "status") {
170 result = RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
172 assert.session(this.session);
174 // Bug 1741854 - Workaround to deny internal methods to be called
175 if (command.startsWith("_")) {
176 throw new error.UnknownCommandError(method);
179 // Finally, instruct the session to execute the command
180 result = await this.session.execute(module, command, params);
183 this.sendResult(id, result);
185 this.sendError(packet.id, e);
191 * Splits a WebDriver BiDi method into module and command components.
193 * @param {String} method
194 * Name of the method to split, e.g. "session.subscribe".
196 * @returns {Object<String, String>}
197 * Object with the module ("session") and command ("subscribe")
200 function splitMethod(method) {
201 const parts = method.split(".");
203 if (parts.length != 2 || parts[0].length == 0 || parts[1].length == 0) {
204 throw new TypeError(`Invalid method format: '${method}'`);