tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Session.sys.mjs (20166B)


      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/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     11 
     12  accessibility:
     13    "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
     14  Capabilities: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
     15  Certificates: "chrome://remote/content/shared/webdriver/Certificates.sys.mjs",
     16  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     17  FilePickerHandler:
     18    "chrome://remote/content/shared/webdriver/FilePickerHandler.sys.mjs",
     19  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
     20  Log: "chrome://remote/content/shared/Log.sys.mjs",
     21  NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs",
     22  registerProcessDataActor:
     23    "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
     24  RootMessageHandler:
     25    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
     26  RootMessageHandlerRegistry:
     27    "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
     28  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
     29  unregisterProcessDataActor:
     30    "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
     31  WebDriverBiDiConnection:
     32    "chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs",
     33  WebSocketHandshake:
     34    "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
     35  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
     36 });
     37 
     38 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
     39 
     40 // Bug 1999693: This preference is a temporary workaround until clients can use
     41 // the unhandledPromptBehavior capability to decide if file pickers should be
     42 // dismissed or not.
     43 XPCOMUtils.defineLazyPreferenceGetter(
     44  lazy,
     45  "dismissFilePickersEnabled",
     46  "remote.bidi.dismiss_file_pickers.enabled",
     47  false
     48 );
     49 
     50 XPCOMUtils.defineLazyServiceGetter(
     51  lazy,
     52  "aomStartup",
     53  "@mozilla.org/addons/addon-manager-startup;1",
     54  Ci.amIAddonManagerStartup
     55 );
     56 
     57 // Global singleton that holds active WebDriver sessions
     58 const webDriverSessions = new Map();
     59 
     60 /**
     61 * @typedef {Set} SessionConfigurationFlags
     62 *     A set of flags defining the features of a WebDriver session. It can be
     63 *     empty or contain entries as listed below. External specifications may
     64 *     define additional flags, or create sessions without the HTTP flag.
     65 *
     66 *     <dl>
     67 *       <dt><code>"bidi"</code> (string)
     68 *       <dd>Flag indicating a WebDriver BiDi session.
     69 *       <dt><code>"http"</code> (string)
     70 *       <dd>Flag indicating a WebDriver classic (HTTP) session.
     71 *     </dl>
     72 */
     73 
     74 /**
     75 * Representation of WebDriver session.
     76 */
     77 export class WebDriverSession {
     78  #bidi;
     79  #capabilities;
     80  #chromeProtocolHandles;
     81  #connections;
     82  #http;
     83  #id;
     84  #messageHandler;
     85  #navigableSeenNodes;
     86  #path;
     87 
     88  static SESSION_FLAG_BIDI = "bidi";
     89  static SESSION_FLAG_HTTP = "http";
     90 
     91  /**
     92   * Construct a new WebDriver session.
     93   *
     94   * It is expected that the caller performs the necessary checks on
     95   * the requested capabilities to be WebDriver conforming.  The WebDriver
     96   * service offered by Marionette does not match or negotiate capabilities
     97   * beyond type- and bounds checks.
     98   *
     99   * <h3>Capabilities</h3>
    100   *
    101   * <dl>
    102   *  <dt><code>acceptInsecureCerts</code> (boolean)
    103   *  <dd>Indicates whether untrusted and self-signed TLS certificates
    104   *   are implicitly trusted on navigation for the duration of the session.
    105   *
    106   *  <dt><code>pageLoadStrategy</code> (string)
    107   *  <dd>(HTTP only) The page load strategy to use for the current session.  Must be
    108   *   one of "<tt>none</tt>", "<tt>eager</tt>", and "<tt>normal</tt>".
    109   *
    110   *  <dt><code>proxy</code> (Proxy object)
    111   *  <dd>Defines the proxy configuration.
    112   *
    113   *  <dt><code>setWindowRect</code> (boolean)
    114   *  <dd>(HTTP only) Indicates whether the remote end supports all of the resizing
    115   *   and repositioning commands.
    116   *
    117   *  <dt><code>strictFileInteractability</code> (boolean)
    118   *  <dd>(HTTP only) Defines the current session’s strict file interactability.
    119   *
    120   *  <dt><code>timeouts</code> (Timeouts object)
    121   *  <dd>(HTTP only) Describes the timeouts imposed on certain session operations.
    122   *
    123   *  <dt><code>unhandledPromptBehavior</code> (string)
    124   *  <dd>Describes the current session’s user prompt handler.  Must be one of
    125   *   "<tt>accept</tt>", "<tt>accept and notify</tt>", "<tt>dismiss</tt>",
    126   *   "<tt>dismiss and notify</tt>", and "<tt>ignore</tt>".  Defaults to the
    127   *   "<tt>dismiss and notify</tt>" state.
    128   *
    129   *  <dt><code>moz:accessibilityChecks</code> (boolean)
    130   *  <dd>(HTTP only) Run a11y checks when clicking elements.
    131   *
    132   *  <dt><code>moz:webdriverClick</code> (boolean)
    133   *  <dd>(HTTP only) Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
    134   * </dl>
    135   *
    136   * <h4>WebAuthn</h4>
    137   *
    138   * <dl>
    139   *  <dt><code>webauthn:virtualAuthenticators</code> (boolean)
    140   *  <dd>Indicates whether the endpoint node supports all Virtual
    141   *   Authenticators commands.
    142   *
    143   *  <dt><code>webauthn:extension:uvm</code> (boolean)
    144   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
    145   *   implementation supports the User Verification Method extension.
    146   *
    147   *  <dt><code>webauthn:extension:prf</code> (boolean)
    148   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
    149   *   implementation supports the prf extension.
    150   *
    151   *  <dt><code>webauthn:extension:largeBlob</code> (boolean)
    152   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
    153   *   supports the largeBlob extension.
    154   *
    155   *  <dt><code>webauthn:extension:credBlob</code> (boolean)
    156   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
    157   *   supports the credBlob extension.
    158   * </dl>
    159   *
    160   * <h4>Timeouts object</h4>
    161   *
    162   * <dl>
    163   *  <dt><code>script</code> (number)
    164   *  <dd>Determines when to interrupt a script that is being evaluates.
    165   *
    166   *  <dt><code>pageLoad</code> (number)
    167   *  <dd>Provides the timeout limit used to interrupt navigation of the
    168   *   browsing context.
    169   *
    170   *  <dt><code>implicit</code> (number)
    171   *  <dd>Gives the timeout of when to abort when locating an element.
    172   * </dl>
    173   *
    174   * <h4>Proxy object</h4>
    175   *
    176   * <dl>
    177   *  <dt><code>proxyType</code> (string)
    178   *  <dd>Indicates the type of proxy configuration.  Must be one
    179   *   of "<tt>pac</tt>", "<tt>direct</tt>", "<tt>autodetect</tt>",
    180   *   "<tt>system</tt>", or "<tt>manual</tt>".
    181   *
    182   *  <dt><code>proxyAutoconfigUrl</code> (string)
    183   *  <dd>Defines the URL for a proxy auto-config file if
    184   *   <code>proxyType</code> is equal to "<tt>pac</tt>".
    185   *
    186   *  <dt><code>httpProxy</code> (string)
    187   *  <dd>Defines the proxy host for HTTP traffic when the
    188   *   <code>proxyType</code> is "<tt>manual</tt>".
    189   *
    190   *  <dt><code>noProxy</code> (string)
    191   *  <dd>Lists the address for which the proxy should be bypassed when
    192   *   the <code>proxyType</code> is "<tt>manual</tt>".  Must be a JSON
    193   *   List containing any number of any of domains, IPv4 addresses, or IPv6
    194   *   addresses.
    195   *
    196   *  <dt><code>sslProxy</code> (string)
    197   *  <dd>Defines the proxy host for encrypted TLS traffic when the
    198   *   <code>proxyType</code> is "<tt>manual</tt>".
    199   *
    200   *  <dt><code>socksProxy</code> (string)
    201   *  <dd>Defines the proxy host for a SOCKS proxy traffic when the
    202   *   <code>proxyType</code> is "<tt>manual</tt>".
    203   *
    204   *  <dt><code>socksVersion</code> (string)
    205   *  <dd>Defines the SOCKS proxy version when the <code>proxyType</code> is
    206   *   "<tt>manual</tt>".  It must be any integer between 0 and 255
    207   *   inclusive.
    208   * </dl>
    209   *
    210   * <h3>Example</h3>
    211   *
    212   * Input:
    213   *
    214   * <pre><code>
    215   *     {"capabilities": {"acceptInsecureCerts": true}}
    216   * </code></pre>
    217   *
    218   * @param {Record<string, *>=} capabilities
    219   *     JSON Object containing any of the recognized capabilities listed
    220   *     above.
    221   * @param {SessionConfigurationFlags} flags
    222   *     Session configuration flags.
    223   * @param {WebDriverBiDiConnection=} connection
    224   *     An optional existing WebDriver BiDi connection to associate with the
    225   *     new session.
    226   *
    227   * @throws {SessionNotCreatedError}
    228   *     If, for whatever reason, a session could not be created.
    229   */
    230  constructor(capabilities, flags, connection) {
    231    // List of handles for registered chrome:// URLs
    232    this.#chromeProtocolHandles = new Map();
    233 
    234    // WebSocket connections that use this session. This also accounts for
    235    // possible disconnects due to network outages, which require clients
    236    // to reconnect.
    237    this.#connections = new Set();
    238 
    239    this.#id = lazy.generateUUID();
    240 
    241    // Flags for WebDriver session features
    242    this.#bidi = flags.has(WebDriverSession.SESSION_FLAG_BIDI);
    243    this.#http = flags.has(WebDriverSession.SESSION_FLAG_HTTP);
    244 
    245    if (this.#bidi == this.#http) {
    246      // Initially a WebDriver session can either be HTTP or BiDi. An upgrade of a
    247      // HTTP session to offer BiDi features is done after the constructor is run.
    248      throw new lazy.error.SessionNotCreatedError(
    249        `Initially the WebDriver session needs to be either HTTP or BiDi (bidi=${
    250          this.#bidi
    251        }, http=${this.#http})`
    252      );
    253    }
    254 
    255    // Define the HTTP path to query this session via WebDriver BiDi
    256    this.#path = `/session/${this.#id}`;
    257 
    258    try {
    259      this.#capabilities = lazy.Capabilities.fromJSON(capabilities, this.#bidi);
    260    } catch (e) {
    261      throw new lazy.error.SessionNotCreatedError(e);
    262    }
    263 
    264    if (this.proxy.init()) {
    265      lazy.logger.info(
    266        `Proxy settings initialized: ${JSON.stringify(this.proxy)}`
    267      );
    268    }
    269 
    270    if (this.acceptInsecureCerts) {
    271      lazy.logger.warn(
    272        "TLS certificate errors will be ignored for this session"
    273      );
    274      lazy.Certificates.disableSecurityChecks();
    275    }
    276 
    277    // If we are testing accessibility with marionette, start a11y service in
    278    // chrome first. This will ensure that we do not have any content-only
    279    // services hanging around.
    280    if (this.a11yChecks && lazy.accessibility.service) {
    281      lazy.logger.info("Preemptively starting accessibility service in Chrome");
    282    }
    283 
    284    // If a connection without an associated session has been specified
    285    // immediately register the newly created session for it.
    286    if (connection) {
    287      connection.registerSession(this);
    288      this.#connections.add(connection);
    289    }
    290 
    291    // Maps a Navigable (browsing context or content browser for top-level
    292    // browsing contexts) to a Set of nodeId's.
    293    this.#navigableSeenNodes = new WeakMap();
    294 
    295    lazy.registerProcessDataActor();
    296 
    297    // Start the tracking of browsing contexts to create Navigable ids.
    298    lazy.NavigableManager.startTracking();
    299    lazy.windowManager.startTracking();
    300 
    301    webDriverSessions.set(this.#id, this);
    302  }
    303 
    304  destroy() {
    305    webDriverSessions.delete(this.#id);
    306 
    307    // Stop the tracking of browsing contexts when no WebDriver
    308    // session exists anymore.
    309    lazy.NavigableManager.stopTracking();
    310    lazy.windowManager.stopTracking();
    311 
    312    lazy.unregisterProcessDataActor();
    313 
    314    this.#navigableSeenNodes = null;
    315 
    316    lazy.Certificates.enableSecurityChecks();
    317 
    318    // Close all open connections which unregister themselves.
    319    this.#connections.forEach(connection => connection.close());
    320    if (this.#connections.size > 0) {
    321      lazy.logger.warn(
    322        `Failed to close ${this.#connections.size} WebSocket connections`
    323      );
    324    }
    325 
    326    // For the WebDriver BiDi session cleanup, the root network module is
    327    // responsible for resuming requests in the blocked request map.
    328    // See root NetworkModule.destroy().
    329 
    330    // Destroy the dedicated MessageHandler instance if we created one.
    331    if (this.#messageHandler) {
    332      this.#messageHandler.off(
    333        "message-handler-protocol-event",
    334        this._onMessageHandlerProtocolEvent
    335      );
    336      this.#messageHandler.destroy();
    337 
    338      // Note: do not check lazy.dismissFilePickersEnabled, the preference might
    339      // have been updated at runtime. allowFilePickers(this) is safe to call,
    340      // if there was no corresponding dismissFilePickers(this), it will be a
    341      // no-op.
    342      lazy.FilePickerHandler.allowFilePickers(this);
    343    }
    344 
    345    for (const id of this.#chromeProtocolHandles.keys()) {
    346      this.unregisterChromeHandler(id);
    347    }
    348  }
    349 
    350  get a11yChecks() {
    351    return this.#capabilities.get("moz:accessibilityChecks");
    352  }
    353 
    354  get acceptInsecureCerts() {
    355    return this.#capabilities.get("acceptInsecureCerts");
    356  }
    357 
    358  get bidi() {
    359    return this.#bidi;
    360  }
    361 
    362  set bidi(value) {
    363    this.#bidi = value;
    364  }
    365 
    366  get capabilities() {
    367    return this.#capabilities;
    368  }
    369 
    370  get http() {
    371    return this.#http;
    372  }
    373 
    374  get id() {
    375    return this.#id;
    376  }
    377 
    378  get messageHandler() {
    379    if (!this.#messageHandler) {
    380      this.#messageHandler =
    381        lazy.RootMessageHandlerRegistry.getOrCreateMessageHandler(this.#id);
    382      this._onMessageHandlerProtocolEvent =
    383        this._onMessageHandlerProtocolEvent.bind(this);
    384      this.#messageHandler.on(
    385        "message-handler-protocol-event",
    386        this._onMessageHandlerProtocolEvent
    387      );
    388 
    389      // Bug 2005673: Only enable dismissing file pickers lazily if the session
    390      // explicitly starts handling BiDi commands.
    391      if (lazy.dismissFilePickersEnabled) {
    392        // Temporarily dismiss all file pickers.
    393        // Bug 1999693: File pickers should only be dismissed when the unhandled
    394        // prompt behaviour for type "file" is not set to "ignore".
    395        lazy.FilePickerHandler.dismissFilePickers(this);
    396      }
    397    }
    398 
    399    return this.#messageHandler;
    400  }
    401 
    402  get navigableSeenNodes() {
    403    return this.#navigableSeenNodes;
    404  }
    405 
    406  get pageLoadStrategy() {
    407    return this.#capabilities.get("pageLoadStrategy");
    408  }
    409 
    410  get path() {
    411    return this.#path;
    412  }
    413 
    414  get proxy() {
    415    return this.#capabilities.get("proxy");
    416  }
    417 
    418  get strictFileInteractability() {
    419    return this.#capabilities.get("strictFileInteractability");
    420  }
    421 
    422  get timeouts() {
    423    return this.#capabilities.get("timeouts");
    424  }
    425 
    426  set timeouts(timeouts) {
    427    this.#capabilities.set("timeouts", timeouts);
    428  }
    429 
    430  get userPromptHandler() {
    431    return this.#capabilities.get("unhandledPromptBehavior");
    432  }
    433 
    434  get webSocketUrl() {
    435    return this.#capabilities.get("webSocketUrl");
    436  }
    437 
    438  async execute(module, command, params) {
    439    // XXX: At the moment, commands do not describe consistently their destination,
    440    // so we will need a translation step based on a specific command and its params
    441    // in order to extract a destination that can be understood by the MessageHandler.
    442    //
    443    // For now, an option is to send all commands to ROOT, and all BiDi MessageHandler
    444    // modules will therefore need to implement this translation step in the root
    445    // implementation of their module.
    446    const destination = {
    447      type: lazy.RootMessageHandler.type,
    448    };
    449    if (!this.messageHandler.supportsCommand(module, command, destination)) {
    450      throw new lazy.error.UnknownCommandError(`${module}.${command}`);
    451    }
    452 
    453    return this.messageHandler.handleCommand({
    454      moduleName: module,
    455      commandName: command,
    456      params,
    457      destination,
    458    });
    459  }
    460 
    461  /**
    462   * Register a chrome protocol handler for a directory containing XHTML or XUL
    463   * files, allowing them to be loaded via the chrome:// protocol.
    464   *
    465   * @param {string} manifestPath
    466   *     The base manifest path for the entries. URL values are resolved
    467   *     relative to this path.
    468   * @param {Array<Array<string, string, string>>} entries
    469   *     An array of arrays, each containing a registry entry (type, namespace,
    470   *     path, options) as it would appear in a chrome.manifest file. Only the
    471   *     following entry types are currently accepted:
    472   *
    473   *         - "content" A URL entry. Must be a 3-element array.
    474   *         - "override" A URL override entry. Must be a 3-element array.
    475   *         - "locale" A locale package entry. Must be a 4-element array.
    476   *
    477   * @returns {string} id
    478   *     The identifier for the registered chrome protocol handler.
    479   */
    480  registerChromeHandler(manifestPath, entries) {
    481    const manifest = new lazy.FileUtils.File(manifestPath);
    482    const rootURI = Services.io.newFileURI(manifest.parent);
    483    const manifestURI = Services.io.newURI(manifest.leafName, null, rootURI);
    484 
    485    const handle = lazy.aomStartup.registerChrome(manifestURI, entries);
    486    const id = lazy.generateUUID();
    487 
    488    this.#chromeProtocolHandles.set(id, handle);
    489 
    490    return id;
    491  }
    492 
    493  /**
    494   * Unregister a previously registered chrome protocol handler.
    495   *
    496   * @param {string} id
    497   *     The identifier returned when the chrome protocol handler was registered.
    498   *
    499   * @throws {UnknownError}
    500   *     If there is no such registered chrome protocol handler.
    501   */
    502  unregisterChromeHandler(id) {
    503    if (!this.#chromeProtocolHandles.has(id)) {
    504      throw new lazy.error.UnknownError(
    505        `Id ${id} is not a known chrome protocol handler`
    506      );
    507    }
    508 
    509    const handle = this.#chromeProtocolHandles.get(id);
    510    this.#chromeProtocolHandles.delete(id);
    511    handle.destruct();
    512  }
    513 
    514  /**
    515   * Remove the specified WebDriver BiDi connection.
    516   *
    517   * @param {WebDriverBiDiConnection} connection
    518   */
    519  removeConnection(connection) {
    520    if (this.#connections.has(connection)) {
    521      this.#connections.delete(connection);
    522    } else {
    523      lazy.logger.warn("Trying to remove a connection that doesn't exist.");
    524    }
    525  }
    526 
    527  toString() {
    528    return `[object ${this.constructor.name} ${this.#id}]`;
    529  }
    530 
    531  // nsIHttpRequestHandler
    532 
    533  /**
    534   * Handle new WebSocket connection requests.
    535   *
    536   * WebSocket clients will attempt to connect to this session at
    537   * `/session/:id`.  Hereby a WebSocket upgrade will automatically
    538   * be performed.
    539   *
    540   * @param {Request} request
    541   *     HTTP request (httpd.js)
    542   * @param {Response} response
    543   *     Response to an HTTP request (httpd.js)
    544   */
    545  async handle(request, response) {
    546    const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
    547    const conn = new lazy.WebDriverBiDiConnection(
    548      webSocket,
    549      response._connection
    550    );
    551    conn.registerSession(this);
    552    this.#connections.add(conn);
    553  }
    554 
    555  _onMessageHandlerProtocolEvent(eventName, messageHandlerEvent) {
    556    const { name, data } = messageHandlerEvent;
    557    this.#connections.forEach(connection => connection.sendEvent(name, data));
    558  }
    559 
    560  // XPCOM
    561 
    562  QueryInterface = ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
    563 }
    564 
    565 /**
    566 * Get the list of seen nodes for the given browsing context unique to a
    567 * WebDriver session.
    568 *
    569 * @param {string} sessionId
    570 *     The id of the WebDriver session to use.
    571 * @param {BrowsingContext} browsingContext
    572 *     Browsing context the node is part of.
    573 *
    574 * @returns {Set}
    575 *     The list of seen nodes.
    576 */
    577 export function getSeenNodesForBrowsingContext(sessionId, browsingContext) {
    578  if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
    579    // If browsingContext is not a valid Browsing Context, return an empty set.
    580    return new Set();
    581  }
    582 
    583  const navigable =
    584    lazy.NavigableManager.getNavigableForBrowsingContext(browsingContext);
    585  const session = getWebDriverSessionById(sessionId);
    586 
    587  if (!session.navigableSeenNodes.has(navigable)) {
    588    // The navigable hasn't been seen yet.
    589    session.navigableSeenNodes.set(navigable, new Set());
    590  }
    591 
    592  return session.navigableSeenNodes.get(navigable);
    593 }
    594 
    595 /**
    596 *
    597 * @param {string} sessionId
    598 *     The ID of the WebDriver session to retrieve.
    599 *
    600 * @returns {WebDriverSession|undefined}
    601 *     The WebDriver session or undefined if the id is not known.
    602 */
    603 export function getWebDriverSessionById(sessionId) {
    604  return webDriverSessions.get(sessionId);
    605 }