UNPKG

8.35 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright 2017 Google Inc.
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7import {
8 firstValueFrom,
9 from,
10 merge,
11 raceWith,
12} from '../../third_party/rxjs/rxjs.js';
13import type {Cookie, CookieData} from '../common/Cookie.js';
14import {EventEmitter, type EventType} from '../common/EventEmitter.js';
15import {
16 debugError,
17 fromEmitterEvent,
18 filterAsync,
19 timeout,
20} from '../common/util.js';
21import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
22import {Mutex} from '../util/Mutex.js';
23
24import type {Browser, Permission, WaitForTargetOptions} from './Browser.js';
25import type {Page} from './Page.js';
26import type {Target} from './Target.js';
27
28/**
29 * @public
30 */
31export 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 */
57export 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
102export 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}