Bug 1793691 - adjust test-info-all to include manifests. r=gbrown
[gecko.git] / remote / webdriver-bidi / WebDriverBiDiConnection.sys.mjs
blob07819c775a9124e9a075480ccbdde1a086e84dc0
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";
9 const lazy = {};
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",
16 });
18 XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
19   lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
22 export class WebDriverBiDiConnection extends WebSocketConnection {
23   /**
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.
28    */
29   constructor(webSocket, httpdConnection) {
30     super(webSocket, httpdConnection);
32     // Each connection has only a single associated WebDriver session.
33     this.session = null;
34   }
36   /**
37    * Register a new WebDriver Session to forward the messages to.
38    *
39    * @param {Session} session
40    *     The WebDriverSession to register.
41    */
42   registerSession(session) {
43     if (this.session) {
44       throw new lazy.error.UnknownError(
45         "A WebDriver session has already been set"
46       );
47     }
49     this.session = session;
50     lazy.logger.debug(
51       `Connection ${this.id} attached to session ${session.id}`
52     );
53   }
55   /**
56    * Unregister the already set WebDriver session.
57    *
58    * @param {Session} session
59    *     The WebDriverSession to register.
60    */
61   unregisterSession() {
62     if (!this.session) {
63       return;
64     }
66     this.session.removeConnection(this);
67     this.session = null;
68   }
70   /**
71    * Send an error back to the WebDriver BiDi client.
72    *
73    * @param {Number} id
74    *     Id of the packet which lead to an error.
75    * @param {Error} err
76    *     Error object with `status`, `message` and `stack` attributes.
77    */
78   sendError(id, err) {
79     const webDriverError = lazy.error.wrap(err);
81     this.send({
82       id,
83       error: webDriverError.status,
84       message: webDriverError.message,
85       stacktrace: webDriverError.stack,
86     });
87   }
89   /**
90    * Send an event coming from a module to the WebDriver BiDi client.
91    *
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.
97    */
98   sendEvent(method, params) {
99     this.send({ method, params });
100   }
102   /**
103    * Send the result of a call to a module's method back to the
104    * WebDriver BiDi client.
105    *
106    * @param {Number} id
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.
110    */
111   sendResult(id, result) {
112     result = typeof result !== "undefined" ? result : {};
113     this.send({ id, result });
114   }
116   // Transport hooks
118   /**
119    * Called by the `transport` when the connection is closed.
120    */
121   onClosed() {
122     this.unregisterSession();
124     super.onClosed();
125   }
127   /**
128    * Receive a packet from the WebSocket layer.
129    *
130    * This packet is sent by a WebDriver BiDi client and is meant to execute
131    * a particular method on a given module.
132    *
133    * @param Object packet
134    *        JSON-serializable object sent by the client
135    */
136   async onPacket(packet) {
137     super.onPacket(packet);
139     const { id, method, params } = packet;
141     try {
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);
149       let result;
151       // Handle static commands first
152       if (module === "session" && command === "new") {
153         // TODO: Needs capability matching code
154         result = await lazy.RemoteAgent.webDriverBiDi.createSession(
155           params,
156           this
157         );
158       } else if (module === "session" && command === "status") {
159         result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
160       } else {
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);
166         }
168         // Finally, instruct the session to execute the command
169         result = await this.session.execute(module, command, params);
170       }
172       this.sendResult(id, result);
173     } catch (e) {
174       this.sendError(packet.id, e);
175     }
176   }
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")
187  *     as properties.
188  */
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}'`);
194   }
196   return {
197     module: parts[0],
198     command: parts[1],
199   };