UNPKG

13.5 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright 2017 Google Inc.
4 * SPDX-License-Identifier: Apache-2.0
5 */
6/// <reference types="node" preserve="true"/>
7import type {ChildProcess} from 'child_process';
8
9import type {Protocol} from 'devtools-protocol';
10
11import {
12 firstValueFrom,
13 from,
14 merge,
15 raceWith,
16} from '../../third_party/rxjs/rxjs.js';
17import type {ProtocolType} from '../common/ConnectOptions.js';
18import type {Cookie} from '../common/Cookie.js';
19import type {DownloadBehavior} from '../common/DownloadBehavior.js';
20import {EventEmitter, type EventType} from '../common/EventEmitter.js';
21import {
22 debugError,
23 fromEmitterEvent,
24 filterAsync,
25 timeout,
26 fromAbortSignal,
27} from '../common/util.js';
28import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
29
30import type {BrowserContext} from './BrowserContext.js';
31import type {Page} from './Page.js';
32import type {Target} from './Target.js';
33/**
34 * @public
35 */
36export 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 */
58export type BrowserCloseCallback = () => Promise<void> | void;
59
60/**
61 * @public
62 */
63export type TargetFilterCallback = (target: Target) => boolean;
64
65/**
66 * @internal
67 */
68export type IsPageTargetCallback = (target: Target) => boolean;
69
70/**
71 * @internal
72 */
73export const WEB_PERMISSION_TO_PROTOCOL_PERMISSION = new Map<
74 Permission,
75 Protocol.Browser.PermissionType
76>([
77 ['geolocation', 'geolocation'],
78 ['midi', 'midi'],
79 ['notifications', 'notifications'],
80 // TODO: push isn't a valid type?
81 // ['push', 'push'],
82 ['camera', 'videoCapture'],
83 ['microphone', 'audioCapture'],
84 ['background-sync', 'backgroundSync'],
85 ['ambient-light-sensor', 'sensors'],
86 ['accelerometer', 'sensors'],
87 ['gyroscope', 'sensors'],
88 ['magnetometer', 'sensors'],
89 ['accessibility-events', 'accessibilityEvents'],
90 ['clipboard-read', 'clipboardReadWrite'],
91 ['clipboard-write', 'clipboardReadWrite'],
92 ['clipboard-sanitized-write', 'clipboardSanitizedWrite'],
93 ['payment-handler', 'paymentHandler'],
94 ['persistent-storage', 'durableStorage'],
95 ['idle-detection', 'idleDetection'],
96 // chrome-specific permissions we have.
97 ['midi-sysex', 'midiSysex'],
98]);
99
100/**
101 * @public
102 */
103export type Permission =
104 | 'geolocation'
105 | 'midi'
106 | 'notifications'
107 | 'camera'
108 | 'microphone'
109 | 'background-sync'
110 | 'ambient-light-sensor'
111 | 'accelerometer'
112 | 'gyroscope'
113 | 'magnetometer'
114 | 'accessibility-events'
115 | 'clipboard-read'
116 | 'clipboard-write'
117 | 'clipboard-sanitized-write'
118 | 'payment-handler'
119 | 'persistent-storage'
120 | 'idle-detection'
121 | 'midi-sysex';
122
123/**
124 * @public
125 */
126export 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 */
145export 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 */
190export 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 */
205export 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 */
248export 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: Cookie[]): 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 * Whether Puppeteer is connected to this {@link Browser | browser}.
466 *
467 * @deprecated Use {@link Browser | Browser.connected}.
468 */
469 isConnected(): boolean {
470 return this.connected;
471 }
472
473 /**
474 * Whether Puppeteer is connected to this {@link Browser | browser}.
475 */
476 abstract get connected(): boolean;
477
478 /** @internal */
479 override [disposeSymbol](): void {
480 if (this.process()) {
481 return void this.close().catch(debugError);
482 }
483 return void this.disconnect().catch(debugError);
484 }
485
486 /** @internal */
487 [asyncDisposeSymbol](): Promise<void> {
488 if (this.process()) {
489 return this.close();
490 }
491 return this.disconnect();
492 }
493
494 /**
495 * @internal
496 */
497 abstract get protocol(): ProtocolType;
498
499 /**
500 * Get debug information from Puppeteer.
501 *
502 * @remarks
503 *
504 * Currently, includes pending protocol calls. In the future, we might add more info.
505 *
506 * @public
507 * @experimental
508 */
509 abstract get debugInfo(): DebugInfo;
510}