tor-browser

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

Browser.ts (14025B)


      1 /**
      2 * @license
      3 * Copyright 2017 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 /// <reference types="node"  preserve="true"/>
      7 import type {ChildProcess} from 'node:child_process';
      8 
      9 import type {Protocol} from 'devtools-protocol';
     10 
     11 import {
     12  firstValueFrom,
     13  from,
     14  merge,
     15  raceWith,
     16 } from '../../third_party/rxjs/rxjs.js';
     17 import type {ProtocolType} from '../common/ConnectOptions.js';
     18 import type {Cookie, CookieData} from '../common/Cookie.js';
     19 import type {DownloadBehavior} from '../common/DownloadBehavior.js';
     20 import {EventEmitter, type EventType} from '../common/EventEmitter.js';
     21 import {
     22  debugError,
     23  fromEmitterEvent,
     24  filterAsync,
     25  timeout,
     26  fromAbortSignal,
     27 } from '../common/util.js';
     28 import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
     29 
     30 import type {BrowserContext} from './BrowserContext.js';
     31 import type {Page} from './Page.js';
     32 import type {Target} from './Target.js';
     33 /**
     34 * @public
     35 */
     36 export interface BrowserContextOptions {
     37  /**
     38   * Proxy server with optional port to use for all requests.
     39   * Username and password can be set in `Page.authenticate`.
     40   */
     41  proxyServer?: string;
     42  /**
     43   * Bypass the proxy for the given list of hosts.
     44   */
     45  proxyBypassList?: string[];
     46  /**
     47   * Behavior definition for when downloading a file.
     48   *
     49   * @remarks
     50   * If not set, the default behavior will be used.
     51   */
     52  downloadBehavior?: DownloadBehavior;
     53 }
     54 
     55 /**
     56 * @internal
     57 */
     58 export type BrowserCloseCallback = () => Promise<void> | void;
     59 
     60 /**
     61 * @public
     62 */
     63 export type TargetFilterCallback = (target: Target) => boolean;
     64 
     65 /**
     66 * @internal
     67 */
     68 export type IsPageTargetCallback = (target: Target) => boolean;
     69 
     70 /**
     71 * @internal
     72 */
     73 export const WEB_PERMISSION_TO_PROTOCOL_PERMISSION = new Map<
     74  Permission,
     75  Protocol.Browser.PermissionType
     76 >([
     77  ['accelerometer', 'sensors'],
     78  ['ambient-light-sensor', 'sensors'],
     79  ['background-sync', 'backgroundSync'],
     80  ['camera', 'videoCapture'],
     81  ['clipboard-read', 'clipboardReadWrite'],
     82  ['clipboard-sanitized-write', 'clipboardSanitizedWrite'],
     83  ['clipboard-write', 'clipboardReadWrite'],
     84  ['geolocation', 'geolocation'],
     85  ['gyroscope', 'sensors'],
     86  ['idle-detection', 'idleDetection'],
     87  ['keyboard-lock', 'keyboardLock'],
     88  ['magnetometer', 'sensors'],
     89  ['microphone', 'audioCapture'],
     90  ['midi', 'midi'],
     91  ['notifications', 'notifications'],
     92  ['payment-handler', 'paymentHandler'],
     93  ['persistent-storage', 'durableStorage'],
     94  ['pointer-lock', 'pointerLock'],
     95  // chrome-specific permissions we have.
     96  ['midi-sysex', 'midiSysex'],
     97 ]);
     98 
     99 /**
    100 * @public
    101 */
    102 export type Permission =
    103  | 'accelerometer'
    104  | 'ambient-light-sensor'
    105  | 'background-sync'
    106  | 'camera'
    107  | 'clipboard-read'
    108  | 'clipboard-sanitized-write'
    109  | 'clipboard-write'
    110  | 'geolocation'
    111  | 'gyroscope'
    112  | 'idle-detection'
    113  | 'keyboard-lock'
    114  | 'magnetometer'
    115  | 'microphone'
    116  | 'midi-sysex'
    117  | 'midi'
    118  | 'notifications'
    119  | 'payment-handler'
    120  | 'persistent-storage'
    121  | 'pointer-lock';
    122 
    123 /**
    124 * @public
    125 */
    126 export interface WaitForTargetOptions {
    127  /**
    128   * Maximum wait time in milliseconds. Pass `0` to disable the timeout.
    129   *
    130   * @defaultValue `30_000`
    131   */
    132  timeout?: number;
    133 
    134  /**
    135   * A signal object that allows you to cancel a waitFor call.
    136   */
    137  signal?: AbortSignal;
    138 }
    139 
    140 /**
    141 * All the events a {@link Browser | browser instance} may emit.
    142 *
    143 * @public
    144 */
    145 export const enum BrowserEvent {
    146  /**
    147   * Emitted when Puppeteer gets disconnected from the browser instance. This
    148   * might happen because either:
    149   *
    150   * - The browser closes/crashes or
    151   * - {@link Browser.disconnect} was called.
    152   */
    153  Disconnected = 'disconnected',
    154  /**
    155   * Emitted when the URL of a target changes. Contains a {@link Target}
    156   * instance.
    157   *
    158   * @remarks Note that this includes target changes in all browser
    159   * contexts.
    160   */
    161  TargetChanged = 'targetchanged',
    162  /**
    163   * Emitted when a target is created, for example when a new page is opened by
    164   * {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open}
    165   * or by {@link Browser.newPage | browser.newPage}
    166   *
    167   * Contains a {@link Target} instance.
    168   *
    169   * @remarks Note that this includes target creations in all browser
    170   * contexts.
    171   */
    172  TargetCreated = 'targetcreated',
    173  /**
    174   * Emitted when a target is destroyed, for example when a page is closed.
    175   * Contains a {@link Target} instance.
    176   *
    177   * @remarks Note that this includes target destructions in all browser
    178   * contexts.
    179   */
    180  TargetDestroyed = 'targetdestroyed',
    181  /**
    182   * @internal
    183   */
    184  TargetDiscovered = 'targetdiscovered',
    185 }
    186 
    187 /**
    188 * @public
    189 */
    190 export interface BrowserEvents extends Record<EventType, unknown> {
    191  [BrowserEvent.Disconnected]: undefined;
    192  [BrowserEvent.TargetCreated]: Target;
    193  [BrowserEvent.TargetDestroyed]: Target;
    194  [BrowserEvent.TargetChanged]: Target;
    195  /**
    196   * @internal
    197   */
    198  [BrowserEvent.TargetDiscovered]: Protocol.Target.TargetInfo;
    199 }
    200 
    201 /**
    202 * @public
    203 * @experimental
    204 */
    205 export interface DebugInfo {
    206  pendingProtocolErrors: Error[];
    207 }
    208 
    209 /**
    210 * {@link Browser} represents a browser instance that is either:
    211 *
    212 * - connected to via {@link Puppeteer.connect} or
    213 * - launched by {@link PuppeteerNode.launch}.
    214 *
    215 * {@link Browser} {@link EventEmitter.emit | emits} various events which are
    216 * documented in the {@link BrowserEvent} enum.
    217 *
    218 * @example Using a {@link Browser} to create a {@link Page}:
    219 *
    220 * ```ts
    221 * import puppeteer from 'puppeteer';
    222 *
    223 * const browser = await puppeteer.launch();
    224 * const page = await browser.newPage();
    225 * await page.goto('https://example.com');
    226 * await browser.close();
    227 * ```
    228 *
    229 * @example Disconnecting from and reconnecting to a {@link Browser}:
    230 *
    231 * ```ts
    232 * import puppeteer from 'puppeteer';
    233 *
    234 * const browser = await puppeteer.launch();
    235 * // Store the endpoint to be able to reconnect to the browser.
    236 * const browserWSEndpoint = browser.wsEndpoint();
    237 * // Disconnect puppeteer from the browser.
    238 * await browser.disconnect();
    239 *
    240 * // Use the endpoint to reestablish a connection
    241 * const browser2 = await puppeteer.connect({browserWSEndpoint});
    242 * // Close the browser.
    243 * await browser2.close();
    244 * ```
    245 *
    246 * @public
    247 */
    248 export abstract class Browser extends EventEmitter<BrowserEvents> {
    249  /**
    250   * @internal
    251   */
    252  constructor() {
    253    super();
    254  }
    255 
    256  /**
    257   * Gets the associated
    258   * {@link https://nodejs.org/api/child_process.html#class-childprocess | ChildProcess}.
    259   *
    260   * @returns `null` if this instance was connected to via
    261   * {@link Puppeteer.connect}.
    262   */
    263  abstract process(): ChildProcess | null;
    264 
    265  /**
    266   * Creates a new {@link BrowserContext | browser context}.
    267   *
    268   * This won't share cookies/cache with other {@link BrowserContext | browser contexts}.
    269   *
    270   * @example
    271   *
    272   * ```ts
    273   * import puppeteer from 'puppeteer';
    274   *
    275   * const browser = await puppeteer.launch();
    276   * // Create a new browser context.
    277   * const context = await browser.createBrowserContext();
    278   * // Create a new page in a pristine context.
    279   * const page = await context.newPage();
    280   * // Do stuff
    281   * await page.goto('https://example.com');
    282   * ```
    283   */
    284  abstract createBrowserContext(
    285    options?: BrowserContextOptions,
    286  ): Promise<BrowserContext>;
    287 
    288  /**
    289   * Gets a list of open {@link BrowserContext | browser contexts}.
    290   *
    291   * In a newly-created {@link Browser | browser}, this will return a single
    292   * instance of {@link BrowserContext}.
    293   */
    294  abstract browserContexts(): BrowserContext[];
    295 
    296  /**
    297   * Gets the default {@link BrowserContext | browser context}.
    298   *
    299   * @remarks The default {@link BrowserContext | browser context} cannot be
    300   * closed.
    301   */
    302  abstract defaultBrowserContext(): BrowserContext;
    303 
    304  /**
    305   * Gets the WebSocket URL to connect to this {@link Browser | browser}.
    306   *
    307   * This is usually used with {@link Puppeteer.connect}.
    308   *
    309   * You can find the debugger URL (`webSocketDebuggerUrl`) from
    310   * `http://HOST:PORT/json/version`.
    311   *
    312   * See {@link https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target | browser endpoint}
    313   * for more information.
    314   *
    315   * @remarks The format is always `ws://HOST:PORT/devtools/browser/<id>`.
    316   */
    317  abstract wsEndpoint(): string;
    318 
    319  /**
    320   * Creates a new {@link Page | page} in the
    321   * {@link Browser.defaultBrowserContext | default browser context}.
    322   */
    323  abstract newPage(): Promise<Page>;
    324 
    325  /**
    326   * Gets all active {@link Target | targets}.
    327   *
    328   * In case of multiple {@link BrowserContext | browser contexts}, this returns
    329   * all {@link Target | targets} in all
    330   * {@link BrowserContext | browser contexts}.
    331   */
    332  abstract targets(): Target[];
    333 
    334  /**
    335   * Gets the {@link Target | target} associated with the
    336   * {@link Browser.defaultBrowserContext | default browser context}).
    337   */
    338  abstract target(): Target;
    339 
    340  /**
    341   * Waits until a {@link Target | target} matching the given `predicate`
    342   * appears and returns it.
    343   *
    344   * This will look all open {@link BrowserContext | browser contexts}.
    345   *
    346   * @example Finding a target for a page opened via `window.open`:
    347   *
    348   * ```ts
    349   * await page.evaluate(() => window.open('https://www.example.com/'));
    350   * const newWindowTarget = await browser.waitForTarget(
    351   *   target => target.url() === 'https://www.example.com/',
    352   * );
    353   * ```
    354   */
    355  async waitForTarget(
    356    predicate: (x: Target) => boolean | Promise<boolean>,
    357    options: WaitForTargetOptions = {},
    358  ): Promise<Target> {
    359    const {timeout: ms = 30000, signal} = options;
    360    return await firstValueFrom(
    361      merge(
    362        fromEmitterEvent(this, BrowserEvent.TargetCreated),
    363        fromEmitterEvent(this, BrowserEvent.TargetChanged),
    364        from(this.targets()),
    365      ).pipe(
    366        filterAsync(predicate),
    367        raceWith(fromAbortSignal(signal), timeout(ms)),
    368      ),
    369    );
    370  }
    371 
    372  /**
    373   * Gets a list of all open {@link Page | pages} inside this {@link Browser}.
    374   *
    375   * If there are multiple {@link BrowserContext | browser contexts}, this
    376   * returns all {@link Page | pages} in all
    377   * {@link BrowserContext | browser contexts}.
    378   *
    379   * @remarks Non-visible {@link Page | pages}, such as `"background_page"`,
    380   * will not be listed here. You can find them using {@link Target.page}.
    381   */
    382  async pages(): Promise<Page[]> {
    383    const contextPages = await Promise.all(
    384      this.browserContexts().map(context => {
    385        return context.pages();
    386      }),
    387    );
    388    // Flatten array.
    389    return contextPages.reduce((acc, x) => {
    390      return acc.concat(x);
    391    }, []);
    392  }
    393 
    394  /**
    395   * Gets a string representing this {@link Browser | browser's} name and
    396   * version.
    397   *
    398   * For headless browser, this is similar to `"HeadlessChrome/61.0.3153.0"`. For
    399   * non-headless or new-headless, this is similar to `"Chrome/61.0.3153.0"`. For
    400   * Firefox, it is similar to `"Firefox/116.0a1"`.
    401   *
    402   * The format of {@link Browser.version} might change with future releases of
    403   * browsers.
    404   */
    405  abstract version(): Promise<string>;
    406 
    407  /**
    408   * Gets this {@link Browser | browser's} original user agent.
    409   *
    410   * {@link Page | Pages} can override the user agent with
    411   * {@link Page.setUserAgent}.
    412   *
    413   */
    414  abstract userAgent(): Promise<string>;
    415 
    416  /**
    417   * Closes this {@link Browser | browser} and all associated
    418   * {@link Page | pages}.
    419   */
    420  abstract close(): Promise<void>;
    421 
    422  /**
    423   * Disconnects Puppeteer from this {@link Browser | browser}, but leaves the
    424   * process running.
    425   */
    426  abstract disconnect(): Promise<void>;
    427 
    428  /**
    429   * Returns all cookies in the default {@link BrowserContext}.
    430   *
    431   * @remarks
    432   *
    433   * Shortcut for
    434   * {@link BrowserContext.cookies | browser.defaultBrowserContext().cookies()}.
    435   */
    436  async cookies(): Promise<Cookie[]> {
    437    return await this.defaultBrowserContext().cookies();
    438  }
    439 
    440  /**
    441   * Sets cookies in the default {@link BrowserContext}.
    442   *
    443   * @remarks
    444   *
    445   * Shortcut for
    446   * {@link BrowserContext.setCookie | browser.defaultBrowserContext().setCookie()}.
    447   */
    448  async setCookie(...cookies: CookieData[]): Promise<void> {
    449    return await this.defaultBrowserContext().setCookie(...cookies);
    450  }
    451 
    452  /**
    453   * Removes cookies from the default {@link BrowserContext}.
    454   *
    455   * @remarks
    456   *
    457   * Shortcut for
    458   * {@link BrowserContext.deleteCookie | browser.defaultBrowserContext().deleteCookie()}.
    459   */
    460  async deleteCookie(...cookies: Cookie[]): Promise<void> {
    461    return await this.defaultBrowserContext().deleteCookie(...cookies);
    462  }
    463 
    464  /**
    465   * Installs an extension and returns the ID. In Chrome, this is only
    466   * available if the browser was created using `pipe: true` and the
    467   * `--enable-unsafe-extension-debugging` flag is set.
    468   */
    469  abstract installExtension(path: string): Promise<string>;
    470 
    471  /**
    472   * Uninstalls an extension. In Chrome, this is only available if the browser
    473   * was created using `pipe: true` and the
    474   * `--enable-unsafe-extension-debugging` flag is set.
    475   */
    476  abstract uninstallExtension(id: string): Promise<void>;
    477 
    478  /**
    479   * Whether Puppeteer is connected to this {@link Browser | browser}.
    480   *
    481   * @deprecated Use {@link Browser | Browser.connected}.
    482   */
    483  isConnected(): boolean {
    484    return this.connected;
    485  }
    486 
    487  /**
    488   * Whether Puppeteer is connected to this {@link Browser | browser}.
    489   */
    490  abstract get connected(): boolean;
    491 
    492  /** @internal */
    493  override [disposeSymbol](): void {
    494    if (this.process()) {
    495      return void this.close().catch(debugError);
    496    }
    497    return void this.disconnect().catch(debugError);
    498  }
    499 
    500  /** @internal */
    501  [asyncDisposeSymbol](): Promise<void> {
    502    if (this.process()) {
    503      return this.close();
    504    }
    505    return this.disconnect();
    506  }
    507 
    508  /**
    509   * @internal
    510   */
    511  abstract get protocol(): ProtocolType;
    512 
    513  /**
    514   * Get debug information from Puppeteer.
    515   *
    516   * @remarks
    517   *
    518   * Currently, includes pending protocol calls. In the future, we might add more info.
    519   *
    520   * @public
    521   * @experimental
    522   */
    523  abstract get debugInfo(): DebugInfo;
    524 }