Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / remote / shared / webdriver / Session.sys.mjs
blob1c1aadca55570e809ba83a2fafa302c899a126c7
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 file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   accessibility:
9     "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
10   allowAllCerts: "chrome://remote/content/marionette/cert.sys.mjs",
11   Capabilities: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
12   error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
13   generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
14   Log: "chrome://remote/content/shared/Log.sys.mjs",
15   registerProcessDataActor:
16     "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
17   RootMessageHandler:
18     "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
19   RootMessageHandlerRegistry:
20     "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
21   TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
22   unregisterProcessDataActor:
23     "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
24   WebDriverBiDiConnection:
25     "chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs",
26   WebSocketHandshake:
27     "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
28 });
30 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
32 // Global singleton that holds active WebDriver sessions
33 const webDriverSessions = new Map();
35 /**
36  * @typedef {Set} SessionConfigurationFlags
37  *     A set of flags defining the features of a WebDriver session. It can be
38  *     empty or contain entries as listed below. External specifications may
39  *     define additional flags, or create sessions without the HTTP flag.
40  *
41  *     <dl>
42  *       <dt><code>"bidi"</code> (string)
43  *       <dd>Flag indicating a WebDriver BiDi session.
44  *       <dt><code>"http"</code> (string)
45  *       <dd>Flag indicating a WebDriver classic (HTTP) session.
46  *     </dl>
47  */
49 /**
50  * Representation of WebDriver session.
51  */
52 export class WebDriverSession {
53   #bidi;
54   #capabilities;
55   #connections;
56   #http;
57   #id;
58   #messageHandler;
59   #path;
61   static SESSION_FLAG_BIDI = "bidi";
62   static SESSION_FLAG_HTTP = "http";
64   /**
65    * Construct a new WebDriver session.
66    *
67    * It is expected that the caller performs the necessary checks on
68    * the requested capabilities to be WebDriver conforming.  The WebDriver
69    * service offered by Marionette does not match or negotiate capabilities
70    * beyond type- and bounds checks.
71    *
72    * <h3>Capabilities</h3>
73    *
74    * <dl>
75    *  <dt><code>acceptInsecureCerts</code> (boolean)
76    *  <dd>Indicates whether untrusted and self-signed TLS certificates
77    *   are implicitly trusted on navigation for the duration of the session.
78    *
79    *  <dt><code>pageLoadStrategy</code> (string)
80    *  <dd>(HTTP only) The page load strategy to use for the current session.  Must be
81    *   one of "<tt>none</tt>", "<tt>eager</tt>", and "<tt>normal</tt>".
82    *
83    *  <dt><code>proxy</code> (Proxy object)
84    *  <dd>Defines the proxy configuration.
85    *
86    *  <dt><code>setWindowRect</code> (boolean)
87    *  <dd>(HTTP only) Indicates whether the remote end supports all of the resizing
88    *   and repositioning commands.
89    *
90    *  <dt><code>strictFileInteractability</code> (boolean)
91    *  <dd>(HTTP only) Defines the current session’s strict file interactability.
92    *
93    *  <dt><code>timeouts</code> (Timeouts object)
94    *  <dd>(HTTP only) Describes the timeouts imposed on certain session operations.
95    *
96    *  <dt><code>unhandledPromptBehavior</code> (string)
97    *  <dd>Describes the current session’s user prompt handler.  Must be one of
98    *   "<tt>accept</tt>", "<tt>accept and notify</tt>", "<tt>dismiss</tt>",
99    *   "<tt>dismiss and notify</tt>", and "<tt>ignore</tt>".  Defaults to the
100    *   "<tt>dismiss and notify</tt>" state.
101    *
102    *  <dt><code>moz:accessibilityChecks</code> (boolean)
103    *  <dd>(HTTP only) Run a11y checks when clicking elements.
104    *
105    *  <dt><code>moz:debuggerAddress</code> (boolean)
106    *  <dd>Indicate that the Chrome DevTools Protocol (CDP) has to be enabled.
107    *
108    *  <dt><code>moz:webdriverClick</code> (boolean)
109    *  <dd>(HTTP only) Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
110    * </dl>
111    *
112    * <h4>WebAuthn</h4>
113    *
114    * <dl>
115    *  <dt><code>webauthn:virtualAuthenticators</code> (boolean)
116    *  <dd>Indicates whether the endpoint node supports all Virtual
117    *   Authenticators commands.
118    *
119    *  <dt><code>webauthn:extension:uvm</code> (boolean)
120    *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
121    *   implementation supports the User Verification Method extension.
122    *
123    *  <dt><code>webauthn:extension:prf</code> (boolean)
124    *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
125    *   implementation supports the prf extension.
126    *
127    *  <dt><code>webauthn:extension:largeBlob</code> (boolean)
128    *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
129    *   supports the largeBlob extension.
130    *
131    *  <dt><code>webauthn:extension:credBlob</code> (boolean)
132    *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
133    *   supports the credBlob extension.
134    * </dl>
135    *
136    * <h4>Timeouts object</h4>
137    *
138    * <dl>
139    *  <dt><code>script</code> (number)
140    *  <dd>Determines when to interrupt a script that is being evaluates.
141    *
142    *  <dt><code>pageLoad</code> (number)
143    *  <dd>Provides the timeout limit used to interrupt navigation of the
144    *   browsing context.
145    *
146    *  <dt><code>implicit</code> (number)
147    *  <dd>Gives the timeout of when to abort when locating an element.
148    * </dl>
149    *
150    * <h4>Proxy object</h4>
151    *
152    * <dl>
153    *  <dt><code>proxyType</code> (string)
154    *  <dd>Indicates the type of proxy configuration.  Must be one
155    *   of "<tt>pac</tt>", "<tt>direct</tt>", "<tt>autodetect</tt>",
156    *   "<tt>system</tt>", or "<tt>manual</tt>".
157    *
158    *  <dt><code>proxyAutoconfigUrl</code> (string)
159    *  <dd>Defines the URL for a proxy auto-config file if
160    *   <code>proxyType</code> is equal to "<tt>pac</tt>".
161    *
162    *  <dt><code>httpProxy</code> (string)
163    *  <dd>Defines the proxy host for HTTP traffic when the
164    *   <code>proxyType</code> is "<tt>manual</tt>".
165    *
166    *  <dt><code>noProxy</code> (string)
167    *  <dd>Lists the address for which the proxy should be bypassed when
168    *   the <code>proxyType</code> is "<tt>manual</tt>".  Must be a JSON
169    *   List containing any number of any of domains, IPv4 addresses, or IPv6
170    *   addresses.
171    *
172    *  <dt><code>sslProxy</code> (string)
173    *  <dd>Defines the proxy host for encrypted TLS traffic when the
174    *   <code>proxyType</code> is "<tt>manual</tt>".
175    *
176    *  <dt><code>socksProxy</code> (string)
177    *  <dd>Defines the proxy host for a SOCKS proxy traffic when the
178    *   <code>proxyType</code> is "<tt>manual</tt>".
179    *
180    *  <dt><code>socksVersion</code> (string)
181    *  <dd>Defines the SOCKS proxy version when the <code>proxyType</code> is
182    *   "<tt>manual</tt>".  It must be any integer between 0 and 255
183    *   inclusive.
184    * </dl>
185    *
186    * <h3>Example</h3>
187    *
188    * Input:
189    *
190    * <pre><code>
191    *     {"capabilities": {"acceptInsecureCerts": true}}
192    * </code></pre>
193    *
194    * @param {Record<string, *>=} capabilities
195    *     JSON Object containing any of the recognized capabilities listed
196    *     above.
197    * @param {SessionConfigurationFlags} flags
198    *     Session configuration flags.
199    * @param {WebDriverBiDiConnection=} connection
200    *     An optional existing WebDriver BiDi connection to associate with the
201    *     new session.
202    *
203    * @throws {SessionNotCreatedError}
204    *     If, for whatever reason, a session could not be created.
205    */
206   constructor(capabilities, flags, connection) {
207     // WebSocket connections that use this session. This also accounts for
208     // possible disconnects due to network outages, which require clients
209     // to reconnect.
210     this.#connections = new Set();
212     this.#id = lazy.generateUUID();
214     // Flags for WebDriver session features
215     this.#bidi = flags.has(WebDriverSession.SESSION_FLAG_BIDI);
216     this.#http = flags.has(WebDriverSession.SESSION_FLAG_HTTP);
218     if (this.#bidi == this.#http) {
219       // Initially a WebDriver session can either be HTTP or BiDi. An upgrade of a
220       // HTTP session to offer BiDi features is done after the constructor is run.
221       throw new lazy.error.SessionNotCreatedError(
222         `Initially the WebDriver session needs to be either HTTP or BiDi (bidi=${
223           this.#bidi
224         }, http=${this.#http})`
225       );
226     }
228     // Define the HTTP path to query this session via WebDriver BiDi
229     this.#path = `/session/${this.#id}`;
231     try {
232       this.#capabilities = lazy.Capabilities.fromJSON(capabilities, this.#bidi);
233     } catch (e) {
234       throw new lazy.error.SessionNotCreatedError(e);
235     }
237     if (this.proxy.init()) {
238       lazy.logger.info(
239         `Proxy settings initialized: ${JSON.stringify(this.proxy)}`
240       );
241     }
243     if (this.acceptInsecureCerts) {
244       lazy.logger.warn(
245         "TLS certificate errors will be ignored for this session"
246       );
247       lazy.allowAllCerts.enable();
248     }
250     // If we are testing accessibility with marionette, start a11y service in
251     // chrome first. This will ensure that we do not have any content-only
252     // services hanging around.
253     if (this.a11yChecks && lazy.accessibility.service) {
254       lazy.logger.info("Preemptively starting accessibility service in Chrome");
255     }
257     // If a connection without an associated session has been specified
258     // immediately register the newly created session for it.
259     if (connection) {
260       connection.registerSession(this);
261       this.#connections.add(connection);
262     }
264     // Maps a Navigable (browsing context or content browser for top-level
265     // browsing contexts) to a Set of nodeId's.
266     this.navigableSeenNodes = new WeakMap();
268     lazy.registerProcessDataActor();
270     webDriverSessions.set(this.#id, this);
271   }
273   destroy() {
274     webDriverSessions.delete(this.#id);
276     lazy.unregisterProcessDataActor();
278     this.navigableSeenNodes = null;
280     lazy.allowAllCerts.disable();
282     // Close all open connections which unregister themselves.
283     this.#connections.forEach(connection => connection.close());
284     if (this.#connections.size > 0) {
285       lazy.logger.warn(
286         `Failed to close ${this.#connections.size} WebSocket connections`
287       );
288     }
290     // Destroy the dedicated MessageHandler instance if we created one.
291     if (this.#messageHandler) {
292       this.#messageHandler.off(
293         "message-handler-protocol-event",
294         this._onMessageHandlerProtocolEvent
295       );
296       this.#messageHandler.destroy();
297     }
298   }
300   get a11yChecks() {
301     return this.#capabilities.get("moz:accessibilityChecks");
302   }
304   get acceptInsecureCerts() {
305     return this.#capabilities.get("acceptInsecureCerts");
306   }
308   get bidi() {
309     return this.#bidi;
310   }
312   set bidi(value) {
313     this.#bidi = value;
314   }
316   get capabilities() {
317     return this.#capabilities;
318   }
320   get http() {
321     return this.#http;
322   }
324   get id() {
325     return this.#id;
326   }
328   get messageHandler() {
329     if (!this.#messageHandler) {
330       this.#messageHandler =
331         lazy.RootMessageHandlerRegistry.getOrCreateMessageHandler(this.#id);
332       this._onMessageHandlerProtocolEvent =
333         this._onMessageHandlerProtocolEvent.bind(this);
334       this.#messageHandler.on(
335         "message-handler-protocol-event",
336         this._onMessageHandlerProtocolEvent
337       );
338     }
340     return this.#messageHandler;
341   }
343   get pageLoadStrategy() {
344     return this.#capabilities.get("pageLoadStrategy");
345   }
347   get path() {
348     return this.#path;
349   }
351   get proxy() {
352     return this.#capabilities.get("proxy");
353   }
355   get strictFileInteractability() {
356     return this.#capabilities.get("strictFileInteractability");
357   }
359   get timeouts() {
360     return this.#capabilities.get("timeouts");
361   }
363   set timeouts(timeouts) {
364     this.#capabilities.set("timeouts", timeouts);
365   }
367   get userPromptHandler() {
368     return this.#capabilities.get("unhandledPromptBehavior");
369   }
371   get webSocketUrl() {
372     return this.#capabilities.get("webSocketUrl");
373   }
375   async execute(module, command, params) {
376     // XXX: At the moment, commands do not describe consistently their destination,
377     // so we will need a translation step based on a specific command and its params
378     // in order to extract a destination that can be understood by the MessageHandler.
379     //
380     // For now, an option is to send all commands to ROOT, and all BiDi MessageHandler
381     // modules will therefore need to implement this translation step in the root
382     // implementation of their module.
383     const destination = {
384       type: lazy.RootMessageHandler.type,
385     };
386     if (!this.messageHandler.supportsCommand(module, command, destination)) {
387       throw new lazy.error.UnknownCommandError(`${module}.${command}`);
388     }
390     return this.messageHandler.handleCommand({
391       moduleName: module,
392       commandName: command,
393       params,
394       destination,
395     });
396   }
398   /**
399    * Remove the specified WebDriver BiDi connection.
400    *
401    * @param {WebDriverBiDiConnection} connection
402    */
403   removeConnection(connection) {
404     if (this.#connections.has(connection)) {
405       this.#connections.delete(connection);
406     } else {
407       lazy.logger.warn("Trying to remove a connection that doesn't exist.");
408     }
409   }
411   toString() {
412     return `[object ${this.constructor.name} ${this.#id}]`;
413   }
415   // nsIHttpRequestHandler
417   /**
418    * Handle new WebSocket connection requests.
419    *
420    * WebSocket clients will attempt to connect to this session at
421    * `/session/:id`.  Hereby a WebSocket upgrade will automatically
422    * be performed.
423    *
424    * @param {Request} request
425    *     HTTP request (httpd.js)
426    * @param {Response} response
427    *     Response to an HTTP request (httpd.js)
428    */
429   async handle(request, response) {
430     const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
431     const conn = new lazy.WebDriverBiDiConnection(
432       webSocket,
433       response._connection
434     );
435     conn.registerSession(this);
436     this.#connections.add(conn);
437   }
439   _onMessageHandlerProtocolEvent(eventName, messageHandlerEvent) {
440     const { name, data } = messageHandlerEvent;
441     this.#connections.forEach(connection => connection.sendEvent(name, data));
442   }
444   // XPCOM
446   get QueryInterface() {
447     return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
448   }
452  * Get the list of seen nodes for the given browsing context unique to a
453  * WebDriver session.
455  * @param {string} sessionId
456  *     The id of the WebDriver session to use.
457  * @param {BrowsingContext} browsingContext
458  *     Browsing context the node is part of.
460  * @returns {Set}
461  *     The list of seen nodes.
462  */
463 export function getSeenNodesForBrowsingContext(sessionId, browsingContext) {
464   if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
465     // If browsingContext is not a valid Browsing Context, return an empty set.
466     return new Set();
467   }
469   const navigable =
470     lazy.TabManager.getNavigableForBrowsingContext(browsingContext);
471   const session = getWebDriverSessionById(sessionId);
473   if (!session.navigableSeenNodes.has(navigable)) {
474     // The navigable hasn't been seen yet.
475     session.navigableSeenNodes.set(navigable, new Set());
476   }
478   return session.navigableSeenNodes.get(navigable);
483  * @param {string} sessionId
484  *     The ID of the WebDriver session to retrieve.
486  * @returns {WebDriverSession|undefined}
487  *     The WebDriver session or undefined if the id is not known.
488  */
489 export function getWebDriverSessionById(sessionId) {
490   return webDriverSessions.get(sessionId);