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 }