Bug 1766413 [wpt PR 33787] - Fix some cases of "ref test" to be "reftest", a=testonly
[gecko.git] / remote / webdriver-bidi / WebDriverBiDiConnection.jsm
blobd571fdbd7c5cba7f7ff0ae3a8fc1d848bcee6782
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 "use strict";
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",
20 });
22 XPCOMUtils.defineLazyGetter(this, "logger", () =>
23   Log.get(Log.TYPES.WEBDRIVER_BIDI)
26 class WebDriverBiDiConnection extends WebSocketConnection {
27   /**
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.
32    */
33   constructor(webSocket, httpdConnection) {
34     super(webSocket, httpdConnection);
36     // Each connection has only a single associated WebDriver session.
37     this.session = null;
38   }
40   /**
41    * Register a new WebDriver Session to forward the messages to.
42    *
43    * @param {Session} session
44    *     The WebDriverSession to register.
45    */
46   registerSession(session) {
47     if (this.session) {
48       throw new error.UnknownError("A WebDriver session has already been set");
49     }
51     this.session = session;
52     logger.debug(`Connection ${this.id} attached to session ${session.id}`);
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 the JSON-serializable object to the WebDriver BiDi client.
72    *
73    * @param {Object} data
74    *     The object to be sent.
75    */
76   send(data) {
77     const payload = JSON.stringify(data, null, Log.verbose ? "\t" : null);
78     logger.debug(truncate`${this.session?.id || "no session"} <- ${payload}`);
80     super.send(data);
81   }
83   /**
84    * Send an error back to the WebDriver BiDi client.
85    *
86    * @param {Number} id
87    *     Id of the packet which lead to an error.
88    * @param {Error} err
89    *     Error object with `status`, `message` and `stack` attributes.
90    */
91   sendError(id, err) {
92     const webDriverError = error.wrap(err);
94     this.send({
95       id,
96       error: webDriverError.status,
97       message: webDriverError.message,
98       stacktrace: webDriverError.stack,
99     });
100   }
102   /**
103    * Send an event coming from a module to the WebDriver BiDi client.
104    *
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.
110    */
111   sendEvent(method, params) {
112     this.send({ method, params });
113   }
115   /**
116    * Send the result of a call to a module's method back to the
117    * WebDriver BiDi client.
118    *
119    * @param {Number} id
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.
123    */
124   sendResult(id, result) {
125     result = typeof result !== "undefined" ? result : {};
126     this.send({ id, result });
127   }
129   // Transport hooks
131   /**
132    * Called by the `transport` when the connection is closed.
133    */
134   onClosed() {
135     this.unregisterSession();
137     super.onClosed();
138   }
140   /**
141    * Receive a packet from the WebSocket layer.
142    *
143    * This packet is sent by a WebDriver BiDi client and is meant to execute
144    * a particular method on a given module.
145    *
146    * @param Object packet
147    *        JSON-serializable object sent by the client
148    */
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;
155     try {
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);
163       let result;
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();
171       } else {
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);
177         }
179         // Finally, instruct the session to execute the command
180         result = await this.session.execute(module, command, params);
181       }
183       this.sendResult(id, result);
184     } catch (e) {
185       this.sendError(packet.id, e);
186     }
187   }
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")
198  *     as properties.
199  */
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}'`);
205   }
207   return {
208     module: parts[0],
209     command: parts[1],
210   };