tor-browser

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

BrowserContext.ts (8352B)


      1 /**
      2 * @license
      3 * Copyright 2017 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import {
      8  firstValueFrom,
      9  from,
     10  merge,
     11  raceWith,
     12 } from '../../third_party/rxjs/rxjs.js';
     13 import type {Cookie, CookieData} from '../common/Cookie.js';
     14 import {EventEmitter, type EventType} from '../common/EventEmitter.js';
     15 import {
     16  debugError,
     17  fromEmitterEvent,
     18  filterAsync,
     19  timeout,
     20 } from '../common/util.js';
     21 import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
     22 import {Mutex} from '../util/Mutex.js';
     23 
     24 import type {Browser, Permission, WaitForTargetOptions} from './Browser.js';
     25 import type {Page} from './Page.js';
     26 import type {Target} from './Target.js';
     27 
     28 /**
     29 * @public
     30 */
     31 export const enum BrowserContextEvent {
     32  /**
     33   * Emitted when the url of a target inside the browser context changes.
     34   * Contains a {@link Target} instance.
     35   */
     36  TargetChanged = 'targetchanged',
     37 
     38  /**
     39   * Emitted when a target is created within the browser context, for example
     40   * when a new page is opened by
     41   * {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open}
     42   * or by {@link BrowserContext.newPage | browserContext.newPage}
     43   *
     44   * Contains a {@link Target} instance.
     45   */
     46  TargetCreated = 'targetcreated',
     47  /**
     48   * Emitted when a target is destroyed within the browser context, for example
     49   * when a page is closed. Contains a {@link Target} instance.
     50   */
     51  TargetDestroyed = 'targetdestroyed',
     52 }
     53 
     54 /**
     55 * @public
     56 */
     57 export interface BrowserContextEvents extends Record<EventType, unknown> {
     58  [BrowserContextEvent.TargetChanged]: Target;
     59  [BrowserContextEvent.TargetCreated]: Target;
     60  [BrowserContextEvent.TargetDestroyed]: Target;
     61 }
     62 
     63 /**
     64 * {@link BrowserContext} represents individual user contexts within a
     65 * {@link Browser | browser}.
     66 *
     67 * When a {@link Browser | browser} is launched, it has at least one default
     68 * {@link BrowserContext | browser context}. Others can be created
     69 * using {@link Browser.createBrowserContext}. Each context has isolated storage
     70 * (cookies/localStorage/etc.)
     71 *
     72 * {@link BrowserContext} {@link EventEmitter | emits} various events which are
     73 * documented in the {@link BrowserContextEvent} enum.
     74 *
     75 * If a {@link Page | page} opens another {@link Page | page}, e.g. using
     76 * `window.open`, the popup will belong to the parent {@link Page.browserContext
     77 * | page's browser context}.
     78 *
     79 * @example Creating a new {@link BrowserContext | browser context}:
     80 *
     81 * ```ts
     82 * // Create a new browser context
     83 * const context = await browser.createBrowserContext();
     84 * // Create a new page inside context.
     85 * const page = await context.newPage();
     86 * // ... do stuff with page ...
     87 * await page.goto('https://example.com');
     88 * // Dispose context once it's no longer needed.
     89 * await context.close();
     90 * ```
     91 *
     92 * @remarks
     93 *
     94 * In Chrome all non-default contexts are incognito,
     95 * and {@link Browser.defaultBrowserContext | default browser context}
     96 * might be incognito if you provide the `--incognito` argument when launching
     97 * the browser.
     98 *
     99 * @public
    100 */
    101 
    102 export abstract class BrowserContext extends EventEmitter<BrowserContextEvents> {
    103  /**
    104   * @internal
    105   */
    106  constructor() {
    107    super();
    108  }
    109 
    110  /**
    111   * Gets all active {@link Target | targets} inside this
    112   * {@link BrowserContext | browser context}.
    113   */
    114  abstract targets(): Target[];
    115 
    116  /**
    117   * If defined, indicates an ongoing screenshot opereation.
    118   */
    119  #pageScreenshotMutex?: Mutex;
    120  #screenshotOperationsCount = 0;
    121 
    122  /**
    123   * @internal
    124   */
    125  startScreenshot(): Promise<InstanceType<typeof Mutex.Guard>> {
    126    const mutex = this.#pageScreenshotMutex || new Mutex();
    127    this.#pageScreenshotMutex = mutex;
    128    this.#screenshotOperationsCount++;
    129    return mutex.acquire(() => {
    130      this.#screenshotOperationsCount--;
    131      if (this.#screenshotOperationsCount === 0) {
    132        // Remove the mutex to indicate no ongoing screenshot operation.
    133        this.#pageScreenshotMutex = undefined;
    134      }
    135    });
    136  }
    137 
    138  /**
    139   * @internal
    140   */
    141  waitForScreenshotOperations():
    142    | Promise<InstanceType<typeof Mutex.Guard>>
    143    | undefined {
    144    return this.#pageScreenshotMutex?.acquire();
    145  }
    146 
    147  /**
    148   * Waits until a {@link Target | target} matching the given `predicate`
    149   * appears and returns it.
    150   *
    151   * This will look all open {@link BrowserContext | browser contexts}.
    152   *
    153   * @example Finding a target for a page opened via `window.open`:
    154   *
    155   * ```ts
    156   * await page.evaluate(() => window.open('https://www.example.com/'));
    157   * const newWindowTarget = await browserContext.waitForTarget(
    158   *   target => target.url() === 'https://www.example.com/',
    159   * );
    160   * ```
    161   */
    162  async waitForTarget(
    163    predicate: (x: Target) => boolean | Promise<boolean>,
    164    options: WaitForTargetOptions = {},
    165  ): Promise<Target> {
    166    const {timeout: ms = 30000} = options;
    167    return await firstValueFrom(
    168      merge(
    169        fromEmitterEvent(this, BrowserContextEvent.TargetCreated),
    170        fromEmitterEvent(this, BrowserContextEvent.TargetChanged),
    171        from(this.targets()),
    172      ).pipe(filterAsync(predicate), raceWith(timeout(ms))),
    173    );
    174  }
    175 
    176  /**
    177   * Gets a list of all open {@link Page | pages} inside this
    178   * {@link BrowserContext | browser context}.
    179   *
    180   * @remarks Non-visible {@link Page | pages}, such as `"background_page"`,
    181   * will not be listed here. You can find them using {@link Target.page}.
    182   */
    183  abstract pages(): Promise<Page[]>;
    184 
    185  /**
    186   * Grants this {@link BrowserContext | browser context} the given
    187   * `permissions` within the given `origin`.
    188   *
    189   * @example Overriding permissions in the
    190   * {@link Browser.defaultBrowserContext | default browser context}:
    191   *
    192   * ```ts
    193   * const context = browser.defaultBrowserContext();
    194   * await context.overridePermissions('https://html5demos.com', [
    195   *   'geolocation',
    196   * ]);
    197   * ```
    198   *
    199   * @param origin - The origin to grant permissions to, e.g.
    200   * "https://example.com".
    201   * @param permissions - An array of permissions to grant. All permissions that
    202   * are not listed here will be automatically denied.
    203   */
    204  abstract overridePermissions(
    205    origin: string,
    206    permissions: Permission[],
    207  ): Promise<void>;
    208 
    209  /**
    210   * Clears all permission overrides for this
    211   * {@link BrowserContext | browser context}.
    212   *
    213   * @example Clearing overridden permissions in the
    214   * {@link Browser.defaultBrowserContext | default browser context}:
    215   *
    216   * ```ts
    217   * const context = browser.defaultBrowserContext();
    218   * context.overridePermissions('https://example.com', ['clipboard-read']);
    219   * // do stuff ..
    220   * context.clearPermissionOverrides();
    221   * ```
    222   */
    223  abstract clearPermissionOverrides(): Promise<void>;
    224 
    225  /**
    226   * Creates a new {@link Page | page} in this
    227   * {@link BrowserContext | browser context}.
    228   */
    229  abstract newPage(): Promise<Page>;
    230 
    231  /**
    232   * Gets the {@link Browser | browser} associated with this
    233   * {@link BrowserContext | browser context}.
    234   */
    235  abstract browser(): Browser;
    236 
    237  /**
    238   * Closes this {@link BrowserContext | browser context} and all associated
    239   * {@link Page | pages}.
    240   *
    241   * @remarks The
    242   * {@link Browser.defaultBrowserContext | default browser context} cannot be
    243   * closed.
    244   */
    245  abstract close(): Promise<void>;
    246 
    247  /**
    248   * Gets all cookies in the browser context.
    249   */
    250  abstract cookies(): Promise<Cookie[]>;
    251 
    252  /**
    253   * Sets a cookie in the browser context.
    254   */
    255  abstract setCookie(...cookies: CookieData[]): Promise<void>;
    256 
    257  /**
    258   * Removes cookie in the browser context
    259   * @param cookies - {@link Cookie | cookie} to remove
    260   */
    261  async deleteCookie(...cookies: Cookie[]): Promise<void> {
    262    return await this.setCookie(
    263      ...cookies.map(cookie => {
    264        return {
    265          ...cookie,
    266          expires: 1,
    267        };
    268      }),
    269    );
    270  }
    271 
    272  /**
    273   * Whether this {@link BrowserContext | browser context} is closed.
    274   */
    275  get closed(): boolean {
    276    return !this.browser().browserContexts().includes(this);
    277  }
    278 
    279  /**
    280   * Identifier for this {@link BrowserContext | browser context}.
    281   */
    282  get id(): string | undefined {
    283    return undefined;
    284  }
    285 
    286  /** @internal */
    287  override [disposeSymbol](): void {
    288    return void this.close().catch(debugError);
    289  }
    290 
    291  /** @internal */
    292  [asyncDisposeSymbol](): Promise<void> {
    293    return this.close();
    294  }
    295 }