| /** |
| * @license |
| * Copyright 2017 Google Inc. |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| import { |
| firstValueFrom, |
| from, |
| merge, |
| raceWith, |
| } from '../../third_party/rxjs/rxjs.js'; |
| import type { |
| Cookie, |
| CookieData, |
| DeleteCookiesRequest, |
| } from '../common/Cookie.js'; |
| import {EventEmitter, type EventType} from '../common/EventEmitter.js'; |
| import { |
| debugError, |
| fromEmitterEvent, |
| filterAsync, |
| timeout, |
| } from '../common/util.js'; |
| import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js'; |
| import {Mutex} from '../util/Mutex.js'; |
| |
| import type { |
| Browser, |
| CreatePageOptions, |
| Permission, |
| WaitForTargetOptions, |
| } from './Browser.js'; |
| import type {Page} from './Page.js'; |
| import type {Target} from './Target.js'; |
| |
| /** |
| * @public |
| */ |
| export const enum BrowserContextEvent { |
| /** |
| * Emitted when the url of a target inside the browser context changes. |
| * Contains a {@link Target} instance. |
| */ |
| TargetChanged = 'targetchanged', |
| |
| /** |
| * Emitted when a target is created within the browser context, for example |
| * when a new page is opened by |
| * {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open} |
| * or by {@link BrowserContext.newPage | browserContext.newPage} |
| * |
| * Contains a {@link Target} instance. |
| */ |
| TargetCreated = 'targetcreated', |
| /** |
| * Emitted when a target is destroyed within the browser context, for example |
| * when a page is closed. Contains a {@link Target} instance. |
| */ |
| TargetDestroyed = 'targetdestroyed', |
| } |
| |
| /** |
| * @public |
| */ |
| export interface BrowserContextEvents extends Record<EventType, unknown> { |
| [BrowserContextEvent.TargetChanged]: Target; |
| [BrowserContextEvent.TargetCreated]: Target; |
| [BrowserContextEvent.TargetDestroyed]: Target; |
| } |
| |
| /** |
| * {@link BrowserContext} represents individual user contexts within a |
| * {@link Browser | browser}. |
| * |
| * When a {@link Browser | browser} is launched, it has at least one default |
| * {@link BrowserContext | browser context}. Others can be created |
| * using {@link Browser.createBrowserContext}. Each context has isolated storage |
| * (cookies/localStorage/etc.) |
| * |
| * {@link BrowserContext} {@link EventEmitter | emits} various events which are |
| * documented in the {@link BrowserContextEvent} enum. |
| * |
| * If a {@link Page | page} opens another {@link Page | page}, e.g. using |
| * `window.open`, the popup will belong to the parent {@link Page.browserContext |
| * | page's browser context}. |
| * |
| * @example Creating a new {@link BrowserContext | browser context}: |
| * |
| * ```ts |
| * // Create a new browser context |
| * const context = await browser.createBrowserContext(); |
| * // Create a new page inside context. |
| * const page = await context.newPage(); |
| * // ... do stuff with page ... |
| * await page.goto('https://example.com'); |
| * // Dispose context once it's no longer needed. |
| * await context.close(); |
| * ``` |
| * |
| * @remarks |
| * |
| * In Chrome all non-default contexts are incognito, |
| * and {@link Browser.defaultBrowserContext | default browser context} |
| * might be incognito if you provide the `--incognito` argument when launching |
| * the browser. |
| * |
| * @public |
| */ |
| |
| export abstract class BrowserContext extends EventEmitter<BrowserContextEvents> { |
| /** |
| * @internal |
| */ |
| constructor() { |
| super(); |
| } |
| |
| /** |
| * Gets all active {@link Target | targets} inside this |
| * {@link BrowserContext | browser context}. |
| */ |
| abstract targets(): Target[]; |
| |
| /** |
| * If defined, indicates an ongoing screenshot opereation. |
| */ |
| #pageScreenshotMutex?: Mutex; |
| #screenshotOperationsCount = 0; |
| |
| /** |
| * @internal |
| */ |
| startScreenshot(): Promise<InstanceType<typeof Mutex.Guard>> { |
| const mutex = this.#pageScreenshotMutex || new Mutex(); |
| this.#pageScreenshotMutex = mutex; |
| this.#screenshotOperationsCount++; |
| return mutex.acquire(() => { |
| this.#screenshotOperationsCount--; |
| if (this.#screenshotOperationsCount === 0) { |
| // Remove the mutex to indicate no ongoing screenshot operation. |
| this.#pageScreenshotMutex = undefined; |
| } |
| }); |
| } |
| |
| /** |
| * @internal |
| */ |
| waitForScreenshotOperations(): |
| | Promise<InstanceType<typeof Mutex.Guard>> |
| | undefined { |
| return this.#pageScreenshotMutex?.acquire(); |
| } |
| |
| /** |
| * Waits until a {@link Target | target} matching the given `predicate` |
| * appears and returns it. |
| * |
| * This will look all open {@link BrowserContext | browser contexts}. |
| * |
| * @example Finding a target for a page opened via `window.open`: |
| * |
| * ```ts |
| * await page.evaluate(() => window.open('https://www.example.com/')); |
| * const newWindowTarget = await browserContext.waitForTarget( |
| * target => target.url() === 'https://www.example.com/', |
| * ); |
| * ``` |
| */ |
| async waitForTarget( |
| predicate: (x: Target) => boolean | Promise<boolean>, |
| options: WaitForTargetOptions = {}, |
| ): Promise<Target> { |
| const {timeout: ms = 30000} = options; |
| return await firstValueFrom( |
| merge( |
| fromEmitterEvent(this, BrowserContextEvent.TargetCreated), |
| fromEmitterEvent(this, BrowserContextEvent.TargetChanged), |
| from(this.targets()), |
| ).pipe(filterAsync(predicate), raceWith(timeout(ms))), |
| ); |
| } |
| |
| /** |
| * Gets a list of all open {@link Page | pages} inside this |
| * {@link BrowserContext | browser context}. |
| * |
| * @param includeAll - experimental, setting to true includes all kinds of pages. |
| * |
| * @remarks Non-visible {@link Page | pages}, such as `"background_page"`, |
| * will not be listed here. You can find them using {@link Target.page}. |
| */ |
| abstract pages(includeAll?: boolean): Promise<Page[]>; |
| |
| /** |
| * Grants this {@link BrowserContext | browser context} the given |
| * `permissions` within the given `origin`. |
| * |
| * @example Overriding permissions in the |
| * {@link Browser.defaultBrowserContext | default browser context}: |
| * |
| * ```ts |
| * const context = browser.defaultBrowserContext(); |
| * await context.overridePermissions('https://html5demos.com', [ |
| * 'geolocation', |
| * ]); |
| * ``` |
| * |
| * @param origin - The origin to grant permissions to, e.g. |
| * "https://example.com". |
| * @param permissions - An array of permissions to grant. All permissions that |
| * are not listed here will be automatically denied. |
| */ |
| abstract overridePermissions( |
| origin: string, |
| permissions: Permission[], |
| ): Promise<void>; |
| |
| /** |
| * Clears all permission overrides for this |
| * {@link BrowserContext | browser context}. |
| * |
| * @example Clearing overridden permissions in the |
| * {@link Browser.defaultBrowserContext | default browser context}: |
| * |
| * ```ts |
| * const context = browser.defaultBrowserContext(); |
| * context.overridePermissions('https://example.com', ['clipboard-read']); |
| * // do stuff .. |
| * context.clearPermissionOverrides(); |
| * ``` |
| */ |
| abstract clearPermissionOverrides(): Promise<void>; |
| |
| /** |
| * Creates a new {@link Page | page} in this |
| * {@link BrowserContext | browser context}. |
| */ |
| abstract newPage(options?: CreatePageOptions): Promise<Page>; |
| |
| /** |
| * Gets the {@link Browser | browser} associated with this |
| * {@link BrowserContext | browser context}. |
| */ |
| abstract browser(): Browser; |
| |
| /** |
| * Closes this {@link BrowserContext | browser context} and all associated |
| * {@link Page | pages}. |
| * |
| * @remarks The |
| * {@link Browser.defaultBrowserContext | default browser context} cannot be |
| * closed. |
| */ |
| abstract close(): Promise<void>; |
| |
| /** |
| * Gets all cookies in the browser context. |
| */ |
| abstract cookies(): Promise<Cookie[]>; |
| |
| /** |
| * Sets a cookie in the browser context. |
| */ |
| abstract setCookie(...cookies: CookieData[]): Promise<void>; |
| |
| /** |
| * Removes cookie in this browser context. |
| * |
| * @param cookies - Complete {@link Cookie | cookie} object to be removed. |
| */ |
| async deleteCookie(...cookies: Cookie[]): Promise<void> { |
| return await this.setCookie( |
| ...cookies.map(cookie => { |
| return { |
| ...cookie, |
| expires: 1, |
| }; |
| }), |
| ); |
| } |
| |
| /** |
| * Deletes cookies matching the provided filters in this browser context. |
| * |
| * @param filters - {@link DeleteCookiesRequest} |
| */ |
| async deleteMatchingCookies( |
| ...filters: DeleteCookiesRequest[] |
| ): Promise<void> { |
| const cookies = await this.cookies(); |
| const cookiesToDelete = cookies.filter(cookie => { |
| return filters.some(filter => { |
| if (filter.name === cookie.name) { |
| if (filter.domain !== undefined && filter.domain === cookie.domain) { |
| return true; |
| } |
| |
| if (filter.path !== undefined && filter.path === cookie.path) { |
| return true; |
| } |
| if ( |
| filter.partitionKey !== undefined && |
| cookie.partitionKey !== undefined |
| ) { |
| if (typeof cookie.partitionKey !== 'object') { |
| throw new Error('Unexpected string partition key'); |
| } |
| if (typeof filter.partitionKey === 'string') { |
| if (filter.partitionKey === cookie.partitionKey?.sourceOrigin) { |
| return true; |
| } |
| } else { |
| if ( |
| filter.partitionKey.sourceOrigin === |
| cookie.partitionKey?.sourceOrigin |
| ) { |
| return true; |
| } |
| } |
| } |
| if (filter.url !== undefined) { |
| const url = new URL(filter.url); |
| if ( |
| url.hostname === cookie.domain && |
| url.pathname === cookie.path |
| ) { |
| return true; |
| } |
| } |
| return true; |
| } |
| return false; |
| }); |
| }); |
| await this.deleteCookie(...cookiesToDelete); |
| } |
| |
| /** |
| * Whether this {@link BrowserContext | browser context} is closed. |
| */ |
| get closed(): boolean { |
| return !this.browser().browserContexts().includes(this); |
| } |
| |
| /** |
| * Identifier for this {@link BrowserContext | browser context}. |
| */ |
| get id(): string | undefined { |
| return undefined; |
| } |
| |
| /** @internal */ |
| override [disposeSymbol](): void { |
| return void this.close().catch(debugError); |
| } |
| |
| /** @internal */ |
| [asyncDisposeSymbol](): Promise<void> { |
| return this.close(); |
| } |
| } |