tor-browser

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

CDPSession.ts (3336B)


      1 /**
      2 * @license
      3 * Copyright 2024 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
      7 
      8 import type {CommandOptions} from '../api/CDPSession.js';
      9 import {CDPSession} from '../api/CDPSession.js';
     10 import type {Connection as CdpConnection} from '../cdp/Connection.js';
     11 import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js';
     12 import {Deferred} from '../util/Deferred.js';
     13 
     14 import type {BidiConnection} from './Connection.js';
     15 import type {BidiFrame} from './Frame.js';
     16 
     17 /**
     18 * @internal
     19 */
     20 export class BidiCdpSession extends CDPSession {
     21  static sessions = new Map<string, BidiCdpSession>();
     22 
     23  #detached = false;
     24  readonly #connection?: BidiConnection;
     25  readonly #sessionId = Deferred.create<string>();
     26  readonly frame: BidiFrame;
     27 
     28  constructor(frame: BidiFrame, sessionId?: string) {
     29    super();
     30    this.frame = frame;
     31    if (!this.frame.page().browser().cdpSupported) {
     32      return;
     33    }
     34 
     35    const connection = this.frame.page().browser().connection;
     36    this.#connection = connection;
     37 
     38    if (sessionId) {
     39      this.#sessionId.resolve(sessionId);
     40      BidiCdpSession.sessions.set(sessionId, this);
     41    } else {
     42      (async () => {
     43        try {
     44          const {result} = await connection.send('goog:cdp.getSession', {
     45            context: frame._id,
     46          });
     47          this.#sessionId.resolve(result.session!);
     48          BidiCdpSession.sessions.set(result.session!, this);
     49        } catch (error) {
     50          this.#sessionId.reject(error as Error);
     51        }
     52      })();
     53    }
     54 
     55    // SAFETY: We never throw #sessionId.
     56    BidiCdpSession.sessions.set(this.#sessionId.value() as string, this);
     57  }
     58 
     59  override connection(): CdpConnection | undefined {
     60    return undefined;
     61  }
     62 
     63  override get detached(): boolean {
     64    return this.#detached;
     65  }
     66 
     67  override async send<T extends keyof ProtocolMapping.Commands>(
     68    method: T,
     69    params?: ProtocolMapping.Commands[T]['paramsType'][0],
     70    options?: CommandOptions,
     71  ): Promise<ProtocolMapping.Commands[T]['returnType']> {
     72    if (this.#connection === undefined) {
     73      throw new UnsupportedOperation(
     74        'CDP support is required for this feature. The current browser does not support CDP.',
     75      );
     76    }
     77    if (this.#detached) {
     78      throw new TargetCloseError(
     79        `Protocol error (${method}): Session closed. Most likely the page has been closed.`,
     80      );
     81    }
     82    const session = await this.#sessionId.valueOrThrow();
     83    const {result} = await this.#connection.send(
     84      'goog:cdp.sendCommand',
     85      {
     86        method: method,
     87        params: params,
     88        session,
     89      },
     90      options?.timeout,
     91    );
     92    return result.result;
     93  }
     94 
     95  override async detach(): Promise<void> {
     96    if (
     97      this.#connection === undefined ||
     98      this.#connection.closed ||
     99      this.#detached
    100    ) {
    101      return;
    102    }
    103    try {
    104      await this.frame.client.send('Target.detachFromTarget', {
    105        sessionId: this.id(),
    106      });
    107    } finally {
    108      this.onClose();
    109    }
    110  }
    111 
    112  /**
    113   * @internal
    114   */
    115  onClose = (): void => {
    116    BidiCdpSession.sessions.delete(this.id());
    117    this.#detached = true;
    118  };
    119 
    120  override id(): string {
    121    const value = this.#sessionId.value();
    122    return typeof value === 'string' ? value : '';
    123  }
    124 }