@crawlee/browser-pool 4.0.0-beta.99 → 4.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ Crawlee is available as the [`crawlee`](https://www.npmjs.com/package/crawlee) N
34
34
 
35
35
  We recommend visiting the [Introduction tutorial](https://crawlee.dev/js/docs/introduction) in Crawlee documentation for more information.
36
36
 
37
- > Crawlee requires **Node.js 16 or higher**.
37
+ > Crawlee requires **Node.js 22.13 or higher**.
38
38
 
39
39
  ### With Crawlee CLI
40
40
 
@@ -80,7 +80,14 @@ export interface BrowserControllerEvents<Library extends CommonLibrary, LibraryO
80
80
  * @hideconstructor
81
81
  */
82
82
  export declare abstract class BrowserController<Library extends CommonLibrary = CommonLibrary, LibraryOptions extends Dictionary | undefined = Parameters<Library['launch']>[0], LaunchResult extends CommonBrowser = UnwrapPromise<ReturnType<Library['launch']>>, NewPageOptions = Parameters<LaunchResult['newPage']>[0], NewPageResult = UnwrapPromise<ReturnType<LaunchResult['newPage']>>> extends TypedEmitter<BrowserControllerEvents<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>> implements IBrowserController<NewPageResult> {
83
+ #private;
83
84
  readonly id: string;
85
+ /**
86
+ * Kept `protected` rather than `#private` because the concrete controllers in this package are
87
+ * separate classes — an ES private field would not be visible to them.
88
+ *
89
+ * @internal
90
+ */
84
91
  protected readonly log: CrawleeLogger;
85
92
  /**
86
93
  * The `BrowserPlugin` instance used to launch the browser.
@@ -99,14 +106,17 @@ export declare abstract class BrowserController<Library extends CommonLibrary =
99
106
  * `undefined` if no proxy is used
100
107
  */
101
108
  proxyUrl?: string;
109
+ /** @internal */
102
110
  isActive: boolean;
103
111
  activePages: number;
112
+ /** @internal */
104
113
  totalPages: number;
114
+ /** @internal */
105
115
  lastPageOpenedAt: number;
106
- private _activate;
107
- private isActivePromise;
108
- private commitBrowser;
109
- private hasBrowserPromise;
116
+ /**
117
+ * Returns a promise that resolves once the browser controller has been activated.
118
+ */
119
+ waitForActive(): Promise<void>;
110
120
  constructor(browserPlugin: BrowserPlugin<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>);
111
121
  /**
112
122
  * Activates the BrowserController. If you try to open new pages before
@@ -137,30 +147,32 @@ export declare abstract class BrowserController<Library extends CommonLibrary =
137
147
  * @ignore
138
148
  */
139
149
  newPage(pageOptions?: NewPageOptions): Promise<NewPageResult>;
140
- setCookies(page: NewPageResult, cookies: Cookie[]): Promise<void>;
141
- getCookies(page: NewPageResult): Promise<Cookie[]>;
142
150
  /**
143
- * @private
151
+ * Accounts for a page no longer being open. Callable from either side and safe to call twice:
152
+ * the page's own `close` event, or the pool when it gives up on a close that never settles.
153
+ * Tracked in a `WeakSet` rather than via `isClosed()`, because that reports the same event.
154
+ * @internal
144
155
  */
145
- protected abstract _close(): Promise<void>;
156
+ registerPageClosed(page: NewPageResult): void;
146
157
  /**
147
- * @private
158
+ * Registers cleanup belonging to a single page: an anonymizing proxy server, an incognito
159
+ * context. It runs from `registerPageClosed`, so it happens whether the page closed on its own
160
+ * or the pool gave up on a close that never settled, and it runs at most once.
161
+ *
162
+ * Hanging it off the page's `close` event instead leaks it in exactly the case this controller
163
+ * now handles, because that event does not arrive for a page the browser never destroyed.
164
+ * @ignore
148
165
  */
166
+ protected registerPageTeardown(page: NewPageResult, teardown: () => Promise<void>): void;
167
+ setCookies(page: NewPageResult, cookies: Cookie[]): Promise<void>;
168
+ getCookies(page: NewPageResult): Promise<Cookie[]>;
169
+ protected abstract _close(): Promise<void>;
149
170
  protected abstract _kill(): Promise<void>;
150
- /**
151
- * @private
152
- */
153
171
  protected abstract _newPage(pageOptions?: NewPageOptions): Promise<NewPageResult>;
154
- /**
155
- * @private
156
- */
157
172
  protected abstract _setCookies(page: NewPageResult, cookies: Cookie[]): Promise<void>;
158
- /**
159
- * @private
160
- */
161
173
  protected abstract _getCookies(page: NewPageResult): Promise<Cookie[]>;
162
174
  /**
163
- * @private
175
+ * @internal
164
176
  */
165
177
  abstract normalizeProxyOptions(proxyUrl: string | undefined, pageOptions: any): Record<string, unknown>;
166
178
  }
@@ -2,6 +2,7 @@ import { serviceLocator } from '@crawlee/core';
2
2
  import { nanoid } from 'nanoid';
3
3
  import { TypedEmitter } from 'tiny-typed-emitter';
4
4
  import { tryCancel } from '@apify/timeout';
5
+ import { BROWSER_CONTROLLER_EVENTS } from '../events.js';
5
6
  const PROCESS_KILL_TIMEOUT_MILLIS = 5000;
6
7
  /**
7
8
  * The `BrowserController` serves two purposes. First, it is the base class that
@@ -13,6 +14,12 @@ const PROCESS_KILL_TIMEOUT_MILLIS = 5000;
13
14
  */
14
15
  export class BrowserController extends TypedEmitter {
15
16
  id = nanoid();
17
+ /**
18
+ * Kept `protected` rather than `#private` because the concrete controllers in this package are
19
+ * separate classes — an ES private field would not be visible to them.
20
+ *
21
+ * @internal
22
+ */
16
23
  log;
17
24
  /**
18
25
  * The `BrowserPlugin` instance used to launch the browser.
@@ -31,17 +38,28 @@ export class BrowserController extends TypedEmitter {
31
38
  * `undefined` if no proxy is used
32
39
  */
33
40
  proxyUrl;
41
+ /** @internal */
34
42
  isActive = false;
35
43
  activePages = 0;
44
+ #closedPages = new WeakSet();
45
+ #pageTeardowns = new WeakMap();
46
+ /** @internal */
36
47
  totalPages = 0;
48
+ /** @internal */
37
49
  lastPageOpenedAt = Date.now();
38
- _activate;
39
- isActivePromise = new Promise((resolve) => {
40
- this._activate = resolve;
50
+ #activate;
51
+ #isActivePromise = new Promise((resolve) => {
52
+ this.#activate = resolve;
41
53
  });
42
- commitBrowser;
43
- hasBrowserPromise = new Promise((resolve) => {
44
- this.commitBrowser = resolve;
54
+ /**
55
+ * Returns a promise that resolves once the browser controller has been activated.
56
+ */
57
+ async waitForActive() {
58
+ await this.#isActivePromise;
59
+ }
60
+ #commitBrowser;
61
+ #hasBrowserPromise = new Promise((resolve) => {
62
+ this.#commitBrowser = resolve;
45
63
  });
46
64
  constructor(browserPlugin) {
47
65
  super();
@@ -58,7 +76,7 @@ export class BrowserController extends TypedEmitter {
58
76
  if (!this.browser) {
59
77
  throw new Error('Cannot activate BrowserController without an assigned browser.');
60
78
  }
61
- this._activate();
79
+ this.#activate();
62
80
  this.isActive = true;
63
81
  }
64
82
  /**
@@ -70,7 +88,7 @@ export class BrowserController extends TypedEmitter {
70
88
  }
71
89
  this.browser = browser;
72
90
  this.launchContext = launchContext;
73
- this.commitBrowser();
91
+ this.#commitBrowser();
74
92
  }
75
93
  /**
76
94
  * Gracefully closes the browser and makes sure
@@ -79,7 +97,7 @@ export class BrowserController extends TypedEmitter {
79
97
  * Emits 'browserClosed' event.
80
98
  */
81
99
  async close() {
82
- await this.hasBrowserPromise;
100
+ await this.#hasBrowserPromise;
83
101
  try {
84
102
  await this._close();
85
103
  // TODO: shouldn't this go in a finally instead?
@@ -88,7 +106,7 @@ export class BrowserController extends TypedEmitter {
88
106
  catch (error) {
89
107
  this.log.debug(`Could not close browser.\nCause: ${error.message}`, { id: this.id });
90
108
  }
91
- this.emit("browserClosed" /* BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED */, this);
109
+ this.emit(BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED, this);
92
110
  const killTimer = setTimeout(() => {
93
111
  this._kill().catch((err) => {
94
112
  this.log.debug(`Could not kill browser.\nCause: ${err.message}`, { id: this.id });
@@ -102,9 +120,9 @@ export class BrowserController extends TypedEmitter {
102
120
  * Emits 'browserClosed' event.
103
121
  */
104
122
  async kill() {
105
- await this.hasBrowserPromise;
123
+ await this.#hasBrowserPromise;
106
124
  await this._kill();
107
- this.emit("browserClosed" /* BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED */, this);
125
+ this.emit(BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED, this);
108
126
  }
109
127
  /**
110
128
  * Opens new browser page.
@@ -113,12 +131,43 @@ export class BrowserController extends TypedEmitter {
113
131
  async newPage(pageOptions) {
114
132
  this.activePages++;
115
133
  this.totalPages++;
116
- await this.isActivePromise;
134
+ await this.#isActivePromise;
117
135
  const page = await this._newPage(pageOptions);
118
136
  tryCancel();
119
137
  this.lastPageOpenedAt = Date.now();
120
138
  return page;
121
139
  }
140
+ /**
141
+ * Accounts for a page no longer being open. Callable from either side and safe to call twice:
142
+ * the page's own `close` event, or the pool when it gives up on a close that never settles.
143
+ * Tracked in a `WeakSet` rather than via `isClosed()`, because that reports the same event.
144
+ * @internal
145
+ */
146
+ registerPageClosed(page) {
147
+ if (this.#closedPages.has(page))
148
+ return;
149
+ this.#closedPages.add(page);
150
+ this.activePages--;
151
+ const teardown = this.#pageTeardowns.get(page);
152
+ if (!teardown)
153
+ return;
154
+ this.#pageTeardowns.delete(page);
155
+ teardown().catch((error) => {
156
+ this.log.debug(`Could not clean up after a closed page.\nCause:${error.message}`, { id: this.id });
157
+ });
158
+ }
159
+ /**
160
+ * Registers cleanup belonging to a single page: an anonymizing proxy server, an incognito
161
+ * context. It runs from `registerPageClosed`, so it happens whether the page closed on its own
162
+ * or the pool gave up on a close that never settled, and it runs at most once.
163
+ *
164
+ * Hanging it off the page's `close` event instead leaks it in exactly the case this controller
165
+ * now handles, because that event does not arrive for a page the browser never destroyed.
166
+ * @ignore
167
+ */
168
+ registerPageTeardown(page, teardown) {
169
+ this.#pageTeardowns.set(page, teardown);
170
+ }
122
171
  async setCookies(page, cookies) {
123
172
  return this._setCookies(page, cookies);
124
173
  }
@@ -12,8 +12,6 @@ import type { BrowserController } from './browser-controller.js';
12
12
  * - without using a fingerprint,
13
13
  * - without specifying a user agent.
14
14
  * Last updated on 2022-05-05.
15
- *
16
- * After you update it here, please update it also in jsdom-crawler.ts
17
15
  */
18
16
  export declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36";
19
17
  /**
@@ -83,7 +81,14 @@ export interface CreateLaunchContextOptions<Library extends CommonLibrary, Libra
83
81
  * feed them to {@link BrowserPool} for use.
84
82
  */
85
83
  export declare abstract class BrowserPlugin<Library extends CommonLibrary = CommonLibrary, LibraryOptions extends Dictionary | undefined = Parameters<Library['launch']>[0], LaunchResult extends CommonBrowser = UnwrapPromise<ReturnType<Library['launch']>>, NewPageOptions = Parameters<LaunchResult['newPage']>[0], NewPageResult = UnwrapPromise<ReturnType<LaunchResult['newPage']>>> {
84
+ #private;
86
85
  readonly name: string;
86
+ /**
87
+ * Kept `protected` rather than `#private` because the concrete plugins in this package are separate
88
+ * classes — an ES private field would not be visible to them.
89
+ *
90
+ * @internal
91
+ */
87
92
  protected readonly log: CrawleeLogger;
88
93
  readonly library: Library;
89
94
  readonly launchOptions: LibraryOptions;
@@ -119,7 +124,7 @@ export declare abstract class BrowserPlugin<Library extends CommonLibrary = Comm
119
124
  * Subclasses implement only the `connect` callback — the resolve / token / release / error-wrap scaffolding
120
125
  * lives here so it stays identical across plugins.
121
126
  */
122
- protected _connectToRemoteBrowser(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>, connect: (url: string) => Promise<LaunchResult>): Promise<LaunchResult>;
127
+ protected connectToRemoteBrowser(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>, connect: (url: string) => Promise<LaunchResult>): Promise<LaunchResult>;
123
128
  /**
124
129
  * Creates a `LaunchContext` with all the information needed
125
130
  * to launch a browser. Aside from library specific launch options,
@@ -132,16 +137,9 @@ export declare abstract class BrowserPlugin<Library extends CommonLibrary = Comm
132
137
  * Launches the browser using provided launch context.
133
138
  */
134
139
  launch(launchContext?: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): Promise<LaunchResult>;
135
- private _mergeArgsToHideWebdriver;
136
- protected _throwAugmentedLaunchError(cause: unknown, executablePath: string | undefined, dockerImage: string, moduleInstallCommand: string): never;
137
- /**
138
- * @private
139
- */
140
- protected abstract _addProxyToLaunchOptions(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): Promise<void>;
141
- protected abstract _isChromiumBasedBrowser(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): boolean;
142
- /**
143
- * @private
144
- */
140
+ protected throwAugmentedLaunchError(cause: unknown, executablePath: string | undefined, dockerImage: string, moduleInstallCommand: string): never;
141
+ protected abstract addProxyToLaunchOptions(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): Promise<void>;
142
+ protected abstract isChromiumBasedBrowser(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): boolean;
145
143
  protected abstract _launch(launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>): Promise<LaunchResult>;
146
144
  }
147
145
  export declare class BrowserLaunchError extends CriticalError {
@@ -9,8 +9,6 @@ import { sanitizeEndpointForLog } from '../utils.js';
9
9
  * - without using a fingerprint,
10
10
  * - without specifying a user agent.
11
11
  * Last updated on 2022-05-05.
12
- *
13
- * After you update it here, please update it also in jsdom-crawler.ts
14
12
  */
15
13
  export const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36';
16
14
  /**
@@ -21,6 +19,12 @@ export const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_
21
19
  */
22
20
  export class BrowserPlugin {
23
21
  name = this.constructor.name;
22
+ /**
23
+ * Kept `protected` rather than `#private` because the concrete plugins in this package are separate
24
+ * classes — an ES private field would not be visible to them.
25
+ *
26
+ * @internal
27
+ */
24
28
  log;
25
29
  library;
26
30
  launchOptions;
@@ -69,7 +73,7 @@ export class BrowserPlugin {
69
73
  * Subclasses implement only the `connect` callback — the resolve / token / release / error-wrap scaffolding
70
74
  * lives here so it stays identical across plugins.
71
75
  */
72
- async _connectToRemoteBrowser(launchContext, connect) {
76
+ async connectToRemoteBrowser(launchContext, connect) {
73
77
  const connection = this.remoteConnection;
74
78
  let url;
75
79
  let token;
@@ -79,7 +83,7 @@ export class BrowserPlugin {
79
83
  catch (cause) {
80
84
  throw new BrowserLaunchError('Failed to resolve the remote browser endpoint.', { cause });
81
85
  }
82
- launchContext._remoteToken = token;
86
+ launchContext.remoteToken = token;
83
87
  try {
84
88
  return await connect(url);
85
89
  }
@@ -117,11 +121,11 @@ export class BrowserPlugin {
117
121
  launchContext.launchOptions ??= {};
118
122
  const { proxyUrl, launchOptions } = launchContext;
119
123
  if (proxyUrl && !launchContext.isRemote) {
120
- await this._addProxyToLaunchOptions(launchContext);
124
+ await this.addProxyToLaunchOptions(launchContext);
121
125
  }
122
- if (!launchContext.isRemote && this._isChromiumBasedBrowser(launchContext)) {
126
+ if (!launchContext.isRemote && this.isChromiumBasedBrowser(launchContext)) {
123
127
  // This will set the args for chromium based browsers to hide the webdriver.
124
- launchOptions.args = this._mergeArgsToHideWebdriver(launchOptions.args);
128
+ launchOptions.args = this.#mergeArgsToHideWebdriver(launchOptions.args);
125
129
  // When User-Agent is not set, and we're using Chromium in headless mode,
126
130
  // it is better to use DEFAULT_USER_AGENT to reduce chance of detection,
127
131
  // as otherwise 'HeadlessChrome' is present in User-Agent string.
@@ -135,7 +139,7 @@ export class BrowserPlugin {
135
139
  }
136
140
  return this._launch(launchContext);
137
141
  }
138
- _mergeArgsToHideWebdriver(originalArgs) {
142
+ #mergeArgsToHideWebdriver(originalArgs) {
139
143
  if (!originalArgs?.length) {
140
144
  return ['--disable-blink-features=AutomationControlled'];
141
145
  }
@@ -148,7 +152,7 @@ export class BrowserPlugin {
148
152
  }
149
153
  return originalArgs;
150
154
  }
151
- _throwAugmentedLaunchError(cause, executablePath, dockerImage, moduleInstallCommand) {
155
+ throwAugmentedLaunchError(cause, executablePath, dockerImage, moduleInstallCommand) {
152
156
  const errorMessage = ['Failed to launch browser. Please check the following:'];
153
157
  if (executablePath) {
154
158
  errorMessage.push(`- Check whether the provided executable path "${executablePath}" is correct.`);
@@ -1,4 +1,5 @@
1
1
  type PromiseVoid = () => Promise<void>;
2
+ /** @internal */
2
3
  export interface AnonymizeProxySugarOptions {
3
4
  ignoreProxyCertificate?: boolean;
4
5
  }
package/browser-pool.d.ts CHANGED
@@ -248,34 +248,21 @@ export interface BrowserPoolHooks<BC extends BrowserController, LC extends Launc
248
248
  * ```
249
249
  */
250
250
  export declare class BrowserPool<Options extends BrowserPoolOptions = BrowserPoolOptions, BrowserPlugins extends BrowserPlugin[] = InferBrowserPluginArray<Options['browserPlugins']>, BrowserControllerReturn extends BrowserController = ReturnType<BrowserPlugins[number]['createController']>, LaunchContextReturn extends LaunchContext = ReturnType<BrowserPlugins[number]['createLaunchContext']>, PageOptions = Parameters<BrowserControllerReturn['newPage']>[0], PageReturn extends UnwrapPromise<ReturnType<BrowserControllerReturn['newPage']>> = UnwrapPromise<ReturnType<BrowserControllerReturn['newPage']>>> extends TypedEmitter<BrowserPoolEvents<BrowserControllerReturn, PageReturn>> implements IBrowserPool<PageReturn> {
251
+ #private;
251
252
  browserPlugins: BrowserPlugins;
252
- maxOpenPagesPerBrowser: number;
253
+ /** @internal */
253
254
  maxOpenBrowsers: number;
254
- retireBrowserAfterPageCount: number;
255
- operationTimeoutMillis: number;
256
- closeInactiveBrowserAfterMillis: number;
257
- useFingerprints?: boolean;
255
+ /** @internal */
258
256
  fingerprintOptions: FingerprintOptions;
259
- preLaunchHooks: PreLaunchHook<LaunchContextReturn>[];
260
- postLaunchHooks: PostLaunchHook<BrowserControllerReturn>[];
261
- prePageCreateHooks: PrePageCreateHook<BrowserControllerReturn, PageOptions>[];
262
- postPageCreateHooks: PostPageCreateHook<BrowserControllerReturn, PageReturn>[];
263
- prePageCloseHooks: PrePageCloseHook<BrowserControllerReturn, PageReturn>[];
264
- postPageCloseHooks: PostPageCloseHook<BrowserControllerReturn>[];
265
- pageCounter: number;
266
- pages: Map<string, PageReturn>;
267
- pageIds: WeakMap<PageReturn, string>;
268
- startingBrowserControllers: Set<BrowserControllerReturn>;
269
- activeBrowserControllers: Set<BrowserControllerReturn>;
270
- retiredBrowserControllers: Set<BrowserControllerReturn>;
271
- pageToBrowserController: WeakMap<PageReturn, BrowserControllerReturn>;
257
+ /** @internal */
272
258
  fingerprintInjector?: FingerprintInjector;
273
259
  fingerprintGenerator?: FingerprintGenerator;
260
+ /** @internal */
274
261
  fingerprintCache?: QuickLRU<string, BrowserFingerprintWithHeaders>;
262
+ private pages;
263
+ private activeBrowserControllers;
264
+ private retiredBrowserControllers;
275
265
  private browserKillerInterval?;
276
- private browserRetireInterval?;
277
- private limiter;
278
- private log;
279
266
  constructor(options: Options & BrowserPoolHooks<BrowserControllerReturn, LaunchContextReturn, PageReturn>);
280
267
  /**
281
268
  * Opens a new page in one of the running browsers or launches
@@ -350,7 +337,6 @@ export declare class BrowserPool<Options extends BrowserPoolOptions = BrowserPoo
350
337
  * until it's closed.
351
338
  */
352
339
  getPageId(page: PageReturn): string | undefined;
353
- private _createPageForBrowser;
354
340
  /**
355
341
  * Removes a browser controller from the pool. The underlying
356
342
  * browser will be closed after all its pages are closed.
@@ -364,8 +350,9 @@ export declare class BrowserPool<Options extends BrowserPoolOptions = BrowserPoo
364
350
  retireBrowserByPage(page: PageReturn): void;
365
351
  /**
366
352
  * Releases a page back to the pool. The page is closed and, if the
367
- * optional `error` is a {@link SessionError}, the browser controller
368
- * that served the page is retired so that its tainted state (cookies,
353
+ * optional `error` is a {@link SessionError} (including
354
+ * {@link SessionRetiredError}), the browser controller that served
355
+ * the page is retired so that the finished session's state (cookies,
369
356
  * storage, etc.) cannot leak into future sessions.
370
357
  *
371
358
  * This is the primary way the crawler should return pages to the pool.
@@ -409,34 +396,36 @@ export declare class BrowserPool<Options extends BrowserPoolOptions = BrowserPoo
409
396
  * @return {Promise<void>}
410
397
  */
411
398
  closeAllBrowsers(): Promise<void>;
399
+ [Symbol.asyncDispose](): Promise<void>;
412
400
  /**
413
- * Closes all managed browsers and tears down the pool.
401
+ * Closes every managed browser and empties the pool, which stays usable afterwards — a crawler releases its
402
+ * browsers when a run ends and may start another run on the same pool.
414
403
  */
415
- destroy(): Promise<void>;
416
- private _teardown;
417
- private _getAllBrowserControllers;
418
- private _launchBrowser;
404
+ releaseAllBrowsers(): Promise<void>;
419
405
  /**
420
- * Picks plugins round robin.
421
- * @private
406
+ * Closes all managed browsers and tears the pool down for good: its intervals are cleared without being
407
+ * re-armed and its listeners are dropped, so it cannot be used again.
422
408
  */
423
- private _pickBrowserPlugin;
424
- private _pickBrowserWithFreeCapacity;
425
- private _closeInactiveRetiredBrowsers;
426
- private _overridePageClose;
427
- private _executeHooks;
428
- private _closeRetiredBrowserWithNoPages;
409
+ destroy(): Promise<void>;
410
+ private closeInactiveRetiredBrowsers;
429
411
  /**
430
- * Returns `true` if the pool can accept a new browser launch without exceeding
431
- * {@link BrowserPoolOptions.maxOpenBrowsers}. Counts starting, active, and retired browsers.
412
+ * Returns `true` if the pool can accept a new browser launch without exceeding `maxOpenBrowsers`.
413
+ * Counts starting, active, and retired browsers.
414
+ *
415
+ * A plain `BrowserPool` leaves `maxOpenBrowsers` at `Infinity`, so this only returns `false` when something
416
+ * has set a cap — {@link RemoteBrowserPool} does, from its own
417
+ * {@link RemoteBrowserPoolOptions.maxOpenBrowsers|`maxOpenBrowsers`} option. There is no
418
+ * `BrowserPoolOptions` key for it.
419
+ *
420
+ * @internal
432
421
  */
433
422
  hasFreeBrowserSlot(): boolean;
434
423
  /**
435
424
  * Returns `true` if any active browser has room for another page.
425
+ *
426
+ * @internal
436
427
  */
437
428
  hasActiveBrowserWithFreeCapacity(): boolean;
438
- private _initializeFingerprinting;
439
- private _addFingerprintHooks;
440
429
  }
441
430
  export interface BrowserPoolNewPageOptions<PageOptions, BP extends BrowserPlugin> extends NewPageOptions {
442
431
  /**