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";
9 ChromeUtils.defineESModuleGetters(lazy, {
10 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
11 Log: "chrome://remote/content/shared/Log.sys.mjs",
12 WebDriverNewSessionHandler:
13 "chrome://remote/content/webdriver-bidi/NewSessionHandler.sys.mjs",
14 WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
17 XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
18 lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
20 XPCOMUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder());
23 * Entry class for the WebDriver BiDi support.
25 * @see https://w3c.github.io/webdriver-bidi
27 export class WebDriverBiDi {
29 * Creates a new instance of the WebDriverBiDi class.
31 * @param {RemoteAgent} agent
32 * Reference to the Remote Agent instance.
36 this._running = false;
39 this._sessionlessConnections = new Set();
43 return `ws://${this.agent.host}:${this.agent.port}`;
51 * Add a new connection that is not yet attached to a WebDriver session.
53 * @param {WebDriverBiDiConnection} connection
54 * The connection without an accociated WebDriver session.
56 addSessionlessConnection(connection) {
57 this._sessionlessConnections.add(connection);
61 * Create a new WebDriver session.
63 * @param {Object<string, *>=} capabilities
64 * JSON Object containing any of the recognised capabilities as listed
65 * on the `WebDriverSession` class.
67 * @param {WebDriverBiDiConnection=} sessionlessConnection
68 * Optional connection that is not yet accociated with a WebDriver
69 * session, and has to be associated with the new WebDriver session.
71 * @returns {Object<string, Capabilities>}
72 * Object containing the current session ID, and all its capabilities.
74 * @throws {SessionNotCreatedError}
75 * If, for whatever reason, a session could not be created.
77 async createSession(capabilities, sessionlessConnection) {
79 throw new lazy.error.SessionNotCreatedError(
80 "Maximum number of active sessions"
84 const session = new lazy.WebDriverSession(
89 // When the Remote Agent is listening, and a BiDi WebSocket connection
90 // has been requested, register a path handler for the session.
91 let webSocketUrl = null;
94 (session.capabilities.get("webSocketUrl") || sessionlessConnection)
96 // Creating a WebDriver BiDi session too early can cause issues with
97 // clients in not being able to find any available browsing context.
98 // Also when closing the application while it's still starting up can
99 // cause shutdown hangs. As such WebDriver BiDi will return a new session
100 // once the initial application window has finished initializing.
101 lazy.logger.debug(`Waiting for initial application window`);
102 await this.agent.browserStartupFinished;
104 this.agent.server.registerPathHandler(session.path, session);
105 webSocketUrl = `${this.address}${session.path}`;
107 lazy.logger.debug(`Registered session handler: ${session.path}`);
109 if (sessionlessConnection) {
110 // Remove temporary session-less connection
111 this._sessionlessConnections.delete(sessionlessConnection);
115 // Also update the webSocketUrl capability to contain the session URL if
116 // a path handler has been registered. Otherwise set its value to null.
117 session.capabilities.set("webSocketUrl", webSocketUrl);
119 this._session = session;
122 sessionId: this.session.id,
123 capabilities: this.session.capabilities,
128 * Delete the current WebDriver session.
135 // When the Remote Agent is listening, and a BiDi WebSocket is active,
136 // unregister the path handler for the session.
137 if (this.agent.running && this.session.capabilities.get("webSocketUrl")) {
138 this.agent.server.registerPathHandler(this.session.path, null);
139 lazy.logger.debug(`Unregistered session handler: ${this.session.path}`);
142 this.session.destroy();
143 this._session = null;
147 * Retrieve the readiness state of the remote end, regarding the creation of
148 * new WebDriverBiDi sessions.
150 * See https://w3c.github.io/webdriver-bidi/#command-session-status
153 * The readiness state.
155 getSessionReadinessStatus() {
157 // We currently only support one session, see Bug 1720707.
160 message: "Session already started",
171 * Starts the WebDriver BiDi support.
178 this._running = true;
180 // Install a HTTP handler for direct WebDriver BiDi connection requests.
181 this.agent.server.registerPathHandler(
183 new lazy.WebDriverNewSessionHandler(this)
186 Cu.printStderr(`WebDriver BiDi listening on ${this.address}\n`);
188 // Write WebSocket connection details to the WebDriverBiDiServer.json file
189 // located within the application's profile.
190 this._bidiServerPath = PathUtils.join(
191 PathUtils.profileDir,
192 "WebDriverBiDiServer.json"
196 ws_host: this.agent.host,
197 ws_port: this.agent.port,
202 this._bidiServerPath,
203 lazy.textEncoder.encode(JSON.stringify(data, undefined, " "))
207 `Failed to create ${this._bidiServerPath} (${e.message})`
213 * Stops the WebDriver BiDi support.
216 if (!this._running) {
221 await IOUtils.remove(this._bidiServerPath);
224 `Failed to remove ${this._bidiServerPath} (${e.message})`
229 // Close open session
230 this.deleteSession();
231 this.agent.server.registerPathHandler("/session", null);
233 // Close all open session-less connections
234 this._sessionlessConnections.forEach(connection => connection.close());
235 this._sessionlessConnections.clear();
237 lazy.logger.error("Failed to stop protocol", e);
239 this._running = false;