From 6c960115a3230e247de628b85dea7c1c02582d6d Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Mon, 13 Jun 2022 11:16:25 +0200 Subject: [PATCH] chore: use private fields (#8506) --- .eslintrc.js | 37 ++- src/common/Accessibility.ts | 86 ++--- src/common/AriaQueryHandler.ts | 4 +- src/common/Browser.ts | 198 ++++++----- src/common/BrowserConnector.ts | 13 +- src/common/BrowserWebSocketTransport.ts | 14 +- src/common/Connection.ts | 146 ++++---- src/common/ConsoleMessage.ts | 26 +- src/common/Coverage.ts | 189 +++++------ src/common/DOMWorld.ts | 270 ++++++++------- src/common/DeviceDescriptors.ts | 6 +- src/common/Dialog.ts | 36 +- src/common/EmulationManager.ts | 18 +- src/common/ExecutionContext.ts | 25 +- src/common/FileChooser.ts | 22 +- src/common/FrameManager.ts | 254 +++++++------- src/common/HTTPRequest.ts | 190 ++++++----- src/common/HTTPResponse.ts | 99 +++--- src/common/Input.ts | 141 ++++---- src/common/JSHandle.ts | 182 +++++----- src/common/LifecycleWatcher.ts | 169 +++++----- src/common/NetworkEventManager.ts | 63 ++-- src/common/NetworkManager.ts | 286 ++++++++-------- src/common/PDFOptions.ts | 30 +- src/common/Page.ts | 420 ++++++++++++------------ src/common/Puppeteer.ts | 28 +- src/common/QueryHandler.ts | 36 +- src/common/SecurityDetails.ts | 36 +- src/common/Target.ts | 82 ++--- src/common/TaskQueue.ts | 8 +- src/common/TimeoutSettings.ts | 20 +- src/common/Tracing.ts | 27 +- src/common/USKeyboardLayout.ts | 2 +- src/common/WebWorker.ts | 34 +- src/node/BrowserFetcher.ts | 171 +++++----- src/node/BrowserRunner.ts | 73 ++-- src/node/Launcher.ts | 22 +- src/node/NodeWebSocketTransport.ts | 14 +- src/node/PipeTransport.ts | 34 +- src/node/Puppeteer.ts | 38 +-- test/fixtures.spec.ts | 6 +- test/frame.spec.ts | 2 +- test/headful.spec.ts | 2 +- test/jshandle.spec.ts | 11 +- test/oopif.spec.ts | 15 +- test/page.spec.ts | 2 +- 46 files changed, 1830 insertions(+), 1757 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4cbba0ce..a5ca5c2a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,12 @@ +// TODO: Enable this at some point. +// const RESTRICTED_UNDERSCORED_IDENTIFIERS = [ +// 'PropertyDefinition > Identifier[name=/^_[a-z].*$/]', +// ].map((selector) => ({ +// selector, +// message: +// 'Use private fields (fields prefixed with #) and an appropriate getter/setter.', +// })); + module.exports = { root: true, env: { @@ -15,14 +24,6 @@ module.exports = { // Error if files are not formatted with Prettier correctly. 'prettier/prettier': 2, // syntax preferences - quotes: [ - 2, - 'single', - { - avoidEscape: true, - allowTemplateLiterals: true, - }, - ], 'spaced-comment': [ 2, 'always', @@ -116,6 +117,12 @@ module.exports = { }, ], 'import/extensions': ['error', 'ignorePackages'], + + 'no-restricted-syntax': [ + 'error', + // Don't allow underscored declarations on camelCased variables/properties. + // ...RESTRICTED_UNDERSCORED_IDENTIFIERS, + ], }, overrides: [ { @@ -144,8 +151,6 @@ module.exports = { // We don't require explicit return types on basic functions or // dummy functions in tests, for example '@typescript-eslint/explicit-function-return-type': 0, - // We know it's bad and use it very sparingly but it's needed :( - '@typescript-eslint/ban-ts-ignore': 0, // We allow non-null assertions if the value was asserted using `assert` API. '@typescript-eslint/no-non-null-assertion': 0, /** @@ -176,6 +181,18 @@ module.exports = { ], // By default this is a warning but we want it to error. '@typescript-eslint/explicit-module-boundary-types': 2, + + 'no-restricted-syntax': [ + 'error', + { + // Never use `require` in TypeScript since they are transpiled out. + selector: "CallExpression[callee.name='require']", + message: '`require` statements are not allowed. Use `import`.', + }, + + // Don't allow underscored declarations on camelCased variables/properties. + // ...RESTRICTED_UNDERSCORED_IDENTIFIERS, + ], }, }, ], diff --git a/src/common/Accessibility.ts b/src/common/Accessibility.ts index 062c3c9e..37b2aae3 100644 --- a/src/common/Accessibility.ts +++ b/src/common/Accessibility.ts @@ -130,13 +130,13 @@ export interface SnapshotOptions { * @public */ export class Accessibility { - private _client: CDPSession; + #client: CDPSession; /** * @internal */ constructor(client: CDPSession) { - this._client = client; + this.#client = client; } /** @@ -182,10 +182,10 @@ export class Accessibility { options: SnapshotOptions = {} ): Promise { const { interestingOnly = true, root = null } = options; - const { nodes } = await this._client.send('Accessibility.getFullAXTree'); + const { nodes } = await this.#client.send('Accessibility.getFullAXTree'); let backendNodeId: number | undefined; if (root) { - const { node } = await this._client.send('DOM.describeNode', { + const { node } = await this.#client.send('DOM.describeNode', { objectId: root._remoteObject.objectId, }); backendNodeId = node.backendNodeId; @@ -238,53 +238,53 @@ class AXNode { public payload: Protocol.Accessibility.AXNode; public children: AXNode[] = []; - private _richlyEditable = false; - private _editable = false; - private _focusable = false; - private _hidden = false; - private _name: string; - private _role: string; - private _ignored: boolean; - private _cachedHasFocusableChild?: boolean; + #richlyEditable = false; + #editable = false; + #focusable = false; + #hidden = false; + #name: string; + #role: string; + #ignored: boolean; + #cachedHasFocusableChild?: boolean; constructor(payload: Protocol.Accessibility.AXNode) { this.payload = payload; - this._name = this.payload.name ? this.payload.name.value : ''; - this._role = this.payload.role ? this.payload.role.value : 'Unknown'; - this._ignored = this.payload.ignored; + this.#name = this.payload.name ? this.payload.name.value : ''; + this.#role = this.payload.role ? this.payload.role.value : 'Unknown'; + this.#ignored = this.payload.ignored; for (const property of this.payload.properties || []) { if (property.name === 'editable') { - this._richlyEditable = property.value.value === 'richtext'; - this._editable = true; + this.#richlyEditable = property.value.value === 'richtext'; + this.#editable = true; } - if (property.name === 'focusable') this._focusable = property.value.value; - if (property.name === 'hidden') this._hidden = property.value.value; + if (property.name === 'focusable') this.#focusable = property.value.value; + if (property.name === 'hidden') this.#hidden = property.value.value; } } - private _isPlainTextField(): boolean { - if (this._richlyEditable) return false; - if (this._editable) return true; - return this._role === 'textbox' || this._role === 'searchbox'; + #isPlainTextField(): boolean { + if (this.#richlyEditable) return false; + if (this.#editable) return true; + return this.#role === 'textbox' || this.#role === 'searchbox'; } - private _isTextOnlyObject(): boolean { - const role = this._role; + #isTextOnlyObject(): boolean { + const role = this.#role; return role === 'LineBreak' || role === 'text' || role === 'InlineTextBox'; } - private _hasFocusableChild(): boolean { - if (this._cachedHasFocusableChild === undefined) { - this._cachedHasFocusableChild = false; + #hasFocusableChild(): boolean { + if (this.#cachedHasFocusableChild === undefined) { + this.#cachedHasFocusableChild = false; for (const child of this.children) { - if (child._focusable || child._hasFocusableChild()) { - this._cachedHasFocusableChild = true; + if (child.#focusable || child.#hasFocusableChild()) { + this.#cachedHasFocusableChild = true; break; } } } - return this._cachedHasFocusableChild; + return this.#cachedHasFocusableChild; } public find(predicate: (x: AXNode) => boolean): AXNode | null { @@ -303,13 +303,13 @@ class AXNode { // implementation details, but we want to expose them as leaves to platform // accessibility APIs because screen readers might be confused if they find // any children. - if (this._isPlainTextField() || this._isTextOnlyObject()) return true; + if (this.#isPlainTextField() || this.#isTextOnlyObject()) return true; // Roles whose children are only presentational according to the ARIA and // HTML5 Specs should be hidden from screen readers. // (Note that whilst ARIA buttons can have only presentational children, HTML5 // buttons are allowed to have content.) - switch (this._role) { + switch (this.#role) { case 'doc-cover': case 'graphics-symbol': case 'img': @@ -324,14 +324,14 @@ class AXNode { } // Here and below: Android heuristics - if (this._hasFocusableChild()) return false; - if (this._focusable && this._name) return true; - if (this._role === 'heading' && this._name) return true; + if (this.#hasFocusableChild()) return false; + if (this.#focusable && this.#name) return true; + if (this.#role === 'heading' && this.#name) return true; return false; } public isControl(): boolean { - switch (this._role) { + switch (this.#role) { case 'button': case 'checkbox': case 'ColorWell': @@ -360,10 +360,10 @@ class AXNode { } public isInteresting(insideControl: boolean): boolean { - const role = this._role; - if (role === 'Ignored' || this._hidden || this._ignored) return false; + const role = this.#role; + if (role === 'Ignored' || this.#hidden || this.#ignored) return false; - if (this._focusable || this._richlyEditable) return true; + if (this.#focusable || this.#richlyEditable) return true; // If it's not focusable but has a control role, then it's interesting. if (this.isControl()) return true; @@ -371,7 +371,7 @@ class AXNode { // A non focusable child of a control is not interesting if (insideControl) return false; - return this.isLeafNode() && !!this._name; + return this.isLeafNode() && !!this.#name; } public serialize(): SerializedAXNode { @@ -384,7 +384,7 @@ class AXNode { properties.set('description', this.payload.description.value); const node: SerializedAXNode = { - role: this._role, + role: this.#role, }; type UserStringProperty = @@ -440,7 +440,7 @@ class AXNode { // RootWebArea's treat focus differently than other nodes. They report whether // their frame has focus, not whether focus is specifically on the root // node. - if (booleanProperty === 'focused' && this._role === 'RootWebArea') + if (booleanProperty === 'focused' && this.#role === 'RootWebArea') continue; const value = getBooleanPropertyValue(booleanProperty); if (!value) continue; diff --git a/src/common/AriaQueryHandler.ts b/src/common/AriaQueryHandler.ts index d9e58cc3..96c4ecd1 100644 --- a/src/common/AriaQueryHandler.ts +++ b/src/common/AriaQueryHandler.ts @@ -107,7 +107,7 @@ const waitFor = async ( return element; }, }; - return domWorld.waitForSelectorInPage( + return domWorld._waitForSelectorInPage( (_: Element, selector: string) => ( globalThis as unknown as { ariaQuerySelector(selector: string): void } @@ -146,7 +146,7 @@ const queryAllArray = async ( /** * @internal */ -export const ariaHandler: InternalQueryHandler = { +export const _ariaHandler: InternalQueryHandler = { queryOne, waitFor, queryAll, diff --git a/src/common/Browser.ts b/src/common/Browser.ts index 60208af9..c0658067 100644 --- a/src/common/Browser.ts +++ b/src/common/Browser.ts @@ -217,7 +217,7 @@ export class Browser extends EventEmitter { /** * @internal */ - static async create( + static async _create( connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, @@ -240,22 +240,25 @@ export class Browser extends EventEmitter { await connection.send('Target.setDiscoverTargets', { discover: true }); return browser; } - private _ignoreHTTPSErrors: boolean; - private _defaultViewport?: Viewport | null; - private _process?: ChildProcess; - private _connection: Connection; - private _closeCallback: BrowserCloseCallback; - private _targetFilterCallback: TargetFilterCallback; - private _isPageTargetCallback!: IsPageTargetCallback; - private _defaultContext: BrowserContext; - private _contexts: Map; - private _screenshotTaskQueue: TaskQueue; - private _ignoredTargets = new Set(); + #ignoreHTTPSErrors: boolean; + #defaultViewport?: Viewport | null; + #process?: ChildProcess; + #connection: Connection; + #closeCallback: BrowserCloseCallback; + #targetFilterCallback: TargetFilterCallback; + #isPageTargetCallback!: IsPageTargetCallback; + #defaultContext: BrowserContext; + #contexts: Map; + #screenshotTaskQueue: TaskQueue; + #targets: Map; + #ignoredTargets = new Set(); + /** * @internal - * Used in Target.ts directly so cannot be marked private. */ - _targets: Map; + get _targets(): Map { + return this.#targets; + } /** * @internal @@ -271,35 +274,35 @@ export class Browser extends EventEmitter { isPageTargetCallback?: IsPageTargetCallback ) { super(); - this._ignoreHTTPSErrors = ignoreHTTPSErrors; - this._defaultViewport = defaultViewport; - this._process = process; - this._screenshotTaskQueue = new TaskQueue(); - this._connection = connection; - this._closeCallback = closeCallback || function (): void {}; - this._targetFilterCallback = targetFilterCallback || ((): boolean => true); - this._setIsPageTargetCallback(isPageTargetCallback); + this.#ignoreHTTPSErrors = ignoreHTTPSErrors; + this.#defaultViewport = defaultViewport; + this.#process = process; + this.#screenshotTaskQueue = new TaskQueue(); + this.#connection = connection; + this.#closeCallback = closeCallback || function (): void {}; + this.#targetFilterCallback = targetFilterCallback || ((): boolean => true); + this.#setIsPageTargetCallback(isPageTargetCallback); - this._defaultContext = new BrowserContext(this._connection, this); - this._contexts = new Map(); + this.#defaultContext = new BrowserContext(this.#connection, this); + this.#contexts = new Map(); for (const contextId of contextIds) - this._contexts.set( + this.#contexts.set( contextId, - new BrowserContext(this._connection, this, contextId) + new BrowserContext(this.#connection, this, contextId) ); - this._targets = new Map(); - this._connection.on(ConnectionEmittedEvents.Disconnected, () => + this.#targets = new Map(); + this.#connection.on(ConnectionEmittedEvents.Disconnected, () => this.emit(BrowserEmittedEvents.Disconnected) ); - this._connection.on('Target.targetCreated', this._targetCreated.bind(this)); - this._connection.on( + this.#connection.on('Target.targetCreated', this.#targetCreated.bind(this)); + this.#connection.on( 'Target.targetDestroyed', - this._targetDestroyed.bind(this) + this.#targetDestroyed.bind(this) ); - this._connection.on( + this.#connection.on( 'Target.targetInfoChanged', - this._targetInfoChanged.bind(this) + this.#targetInfoChanged.bind(this) ); } @@ -308,14 +311,11 @@ export class Browser extends EventEmitter { * {@link Puppeteer.connect}. */ process(): ChildProcess | null { - return this._process ?? null; + return this.#process ?? null; } - /** - * @internal - */ - _setIsPageTargetCallback(isPageTargetCallback?: IsPageTargetCallback): void { - this._isPageTargetCallback = + #setIsPageTargetCallback(isPageTargetCallback?: IsPageTargetCallback): void { + this.#isPageTargetCallback = isPageTargetCallback || ((target: Protocol.Target.TargetInfo): boolean => { return ( @@ -330,7 +330,7 @@ export class Browser extends EventEmitter { * @internal */ _getIsPageTargetCallback(): IsPageTargetCallback | undefined { - return this._isPageTargetCallback; + return this.#isPageTargetCallback; } /** @@ -355,7 +355,7 @@ export class Browser extends EventEmitter { ): Promise { const { proxyServer, proxyBypassList } = options; - const { browserContextId } = await this._connection.send( + const { browserContextId } = await this.#connection.send( 'Target.createBrowserContext', { proxyServer, @@ -363,11 +363,11 @@ export class Browser extends EventEmitter { } ); const context = new BrowserContext( - this._connection, + this.#connection, this, browserContextId ); - this._contexts.set(browserContextId, context); + this.#contexts.set(browserContextId, context); return context; } @@ -376,64 +376,63 @@ export class Browser extends EventEmitter { * return a single instance of {@link BrowserContext}. */ browserContexts(): BrowserContext[] { - return [this._defaultContext, ...Array.from(this._contexts.values())]; + return [this.#defaultContext, ...Array.from(this.#contexts.values())]; } /** * Returns the default browser context. The default browser context cannot be closed. */ defaultBrowserContext(): BrowserContext { - return this._defaultContext; + return this.#defaultContext; } /** * @internal - * Used by BrowserContext directly so cannot be marked private. */ async _disposeContext(contextId?: string): Promise { if (!contextId) { return; } - await this._connection.send('Target.disposeBrowserContext', { + await this.#connection.send('Target.disposeBrowserContext', { browserContextId: contextId, }); - this._contexts.delete(contextId); + this.#contexts.delete(contextId); } - private async _targetCreated( + async #targetCreated( event: Protocol.Target.TargetCreatedEvent ): Promise { const targetInfo = event.targetInfo; const { browserContextId } = targetInfo; const context = - browserContextId && this._contexts.has(browserContextId) - ? this._contexts.get(browserContextId) - : this._defaultContext; + browserContextId && this.#contexts.has(browserContextId) + ? this.#contexts.get(browserContextId) + : this.#defaultContext; if (!context) { throw new Error('Missing browser context'); } - const shouldAttachToTarget = this._targetFilterCallback(targetInfo); + const shouldAttachToTarget = this.#targetFilterCallback(targetInfo); if (!shouldAttachToTarget) { - this._ignoredTargets.add(targetInfo.targetId); + this.#ignoredTargets.add(targetInfo.targetId); return; } const target = new Target( targetInfo, context, - () => this._connection.createSession(targetInfo), - this._ignoreHTTPSErrors, - this._defaultViewport ?? null, - this._screenshotTaskQueue, - this._isPageTargetCallback + () => this.#connection.createSession(targetInfo), + this.#ignoreHTTPSErrors, + this.#defaultViewport ?? null, + this.#screenshotTaskQueue, + this.#isPageTargetCallback ); assert( - !this._targets.has(event.targetInfo.targetId), + !this.#targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated' ); - this._targets.set(event.targetInfo.targetId, target); + this.#targets.set(event.targetInfo.targetId, target); if (await target._initializedPromise) { this.emit(BrowserEmittedEvents.TargetCreated, target); @@ -441,16 +440,16 @@ export class Browser extends EventEmitter { } } - private async _targetDestroyed(event: { targetId: string }): Promise { - if (this._ignoredTargets.has(event.targetId)) return; - const target = this._targets.get(event.targetId); + async #targetDestroyed(event: { targetId: string }): Promise { + if (this.#ignoredTargets.has(event.targetId)) return; + const target = this.#targets.get(event.targetId); if (!target) { throw new Error( `Missing target in _targetDestroyed (id = ${event.targetId})` ); } target._initializedCallback(false); - this._targets.delete(event.targetId); + this.#targets.delete(event.targetId); target._closedCallback(); if (await target._initializedPromise) { this.emit(BrowserEmittedEvents.TargetDestroyed, target); @@ -460,11 +459,9 @@ export class Browser extends EventEmitter { } } - private _targetInfoChanged( - event: Protocol.Target.TargetInfoChangedEvent - ): void { - if (this._ignoredTargets.has(event.targetInfo.targetId)) return; - const target = this._targets.get(event.targetInfo.targetId); + #targetInfoChanged(event: Protocol.Target.TargetInfoChangedEvent): void { + if (this.#ignoredTargets.has(event.targetInfo.targetId)) return; + const target = this.#targets.get(event.targetInfo.targetId); if (!target) { throw new Error( `Missing target in targetInfoChanged (id = ${event.targetInfo.targetId})` @@ -499,7 +496,7 @@ export class Browser extends EventEmitter { * | browser endpoint}. */ wsEndpoint(): string { - return this._connection.url(); + return this.#connection.url(); } /** @@ -507,19 +504,18 @@ export class Browser extends EventEmitter { * a default browser context. */ async newPage(): Promise { - return this._defaultContext.newPage(); + return this.#defaultContext.newPage(); } /** * @internal - * Used by BrowserContext directly so cannot be marked private. */ async _createPageInContext(contextId?: string): Promise { - const { targetId } = await this._connection.send('Target.createTarget', { + const { targetId } = await this.#connection.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined, }); - const target = this._targets.get(targetId); + const target = this.#targets.get(targetId); if (!target) { throw new Error(`Missing target for page (id = ${targetId})`); } @@ -541,7 +537,7 @@ export class Browser extends EventEmitter { * an array with all the targets in all browser contexts. */ targets(): Target[] { - return Array.from(this._targets.values()).filter( + return Array.from(this.#targets.values()).filter( (target) => target._isInitialized ); } @@ -628,7 +624,7 @@ export class Browser extends EventEmitter { * The format of browser.version() might change with future releases of Chromium. */ async version(): Promise { - const version = await this._getVersion(); + const version = await this.#getVersion(); return version.product; } @@ -637,7 +633,7 @@ export class Browser extends EventEmitter { * {@link Page.setUserAgent}. */ async userAgent(): Promise { - const version = await this._getVersion(); + const version = await this.#getVersion(); return version.userAgent; } @@ -646,7 +642,7 @@ export class Browser extends EventEmitter { * itself is considered to be disposed and cannot be used anymore. */ async close(): Promise { - await this._closeCallback.call(null); + await this.#closeCallback.call(null); this.disconnect(); } @@ -656,18 +652,18 @@ export class Browser extends EventEmitter { * cannot be used anymore. */ disconnect(): void { - this._connection.dispose(); + this.#connection.dispose(); } /** * Indicates that the browser is connected. */ isConnected(): boolean { - return !this._connection._closed; + return !this.#connection._closed; } - private _getVersion(): Promise { - return this._connection.send('Browser.getVersion'); + #getVersion(): Promise { + return this.#connection.send('Browser.getVersion'); } } /** @@ -729,25 +725,25 @@ export const enum BrowserContextEmittedEvents { * @public */ export class BrowserContext extends EventEmitter { - private _connection: Connection; - private _browser: Browser; - private _id?: string; + #connection: Connection; + #browser: Browser; + #id?: string; /** * @internal */ constructor(connection: Connection, browser: Browser, contextId?: string) { super(); - this._connection = connection; - this._browser = browser; - this._id = contextId; + this.#connection = connection; + this.#browser = browser; + this.#id = contextId; } /** * An array of all active targets inside the browser context. */ targets(): Target[] { - return this._browser + return this.#browser .targets() .filter((target) => target.browserContext() === this); } @@ -773,7 +769,7 @@ export class BrowserContext extends EventEmitter { predicate: (x: Target) => boolean | Promise, options: { timeout?: number } = {} ): Promise { - return this._browser.waitForTarget( + return this.#browser.waitForTarget( (target) => target.browserContext() === this && predicate(target), options ); @@ -793,7 +789,7 @@ export class BrowserContext extends EventEmitter { (target) => target.type() === 'page' || (target.type() === 'other' && - this._browser._getIsPageTargetCallback()?.( + this.#browser._getIsPageTargetCallback()?.( target._getTargetInfo() )) ) @@ -810,7 +806,7 @@ export class BrowserContext extends EventEmitter { * The default browser context cannot be closed. */ isIncognito(): boolean { - return !!this._id; + return !!this.#id; } /** @@ -835,9 +831,9 @@ export class BrowserContext extends EventEmitter { throw new Error('Unknown permission: ' + permission); return protocolPermission; }); - await this._connection.send('Browser.grantPermissions', { + await this.#connection.send('Browser.grantPermissions', { origin, - browserContextId: this._id || undefined, + browserContextId: this.#id || undefined, permissions: protocolPermissions, }); } @@ -854,8 +850,8 @@ export class BrowserContext extends EventEmitter { * ``` */ async clearPermissionOverrides(): Promise { - await this._connection.send('Browser.resetPermissions', { - browserContextId: this._id || undefined, + await this.#connection.send('Browser.resetPermissions', { + browserContextId: this.#id || undefined, }); } @@ -863,14 +859,14 @@ export class BrowserContext extends EventEmitter { * Creates a new page in the browser context. */ newPage(): Promise { - return this._browser._createPageInContext(this._id); + return this.#browser._createPageInContext(this.#id); } /** * The browser this browser context belongs to. */ browser(): Browser { - return this._browser; + return this.#browser; } /** @@ -881,7 +877,7 @@ export class BrowserContext extends EventEmitter { * Only incognito browser contexts can be closed. */ async close(): Promise { - assert(this._id, 'Non-incognito profiles cannot be closed!'); - await this._browser._disposeContext(this._id); + assert(this.#id, 'Non-incognito profiles cannot be closed!'); + await this.#browser._disposeContext(this.#id); } } diff --git a/src/common/BrowserConnector.ts b/src/common/BrowserConnector.ts index c2ce220f..ea69d140 100644 --- a/src/common/BrowserConnector.ts +++ b/src/common/BrowserConnector.ts @@ -54,7 +54,7 @@ export interface BrowserConnectOptions { /** * @internal */ - isPageTarget?: IsPageTargetCallback; + _isPageTarget?: IsPageTargetCallback; } const getWebSocketTransportClass = async () => { @@ -67,15 +67,16 @@ const getWebSocketTransportClass = async () => { /** * Users should never call this directly; it's called when calling * `puppeteer.connect`. + * * @internal */ -export const connectToBrowser = async ( +export async function _connectToBrowser( options: BrowserConnectOptions & { browserWSEndpoint?: string; browserURL?: string; transport?: ConnectionTransport; } -): Promise => { +): Promise { const { browserWSEndpoint, browserURL, @@ -84,7 +85,7 @@ export const connectToBrowser = async ( transport, slowMo = 0, targetFilter, - isPageTarget, + _isPageTarget: isPageTarget, } = options; assert( @@ -112,7 +113,7 @@ export const connectToBrowser = async ( const { browserContextIds } = await connection.send( 'Target.getBrowserContexts' ); - return Browser.create( + return Browser._create( connection, browserContextIds, ignoreHTTPSErrors, @@ -122,7 +123,7 @@ export const connectToBrowser = async ( targetFilter, isPageTarget ); -}; +} async function getWSEndpoint(browserURL: string): Promise { const endpointURL = new URL('/json/version', browserURL); diff --git a/src/common/BrowserWebSocketTransport.ts b/src/common/BrowserWebSocketTransport.ts index 0ebddb8a..b6bf905f 100644 --- a/src/common/BrowserWebSocketTransport.ts +++ b/src/common/BrowserWebSocketTransport.ts @@ -27,27 +27,27 @@ export class BrowserWebSocketTransport implements ConnectionTransport { }); } - private _ws: WebSocket; + #ws: WebSocket; onmessage?: (message: string) => void; onclose?: () => void; constructor(ws: WebSocket) { - this._ws = ws; - this._ws.addEventListener('message', (event) => { + this.#ws = ws; + this.#ws.addEventListener('message', (event) => { if (this.onmessage) this.onmessage.call(null, event.data); }); - this._ws.addEventListener('close', () => { + this.#ws.addEventListener('close', () => { if (this.onclose) this.onclose.call(null); }); // Silently ignore all errors - we don't know what to do with them. - this._ws.addEventListener('error', () => {}); + this.#ws.addEventListener('error', () => {}); } send(message: string): void { - this._ws.send(message); + this.#ws.send(message); } close(): void { - this._ws.close(); + this.#ws.close(); } } diff --git a/src/common/Connection.ts b/src/common/Connection.ts index 10b12254..da625087 100644 --- a/src/common/Connection.ts +++ b/src/common/Connection.ts @@ -52,27 +52,33 @@ export const ConnectionEmittedEvents = { * @public */ export class Connection extends EventEmitter { - _url: string; - _transport: ConnectionTransport; - _delay: number; - _lastId = 0; - _sessions: Map = new Map(); - _closed = false; - - _callbacks: Map = new Map(); + #url: string; + #transport: ConnectionTransport; + #delay: number; + #lastId = 0; + #sessions: Map = new Map(); + #closed = false; + #callbacks: Map = new Map(); constructor(url: string, transport: ConnectionTransport, delay = 0) { super(); - this._url = url; - this._delay = delay; + this.#url = url; + this.#delay = delay; - this._transport = transport; - this._transport.onmessage = this._onMessage.bind(this); - this._transport.onclose = this._onClose.bind(this); + this.#transport = transport; + this.#transport.onmessage = this.#onMessage.bind(this); + this.#transport.onclose = this.#onClose.bind(this); } static fromSession(session: CDPSession): Connection | undefined { - return session._connection; + return session.connection(); + } + + /** + * @internal + */ + get _closed(): boolean { + return this.#closed; } /** @@ -80,11 +86,11 @@ export class Connection extends EventEmitter { * @returns The current CDP session if it exists */ session(sessionId: string): CDPSession | null { - return this._sessions.get(sessionId) || null; + return this.#sessions.get(sessionId) || null; } url(): string { - return this._url; + return this.#url; } send( @@ -100,7 +106,7 @@ export class Connection extends EventEmitter { const params = paramArgs.length ? paramArgs[0] : undefined; const id = this._rawSend({ method, params }); return new Promise((resolve, reject) => { - this._callbacks.set(id, { + this.#callbacks.set(id, { resolve, reject, error: new ProtocolError(), @@ -109,18 +115,21 @@ export class Connection extends EventEmitter { }); } + /** + * @internal + */ _rawSend(message: Record): number { - const id = ++this._lastId; + const id = ++this.#lastId; const stringifiedMessage = JSON.stringify( Object.assign({}, message, { id }) ); debugProtocolSend(stringifiedMessage); - this._transport.send(stringifiedMessage); + this.#transport.send(stringifiedMessage); return id; } - async _onMessage(message: string): Promise { - if (this._delay) await new Promise((f) => setTimeout(f, this._delay)); + async #onMessage(message: string): Promise { + if (this.#delay) await new Promise((f) => setTimeout(f, this.#delay)); debugProtocolReceive(message); const object = JSON.parse(message); if (object.method === 'Target.attachedToTarget') { @@ -130,32 +139,32 @@ export class Connection extends EventEmitter { object.params.targetInfo.type, sessionId ); - this._sessions.set(sessionId, session); + this.#sessions.set(sessionId, session); this.emit('sessionattached', session); - const parentSession = this._sessions.get(object.sessionId); + const parentSession = this.#sessions.get(object.sessionId); if (parentSession) { parentSession.emit('sessionattached', session); } } else if (object.method === 'Target.detachedFromTarget') { - const session = this._sessions.get(object.params.sessionId); + const session = this.#sessions.get(object.params.sessionId); if (session) { session._onClosed(); - this._sessions.delete(object.params.sessionId); + this.#sessions.delete(object.params.sessionId); this.emit('sessiondetached', session); - const parentSession = this._sessions.get(object.sessionId); + const parentSession = this.#sessions.get(object.sessionId); if (parentSession) { parentSession.emit('sessiondetached', session); } } } if (object.sessionId) { - const session = this._sessions.get(object.sessionId); + const session = this.#sessions.get(object.sessionId); if (session) session._onMessage(object); } else if (object.id) { - const callback = this._callbacks.get(object.id); + const callback = this.#callbacks.get(object.id); // Callbacks could be all rejected if someone has called `.dispose()`. if (callback) { - this._callbacks.delete(object.id); + this.#callbacks.delete(object.id); if (object.error) callback.reject( createProtocolError(callback.error, callback.method, object) @@ -167,27 +176,27 @@ export class Connection extends EventEmitter { } } - _onClose(): void { - if (this._closed) return; - this._closed = true; - this._transport.onmessage = undefined; - this._transport.onclose = undefined; - for (const callback of this._callbacks.values()) + #onClose(): void { + if (this.#closed) return; + this.#closed = true; + this.#transport.onmessage = undefined; + this.#transport.onclose = undefined; + for (const callback of this.#callbacks.values()) callback.reject( rewriteError( callback.error, `Protocol error (${callback.method}): Target closed.` ) ); - this._callbacks.clear(); - for (const session of this._sessions.values()) session._onClosed(); - this._sessions.clear(); + this.#callbacks.clear(); + for (const session of this.#sessions.values()) session._onClosed(); + this.#sessions.clear(); this.emit(ConnectionEmittedEvents.Disconnected); } dispose(): void { - this._onClose(); - this._transport.close(); + this.#onClose(); + this.#transport.close(); } /** @@ -201,7 +210,7 @@ export class Connection extends EventEmitter { targetId: targetInfo.targetId, flatten: true, }); - const session = this._sessions.get(sessionId); + const session = this.#sessions.get(sessionId); if (!session) { throw new Error('CDPSession creation failed.'); } @@ -255,50 +264,49 @@ export const CDPSessionEmittedEvents = { * @public */ export class CDPSession extends EventEmitter { - /** - * @internal - */ - _connection?: Connection; - private _sessionId: string; - private _targetType: string; - private _callbacks: Map = new Map(); + #sessionId: string; + #targetType: string; + #callbacks: Map = new Map(); + #connection?: Connection; /** * @internal */ constructor(connection: Connection, targetType: string, sessionId: string) { super(); - this._connection = connection; - this._targetType = targetType; - this._sessionId = sessionId; + this.#connection = connection; + this.#targetType = targetType; + this.#sessionId = sessionId; } connection(): Connection | undefined { - return this._connection; + return this.#connection; } send( method: T, ...paramArgs: ProtocolMapping.Commands[T]['paramsType'] ): Promise { - if (!this._connection) + if (!this.#connection) return Promise.reject( new Error( - `Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.` + `Protocol error (${method}): Session closed. Most likely the ${ + this.#targetType + } has been closed.` ) ); // See the comment in Connection#send explaining why we do this. const params = paramArgs.length ? paramArgs[0] : undefined; - const id = this._connection._rawSend({ - sessionId: this._sessionId, + const id = this.#connection._rawSend({ + sessionId: this.#sessionId, method, params, }); return new Promise((resolve, reject) => { - this._callbacks.set(id, { + this.#callbacks.set(id, { resolve, reject, error: new ProtocolError(), @@ -311,9 +319,9 @@ export class CDPSession extends EventEmitter { * @internal */ _onMessage(object: CDPSessionOnMessageObject): void { - const callback = object.id ? this._callbacks.get(object.id) : undefined; + const callback = object.id ? this.#callbacks.get(object.id) : undefined; if (object.id && callback) { - this._callbacks.delete(object.id); + this.#callbacks.delete(object.id); if (object.error) callback.reject( createProtocolError(callback.error, callback.method, object) @@ -330,12 +338,14 @@ export class CDPSession extends EventEmitter { * won't emit any events and can't be used to send messages. */ async detach(): Promise { - if (!this._connection) + if (!this.#connection) throw new Error( - `Session already detached. Most likely the ${this._targetType} has been closed.` + `Session already detached. Most likely the ${ + this.#targetType + } has been closed.` ); - await this._connection.send('Target.detachFromTarget', { - sessionId: this._sessionId, + await this.#connection.send('Target.detachFromTarget', { + sessionId: this.#sessionId, }); } @@ -343,23 +353,23 @@ export class CDPSession extends EventEmitter { * @internal */ _onClosed(): void { - for (const callback of this._callbacks.values()) + for (const callback of this.#callbacks.values()) callback.reject( rewriteError( callback.error, `Protocol error (${callback.method}): Target closed.` ) ); - this._callbacks.clear(); - this._connection = undefined; + this.#callbacks.clear(); + this.#connection = undefined; this.emit(CDPSessionEmittedEvents.Disconnected); } /** - * @internal + * Returns the session's id. */ id(): string { - return this._sessionId; + return this.#sessionId; } } diff --git a/src/common/ConsoleMessage.ts b/src/common/ConsoleMessage.ts index c31de8ce..49b37ff1 100644 --- a/src/common/ConsoleMessage.ts +++ b/src/common/ConsoleMessage.ts @@ -66,10 +66,10 @@ export type ConsoleMessageType = * @public */ export class ConsoleMessage { - private _type: ConsoleMessageType; - private _text: string; - private _args: JSHandle[]; - private _stackTraceLocations: ConsoleMessageLocation[]; + #type: ConsoleMessageType; + #text: string; + #args: JSHandle[]; + #stackTraceLocations: ConsoleMessageLocation[]; /** * @public @@ -80,44 +80,44 @@ export class ConsoleMessage { args: JSHandle[], stackTraceLocations: ConsoleMessageLocation[] ) { - this._type = type; - this._text = text; - this._args = args; - this._stackTraceLocations = stackTraceLocations; + this.#type = type; + this.#text = text; + this.#args = args; + this.#stackTraceLocations = stackTraceLocations; } /** * @returns The type of the console message. */ type(): ConsoleMessageType { - return this._type; + return this.#type; } /** * @returns The text of the console message. */ text(): string { - return this._text; + return this.#text; } /** * @returns An array of arguments passed to the console. */ args(): JSHandle[] { - return this._args; + return this.#args; } /** * @returns The location of the console message. */ location(): ConsoleMessageLocation { - return this._stackTraceLocations[0] ?? {}; + return this.#stackTraceLocations[0] ?? {}; } /** * @returns The array of locations on the stack of the console message. */ stackTrace(): ConsoleMessageLocation[] { - return this._stackTraceLocations; + return this.#stackTraceLocations; } } diff --git a/src/common/Coverage.ts b/src/common/Coverage.ts index 286e5c42..a21910f0 100644 --- a/src/common/Coverage.ts +++ b/src/common/Coverage.ts @@ -123,18 +123,12 @@ export interface CSSCoverageOptions { * @public */ export class Coverage { - /** - * @internal - */ - _jsCoverage: JSCoverage; - /** - * @internal - */ - _cssCoverage: CSSCoverage; + #jsCoverage: JSCoverage; + #cssCoverage: CSSCoverage; constructor(client: CDPSession) { - this._jsCoverage = new JSCoverage(client); - this._cssCoverage = new CSSCoverage(client); + this.#jsCoverage = new JSCoverage(client); + this.#cssCoverage = new CSSCoverage(client); } /** @@ -149,7 +143,7 @@ export class Coverage { * scripts will have `pptr://__puppeteer_evaluation_script__` as their URL. */ async startJSCoverage(options: JSCoverageOptions = {}): Promise { - return await this._jsCoverage.start(options); + return await this.#jsCoverage.start(options); } /** @@ -161,7 +155,7 @@ export class Coverage { * However, scripts with sourceURLs are reported. */ async stopJSCoverage(): Promise { - return await this._jsCoverage.stop(); + return await this.#jsCoverage.stop(); } /** @@ -170,7 +164,7 @@ export class Coverage { * @returns Promise that resolves when coverage is started. */ async startCSSCoverage(options: CSSCoverageOptions = {}): Promise { - return await this._cssCoverage.start(options); + return await this.#cssCoverage.start(options); } /** @@ -181,7 +175,7 @@ export class Coverage { * without sourceURLs. */ async stopCSSCoverage(): Promise { - return await this._cssCoverage.stop(); + return await this.#cssCoverage.stop(); } } @@ -189,17 +183,17 @@ export class Coverage { * @public */ export class JSCoverage { - _client: CDPSession; - _enabled = false; - _scriptURLs = new Map(); - _scriptSources = new Map(); - _eventListeners: PuppeteerEventListener[] = []; - _resetOnNavigation = false; - _reportAnonymousScripts = false; - _includeRawScriptCoverage = false; + #client: CDPSession; + #enabled = false; + #scriptURLs = new Map(); + #scriptSources = new Map(); + #eventListeners: PuppeteerEventListener[] = []; + #resetOnNavigation = false; + #reportAnonymousScripts = false; + #includeRawScriptCoverage = false; constructor(client: CDPSession) { - this._client = client; + this.#client = client; } async start( @@ -209,60 +203,60 @@ export class JSCoverage { includeRawScriptCoverage?: boolean; } = {} ): Promise { - assert(!this._enabled, 'JSCoverage is already enabled'); + assert(!this.#enabled, 'JSCoverage is already enabled'); const { resetOnNavigation = true, reportAnonymousScripts = false, includeRawScriptCoverage = false, } = options; - this._resetOnNavigation = resetOnNavigation; - this._reportAnonymousScripts = reportAnonymousScripts; - this._includeRawScriptCoverage = includeRawScriptCoverage; - this._enabled = true; - this._scriptURLs.clear(); - this._scriptSources.clear(); - this._eventListeners = [ + this.#resetOnNavigation = resetOnNavigation; + this.#reportAnonymousScripts = reportAnonymousScripts; + this.#includeRawScriptCoverage = includeRawScriptCoverage; + this.#enabled = true; + this.#scriptURLs.clear(); + this.#scriptSources.clear(); + this.#eventListeners = [ helper.addEventListener( - this._client, + this.#client, 'Debugger.scriptParsed', - this._onScriptParsed.bind(this) + this.#onScriptParsed.bind(this) ), helper.addEventListener( - this._client, + this.#client, 'Runtime.executionContextsCleared', - this._onExecutionContextsCleared.bind(this) + this.#onExecutionContextsCleared.bind(this) ), ]; await Promise.all([ - this._client.send('Profiler.enable'), - this._client.send('Profiler.startPreciseCoverage', { - callCount: this._includeRawScriptCoverage, + this.#client.send('Profiler.enable'), + this.#client.send('Profiler.startPreciseCoverage', { + callCount: this.#includeRawScriptCoverage, detailed: true, }), - this._client.send('Debugger.enable'), - this._client.send('Debugger.setSkipAllPauses', { skip: true }), + this.#client.send('Debugger.enable'), + this.#client.send('Debugger.setSkipAllPauses', { skip: true }), ]); } - _onExecutionContextsCleared(): void { - if (!this._resetOnNavigation) return; - this._scriptURLs.clear(); - this._scriptSources.clear(); + #onExecutionContextsCleared(): void { + if (!this.#resetOnNavigation) return; + this.#scriptURLs.clear(); + this.#scriptSources.clear(); } - async _onScriptParsed( + async #onScriptParsed( event: Protocol.Debugger.ScriptParsedEvent ): Promise { // Ignore puppeteer-injected scripts if (event.url === EVALUATION_SCRIPT_URL) return; // Ignore other anonymous scripts unless the reportAnonymousScripts option is true. - if (!event.url && !this._reportAnonymousScripts) return; + if (!event.url && !this.#reportAnonymousScripts) return; try { - const response = await this._client.send('Debugger.getScriptSource', { + const response = await this.#client.send('Debugger.getScriptSource', { scriptId: event.scriptId, }); - this._scriptURLs.set(event.scriptId, event.url); - this._scriptSources.set(event.scriptId, response.scriptSource); + this.#scriptURLs.set(event.scriptId, event.url); + this.#scriptSources.set(event.scriptId, response.scriptSource); } catch (error) { // This might happen if the page has already navigated away. debugError(error); @@ -270,31 +264,31 @@ export class JSCoverage { } async stop(): Promise { - assert(this._enabled, 'JSCoverage is not enabled'); - this._enabled = false; + assert(this.#enabled, 'JSCoverage is not enabled'); + this.#enabled = false; const result = await Promise.all([ - this._client.send('Profiler.takePreciseCoverage'), - this._client.send('Profiler.stopPreciseCoverage'), - this._client.send('Profiler.disable'), - this._client.send('Debugger.disable'), + this.#client.send('Profiler.takePreciseCoverage'), + this.#client.send('Profiler.stopPreciseCoverage'), + this.#client.send('Profiler.disable'), + this.#client.send('Debugger.disable'), ]); - helper.removeEventListeners(this._eventListeners); + helper.removeEventListeners(this.#eventListeners); const coverage = []; const profileResponse = result[0]; for (const entry of profileResponse.result) { - let url = this._scriptURLs.get(entry.scriptId); - if (!url && this._reportAnonymousScripts) + let url = this.#scriptURLs.get(entry.scriptId); + if (!url && this.#reportAnonymousScripts) url = 'debugger://VM' + entry.scriptId; - const text = this._scriptSources.get(entry.scriptId); + const text = this.#scriptSources.get(entry.scriptId); if (text === undefined || url === undefined) continue; const flattenRanges = []; for (const func of entry.functions) flattenRanges.push(...func.ranges); const ranges = convertToDisjointRanges(flattenRanges); - if (!this._includeRawScriptCoverage) { + if (!this.#includeRawScriptCoverage) { coverage.push({ url, ranges, text }); } else { coverage.push({ url, ranges, text, rawScriptCoverage: entry }); @@ -308,60 +302,59 @@ export class JSCoverage { * @public */ export class CSSCoverage { - _client: CDPSession; - _enabled = false; - _stylesheetURLs = new Map(); - _stylesheetSources = new Map(); - _eventListeners: PuppeteerEventListener[] = []; - _resetOnNavigation = false; - _reportAnonymousScripts = false; + #client: CDPSession; + #enabled = false; + #stylesheetURLs = new Map(); + #stylesheetSources = new Map(); + #eventListeners: PuppeteerEventListener[] = []; + #resetOnNavigation = false; constructor(client: CDPSession) { - this._client = client; + this.#client = client; } async start(options: { resetOnNavigation?: boolean } = {}): Promise { - assert(!this._enabled, 'CSSCoverage is already enabled'); + assert(!this.#enabled, 'CSSCoverage is already enabled'); const { resetOnNavigation = true } = options; - this._resetOnNavigation = resetOnNavigation; - this._enabled = true; - this._stylesheetURLs.clear(); - this._stylesheetSources.clear(); - this._eventListeners = [ + this.#resetOnNavigation = resetOnNavigation; + this.#enabled = true; + this.#stylesheetURLs.clear(); + this.#stylesheetSources.clear(); + this.#eventListeners = [ helper.addEventListener( - this._client, + this.#client, 'CSS.styleSheetAdded', - this._onStyleSheet.bind(this) + this.#onStyleSheet.bind(this) ), helper.addEventListener( - this._client, + this.#client, 'Runtime.executionContextsCleared', - this._onExecutionContextsCleared.bind(this) + this.#onExecutionContextsCleared.bind(this) ), ]; await Promise.all([ - this._client.send('DOM.enable'), - this._client.send('CSS.enable'), - this._client.send('CSS.startRuleUsageTracking'), + this.#client.send('DOM.enable'), + this.#client.send('CSS.enable'), + this.#client.send('CSS.startRuleUsageTracking'), ]); } - _onExecutionContextsCleared(): void { - if (!this._resetOnNavigation) return; - this._stylesheetURLs.clear(); - this._stylesheetSources.clear(); + #onExecutionContextsCleared(): void { + if (!this.#resetOnNavigation) return; + this.#stylesheetURLs.clear(); + this.#stylesheetSources.clear(); } - async _onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise { + async #onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise { const header = event.header; // Ignore anonymous scripts if (!header.sourceURL) return; try { - const response = await this._client.send('CSS.getStyleSheetText', { + const response = await this.#client.send('CSS.getStyleSheetText', { styleSheetId: header.styleSheetId, }); - this._stylesheetURLs.set(header.styleSheetId, header.sourceURL); - this._stylesheetSources.set(header.styleSheetId, response.text); + this.#stylesheetURLs.set(header.styleSheetId, header.sourceURL); + this.#stylesheetSources.set(header.styleSheetId, response.text); } catch (error) { // This might happen if the page has already navigated away. debugError(error); @@ -369,16 +362,16 @@ export class CSSCoverage { } async stop(): Promise { - assert(this._enabled, 'CSSCoverage is not enabled'); - this._enabled = false; - const ruleTrackingResponse = await this._client.send( + assert(this.#enabled, 'CSSCoverage is not enabled'); + this.#enabled = false; + const ruleTrackingResponse = await this.#client.send( 'CSS.stopRuleUsageTracking' ); await Promise.all([ - this._client.send('CSS.disable'), - this._client.send('DOM.disable'), + this.#client.send('CSS.disable'), + this.#client.send('DOM.disable'), ]); - helper.removeEventListeners(this._eventListeners); + helper.removeEventListeners(this.#eventListeners); // aggregate by styleSheetId const styleSheetIdToCoverage = new Map(); @@ -396,10 +389,10 @@ export class CSSCoverage { } const coverage: CoverageEntry[] = []; - for (const styleSheetId of this._stylesheetURLs.keys()) { - const url = this._stylesheetURLs.get(styleSheetId); + for (const styleSheetId of this.#stylesheetURLs.keys()) { + const url = this.#stylesheetURLs.get(styleSheetId); assert(url); - const text = this._stylesheetSources.get(styleSheetId); + const text = this.#stylesheetSources.get(styleSheetId); assert(text); const ranges = convertToDisjointRanges( styleSheetIdToCoverage.get(styleSheetId) || [] diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts index 5558f9d8..33750090 100644 --- a/src/common/DOMWorld.ts +++ b/src/common/DOMWorld.ts @@ -35,7 +35,7 @@ import { LifecycleWatcher, PuppeteerLifeCycleEvent, } from './LifecycleWatcher.js'; -import { getQueryHandlerAndSelector } from './QueryHandler.js'; +import { _getQueryHandlerAndSelector } from './QueryHandler.js'; import { TimeoutSettings } from './TimeoutSettings.js'; // predicateQueryHandler and checkWaitForOptions are declared here so that @@ -72,30 +72,37 @@ export interface PageBinding { * @internal */ export class DOMWorld { - private _frameManager: FrameManager; - private _client: CDPSession; - private _frame: Frame; - private _timeoutSettings: TimeoutSettings; - private _documentPromise: Promise | null = null; - private _contextPromise: Promise | null = null; + #frameManager: FrameManager; + #client: CDPSession; + #frame: Frame; + #timeoutSettings: TimeoutSettings; + #documentPromise: Promise | null = null; + #contextPromise: Promise | null = null; + #contextResolveCallback: ((x: ExecutionContext) => void) | null = null; + #detached = false; - private _contextResolveCallback: ((x: ExecutionContext) => void) | null = - null; - - private _detached = false; - /** - * @internal - */ - _waitTasks = new Set(); - - /** - * @internal - * Contains mapping from functions that should be bound to Puppeteer functions. - */ - _boundFunctions = new Map(); // Set of bindings that have been registered in the current context. - private _ctxBindings = new Set(); - private static bindingIdentifier = (name: string, contextId: number) => + #ctxBindings = new Set(); + + // Contains mapping from functions that should be bound to Puppeteer functions. + #boundFunctions = new Map(); + #waitTasks = new Set(); + + /** + * @internal + */ + get _waitTasks(): Set { + return this.#waitTasks; + } + + /** + * @internal + */ + get _boundFunctions(): Map { + return this.#boundFunctions; + } + + static #bindingIdentifier = (name: string, contextId: number) => `${name}_${contextId}`; constructor( @@ -106,44 +113,52 @@ export class DOMWorld { ) { // Keep own reference to client because it might differ from the FrameManager's // client for OOP iframes. - this._client = client; - this._frameManager = frameManager; - this._frame = frame; - this._timeoutSettings = timeoutSettings; + this.#client = client; + this.#frameManager = frameManager; + this.#frame = frame; + this.#timeoutSettings = timeoutSettings; this._setContext(null); - this._onBindingCalled = this._onBindingCalled.bind(this); - this._client.on('Runtime.bindingCalled', this._onBindingCalled); + this.#client.on('Runtime.bindingCalled', this.#onBindingCalled); } frame(): Frame { - return this._frame; + return this.#frame; } + /** + * @internal + */ async _setContext(context: ExecutionContext | null): Promise { if (context) { assert( - this._contextResolveCallback, + this.#contextResolveCallback, 'Execution Context has already been set.' ); - this._ctxBindings.clear(); - this._contextResolveCallback?.call(null, context); - this._contextResolveCallback = null; + this.#ctxBindings.clear(); + this.#contextResolveCallback?.call(null, context); + this.#contextResolveCallback = null; for (const waitTask of this._waitTasks) waitTask.rerun(); } else { - this._documentPromise = null; - this._contextPromise = new Promise((fulfill) => { - this._contextResolveCallback = fulfill; + this.#documentPromise = null; + this.#contextPromise = new Promise((fulfill) => { + this.#contextResolveCallback = fulfill; }); } } + /** + * @internal + */ _hasContext(): boolean { - return !this._contextResolveCallback; + return !this.#contextResolveCallback; } + /** + * @internal + */ _detach(): void { - this._detached = true; - this._client.off('Runtime.bindingCalled', this._onBindingCalled); + this.#detached = true; + this.#client.off('Runtime.bindingCalled', this.#onBindingCalled); for (const waitTask of this._waitTasks) waitTask.terminate( new Error('waitForFunction failed: frame got detached.') @@ -151,13 +166,13 @@ export class DOMWorld { } executionContext(): Promise { - if (this._detached) + if (this.#detached) throw new Error( - `Execution context is not available in detached frame "${this._frame.url()}" (are you trying to evaluate?)` + `Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)` ); - if (this._contextPromise === null) + if (this.#contextPromise === null) throw new Error(`Execution content promise is missing`); - return this._contextPromise; + return this.#contextPromise; } async evaluateHandle( @@ -187,9 +202,12 @@ export class DOMWorld { return value; } + /** + * @internal + */ async _document(): Promise { - if (this._documentPromise) return this._documentPromise; - this._documentPromise = this.executionContext().then(async (context) => { + if (this.#documentPromise) return this.#documentPromise; + this.#documentPromise = this.executionContext().then(async (context) => { const document = await context.evaluateHandle('document'); const element = document.asElement(); if (element === null) { @@ -197,7 +215,7 @@ export class DOMWorld { } return element; }); - return this._documentPromise; + return this.#documentPromise; } async $x(expression: string): Promise { @@ -263,7 +281,7 @@ export class DOMWorld { ): Promise { const { waitUntil = ['load'], - timeout = this._timeoutSettings.navigationTimeout(), + timeout = this.#timeoutSettings.navigationTimeout(), } = options; // We rely upon the fact that document.open() will reset frame lifecycle with "init" // lifecycle event. @see https://crrev.com/608658 @@ -273,8 +291,8 @@ export class DOMWorld { document.close(); }, html); const watcher = new LifecycleWatcher( - this._frameManager, - this._frame, + this.#frameManager, + this.#frame, waitUntil, timeout ); @@ -560,33 +578,34 @@ export class DOMWorld { options: WaitForSelectorOptions ): Promise { const { updatedSelector, queryHandler } = - getQueryHandlerAndSelector(selector); + _getQueryHandlerAndSelector(selector); assert(queryHandler.waitFor, 'Query handler does not support waiting'); return queryHandler.waitFor(this, updatedSelector, options); } // If multiple waitFor are set up asynchronously, we need to wait for the // first one to set up the binding in the page before running the others. - private _settingUpBinding: Promise | null = null; + #settingUpBinding: Promise | null = null; + /** * @internal */ - async addBindingToContext( + async _addBindingToContext( context: ExecutionContext, name: string ): Promise { // Previous operation added the binding so we are done. if ( - this._ctxBindings.has( - DOMWorld.bindingIdentifier(name, context._contextId) + this.#ctxBindings.has( + DOMWorld.#bindingIdentifier(name, context._contextId) ) ) { return; } // Wait for other operation to finish - if (this._settingUpBinding) { - await this._settingUpBinding; - return this.addBindingToContext(context, name); + if (this.#settingUpBinding) { + await this.#settingUpBinding; + return this._addBindingToContext(context, name); } const bind = async (name: string) => { @@ -617,19 +636,19 @@ export class DOMWorld { return; } } - this._ctxBindings.add( - DOMWorld.bindingIdentifier(name, context._contextId) + this.#ctxBindings.add( + DOMWorld.#bindingIdentifier(name, context._contextId) ); }; - this._settingUpBinding = bind(name); - await this._settingUpBinding; - this._settingUpBinding = null; + this.#settingUpBinding = bind(name); + await this.#settingUpBinding; + this.#settingUpBinding = null; } - private async _onBindingCalled( + #onBindingCalled = async ( event: Protocol.Runtime.BindingCalledEvent - ): Promise { + ): Promise => { let payload: { type: string; name: string; seq: number; args: unknown[] }; if (!this._hasContext()) return; const context = await this.executionContext(); @@ -643,8 +662,8 @@ export class DOMWorld { const { type, name, seq, args } = payload; if ( type !== 'internal' || - !this._ctxBindings.has( - DOMWorld.bindingIdentifier(name, context._contextId) + !this.#ctxBindings.has( + DOMWorld.#bindingIdentifier(name, context._contextId) ) ) return; @@ -673,12 +692,12 @@ export class DOMWorld { // @ts-ignore Code is evaluated in a different context. globalThis[name].callbacks.delete(seq); } - } + }; /** * @internal */ - async waitForSelectorInPage( + async _waitForSelectorInPage( queryOne: Function, selector: string, options: WaitForSelectorOptions, @@ -687,7 +706,7 @@ export class DOMWorld { const { visible: waitForVisible = false, hidden: waitForHidden = false, - timeout = this._timeoutSettings.timeout(), + timeout = this.#timeoutSettings.timeout(), } = options; const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; const title = `selector \`${selector}\`${ @@ -732,7 +751,7 @@ export class DOMWorld { const { visible: waitForVisible = false, hidden: waitForHidden = false, - timeout = this._timeoutSettings.timeout(), + timeout = this.#timeoutSettings.timeout(), } = options; const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; const title = `XPath \`${xpath}\`${waitForHidden ? ' to be hidden' : ''}`; @@ -776,7 +795,7 @@ export class DOMWorld { options: { polling?: string | number; timeout?: number } = {}, ...args: SerializableOrJSHandle[] ): Promise { - const { polling = 'raf', timeout = this._timeoutSettings.timeout() } = + const { polling = 'raf', timeout = this.#timeoutSettings.timeout() } = options; const waitTaskOptions: WaitTaskOptions = { domWorld: this, @@ -817,20 +836,21 @@ const noop = (): void => {}; * @internal */ export class WaitTask { - _domWorld: DOMWorld; - _polling: string | number; - _timeout: number; - _predicateBody: string; - _predicateAcceptsContextElement: boolean; - _args: SerializableOrJSHandle[]; - _binding?: PageBinding; - _runCount = 0; + #domWorld: DOMWorld; + #polling: string | number; + #timeout: number; + #predicateBody: string; + #predicateAcceptsContextElement: boolean; + #args: SerializableOrJSHandle[]; + #binding?: PageBinding; + #runCount = 0; + #resolve: (x: JSHandle) => void = noop; + #reject: (x: Error) => void = noop; + #timeoutTimer?: NodeJS.Timeout; + #terminated = false; + #root: ElementHandle | null = null; + promise: Promise; - _resolve: (x: JSHandle) => void = noop; - _reject: (x: Error) => void = noop; - _timeoutTimer?: NodeJS.Timeout; - _terminated = false; - _root: ElementHandle | null = null; constructor(options: WaitTaskOptions) { if (helper.isString(options.polling)) @@ -850,26 +870,26 @@ export class WaitTask { return `return (${predicateBody})(...args);`; } - this._domWorld = options.domWorld; - this._polling = options.polling; - this._timeout = options.timeout; - this._root = options.root || null; - this._predicateBody = getPredicateBody(options.predicateBody); - this._predicateAcceptsContextElement = + this.#domWorld = options.domWorld; + this.#polling = options.polling; + this.#timeout = options.timeout; + this.#root = options.root || null; + this.#predicateBody = getPredicateBody(options.predicateBody); + this.#predicateAcceptsContextElement = options.predicateAcceptsContextElement; - this._args = options.args; - this._binding = options.binding; - this._runCount = 0; - this._domWorld._waitTasks.add(this); - if (this._binding) { - this._domWorld._boundFunctions.set( - this._binding.name, - this._binding.pptrFunction + this.#args = options.args; + this.#binding = options.binding; + this.#runCount = 0; + this.#domWorld._waitTasks.add(this); + if (this.#binding) { + this.#domWorld._boundFunctions.set( + this.#binding.name, + this.#binding.pptrFunction ); } this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; + this.#resolve = resolve; + this.#reject = reject; }); // Since page navigation requires us to re-install the pageScript, we should track // timeout on our end. @@ -877,7 +897,7 @@ export class WaitTask { const timeoutError = new TimeoutError( `waiting for ${options.title} failed: timeout ${options.timeout}ms exceeded` ); - this._timeoutTimer = setTimeout( + this.#timeoutTimer = setTimeout( () => this.terminate(timeoutError), options.timeout ); @@ -886,36 +906,36 @@ export class WaitTask { } terminate(error: Error): void { - this._terminated = true; - this._reject(error); - this._cleanup(); + this.#terminated = true; + this.#reject(error); + this.#cleanup(); } async rerun(): Promise { - const runCount = ++this._runCount; + const runCount = ++this.#runCount; let success: JSHandle | null = null; let error: Error | null = null; - const context = await this._domWorld.executionContext(); - if (this._terminated || runCount !== this._runCount) return; - if (this._binding) { - await this._domWorld.addBindingToContext(context, this._binding.name); + const context = await this.#domWorld.executionContext(); + if (this.#terminated || runCount !== this.#runCount) return; + if (this.#binding) { + await this.#domWorld._addBindingToContext(context, this.#binding.name); } - if (this._terminated || runCount !== this._runCount) return; + if (this.#terminated || runCount !== this.#runCount) return; try { success = await context.evaluateHandle( waitForPredicatePageFunction, - this._root || null, - this._predicateBody, - this._predicateAcceptsContextElement, - this._polling, - this._timeout, - ...this._args + this.#root || null, + this.#predicateBody, + this.#predicateAcceptsContextElement, + this.#polling, + this.#timeout, + ...this.#args ); } catch (error_) { error = error_ as Error; } - if (this._terminated || runCount !== this._runCount) { + if (this.#terminated || runCount !== this.#runCount) { if (success) await success.dispose(); return; } @@ -925,7 +945,7 @@ export class WaitTask { // throw an error - ignore this predicate run altogether. if ( !error && - (await this._domWorld.evaluate((s) => !s, success).catch(() => true)) + (await this.#domWorld.evaluate((s) => !s, success).catch(() => true)) ) { if (!success) throw new Error('Assertion: result handle is not available'); @@ -959,18 +979,18 @@ export class WaitTask { if (error.message.includes('Cannot find context with specified id')) return; - this._reject(error); + this.#reject(error); } else { if (!success) throw new Error('Assertion: result handle is not available'); - this._resolve(success); + this.#resolve(success); } - this._cleanup(); + this.#cleanup(); } - _cleanup(): void { - this._timeoutTimer !== undefined && clearTimeout(this._timeoutTimer); - this._domWorld._waitTasks.delete(this); + #cleanup(): void { + this.#timeoutTimer !== undefined && clearTimeout(this.#timeoutTimer); + this.#domWorld._waitTasks.delete(this); } } diff --git a/src/common/DeviceDescriptors.ts b/src/common/DeviceDescriptors.ts index 3d03cf61..75a4a65d 100644 --- a/src/common/DeviceDescriptors.ts +++ b/src/common/DeviceDescriptors.ts @@ -1537,6 +1537,8 @@ export type DevicesMap = { /** * @internal */ -export const devicesMap: DevicesMap = {}; +export const _devicesMap: DevicesMap = {}; -for (const device of devices) devicesMap[device.name] = device; +for (const device of devices) { + _devicesMap[device.name] = device; +} diff --git a/src/common/Dialog.ts b/src/common/Dialog.ts index 904e9480..0c235684 100644 --- a/src/common/Dialog.ts +++ b/src/common/Dialog.ts @@ -41,11 +41,11 @@ import { Protocol } from 'devtools-protocol'; * @public */ export class Dialog { - private _client: CDPSession; - private _type: Protocol.Page.DialogType; - private _message: string; - private _defaultValue: string; - private _handled = false; + #client: CDPSession; + #type: Protocol.Page.DialogType; + #message: string; + #defaultValue: string; + #handled = false; /** * @internal @@ -56,24 +56,24 @@ export class Dialog { message: string, defaultValue = '' ) { - this._client = client; - this._type = type; - this._message = message; - this._defaultValue = defaultValue; + this.#client = client; + this.#type = type; + this.#message = message; + this.#defaultValue = defaultValue; } /** * @returns The type of the dialog. */ type(): Protocol.Page.DialogType { - return this._type; + return this.#type; } /** * @returns The message displayed in the dialog. */ message(): string { - return this._message; + return this.#message; } /** @@ -81,7 +81,7 @@ export class Dialog { * is not a `prompt`. */ defaultValue(): string { - return this._defaultValue; + return this.#defaultValue; } /** @@ -91,9 +91,9 @@ export class Dialog { * @returns A promise that resolves when the dialog has been accepted. */ async accept(promptText?: string): Promise { - assert(!this._handled, 'Cannot accept dialog which is already handled!'); - this._handled = true; - await this._client.send('Page.handleJavaScriptDialog', { + assert(!this.#handled, 'Cannot accept dialog which is already handled!'); + this.#handled = true; + await this.#client.send('Page.handleJavaScriptDialog', { accept: true, promptText: promptText, }); @@ -103,9 +103,9 @@ export class Dialog { * @returns A promise which will resolve once the dialog has been dismissed */ async dismiss(): Promise { - assert(!this._handled, 'Cannot dismiss dialog which is already handled!'); - this._handled = true; - await this._client.send('Page.handleJavaScriptDialog', { + assert(!this.#handled, 'Cannot dismiss dialog which is already handled!'); + this.#handled = true; + await this.#client.send('Page.handleJavaScriptDialog', { accept: false, }); } diff --git a/src/common/EmulationManager.ts b/src/common/EmulationManager.ts index baae0b5c..a1e6d4fb 100644 --- a/src/common/EmulationManager.ts +++ b/src/common/EmulationManager.ts @@ -18,12 +18,12 @@ import { Viewport } from './PuppeteerViewport.js'; import { Protocol } from 'devtools-protocol'; export class EmulationManager { - _client: CDPSession; - _emulatingMobile = false; - _hasTouch = false; + #client: CDPSession; + #emulatingMobile = false; + #hasTouch = false; constructor(client: CDPSession) { - this._client = client; + this.#client = client; } async emulateViewport(viewport: Viewport): Promise { @@ -38,22 +38,22 @@ export class EmulationManager { const hasTouch = viewport.hasTouch || false; await Promise.all([ - this._client.send('Emulation.setDeviceMetricsOverride', { + this.#client.send('Emulation.setDeviceMetricsOverride', { mobile, width, height, deviceScaleFactor, screenOrientation, }), - this._client.send('Emulation.setTouchEmulationEnabled', { + this.#client.send('Emulation.setTouchEmulationEnabled', { enabled: hasTouch, }), ]); const reloadNeeded = - this._emulatingMobile !== mobile || this._hasTouch !== hasTouch; - this._emulatingMobile = mobile; - this._hasTouch = hasTouch; + this.#emulatingMobile !== mobile || this.#hasTouch !== hasTouch; + this.#emulatingMobile = mobile; + this.#hasTouch = hasTouch; return reloadNeeded; } } diff --git a/src/common/ExecutionContext.ts b/src/common/ExecutionContext.ts index 3cddaa2e..85a1b283 100644 --- a/src/common/ExecutionContext.ts +++ b/src/common/ExecutionContext.ts @@ -16,7 +16,7 @@ import { assert } from './assert.js'; import { helper } from './helper.js'; -import { createJSHandle, JSHandle, ElementHandle } from './JSHandle.js'; +import { _createJSHandle, JSHandle, ElementHandle } from './JSHandle.js'; import { CDPSession } from './Connection.js'; import { DOMWorld } from './DOMWorld.js'; import { Frame } from './FrameManager.js'; @@ -137,11 +137,7 @@ export class ExecutionContext { pageFunction: Function | string, ...args: unknown[] ): Promise { - return await this._evaluateInternal( - true, - pageFunction, - ...args - ); + return await this.#evaluate(true, pageFunction, ...args); } /** @@ -190,10 +186,10 @@ export class ExecutionContext { pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[] ): Promise { - return this._evaluateInternal(false, pageFunction, ...args); + return this.#evaluate(false, pageFunction, ...args); } - private async _evaluateInternal( + async #evaluate( returnByValue: boolean, pageFunction: Function | string, ...args: unknown[] @@ -224,7 +220,7 @@ export class ExecutionContext { return returnByValue ? helper.valueFromRemoteObject(remoteObject) - : createJSHandle(this, remoteObject); + : _createJSHandle(this, remoteObject); } if (typeof pageFunction !== 'function') @@ -263,8 +259,9 @@ export class ExecutionContext { if ( error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON') - ) - error.message += ' Are you passing a nested JSHandle?'; + ) { + error.message += ' Recursive objects are not allowed.'; + } throw error; } const { exceptionDetails, result: remoteObject } = @@ -275,7 +272,7 @@ export class ExecutionContext { ); return returnByValue ? helper.valueFromRemoteObject(remoteObject) - : createJSHandle(this, remoteObject); + : _createJSHandle(this, remoteObject); function convertArgument( this: ExecutionContext, @@ -355,7 +352,7 @@ export class ExecutionContext { const response = await this._client.send('Runtime.queryObjects', { prototypeObjectId: prototypeHandle._remoteObject.objectId, }); - return createJSHandle(this, response.objects); + return _createJSHandle(this, response.objects); } /** @@ -368,7 +365,7 @@ export class ExecutionContext { backendNodeId: backendNodeId, executionContextId: this._contextId, }); - return createJSHandle(this, object) as ElementHandle; + return _createJSHandle(this, object) as ElementHandle; } /** diff --git a/src/common/FileChooser.ts b/src/common/FileChooser.ts index d1cbea73..daf588ee 100644 --- a/src/common/FileChooser.ts +++ b/src/common/FileChooser.ts @@ -37,9 +37,9 @@ import { assert } from './assert.js'; * @public */ export class FileChooser { - private _element: ElementHandle; - private _multiple: boolean; - private _handled = false; + #element: ElementHandle; + #multiple: boolean; + #handled = false; /** * @internal @@ -48,15 +48,15 @@ export class FileChooser { element: ElementHandle, event: Protocol.Page.FileChooserOpenedEvent ) { - this._element = element; - this._multiple = event.mode !== 'selectSingle'; + this.#element = element; + this.#multiple = event.mode !== 'selectSingle'; } /** * Whether file chooser allow for {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple | multiple} file selection. */ isMultiple(): boolean { - return this._multiple; + return this.#multiple; } /** @@ -66,11 +66,11 @@ export class FileChooser { */ async accept(filePaths: string[]): Promise { assert( - !this._handled, + !this.#handled, 'Cannot accept FileChooser which is already handled!' ); - this._handled = true; - await this._element.uploadFile(...filePaths); + this.#handled = true; + await this.#element.uploadFile(...filePaths); } /** @@ -78,9 +78,9 @@ export class FileChooser { */ cancel(): void { assert( - !this._handled, + !this.#handled, 'Cannot cancel FileChooser which is already handled!' ); - this._handled = true; + this.#handled = true; } } diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts index 833568ef..378f8b99 100644 --- a/src/common/FrameManager.ts +++ b/src/common/FrameManager.ts @@ -66,14 +66,28 @@ export const FrameManagerEmittedEvents = { * @internal */ export class FrameManager extends EventEmitter { - _client: CDPSession; - private _page: Page; - private _networkManager: NetworkManager; - _timeoutSettings: TimeoutSettings; - private _frames = new Map(); - private _contextIdToContext = new Map(); - private _isolatedWorlds = new Set(); - private _mainFrame?: Frame; + #page: Page; + #networkManager: NetworkManager; + #timeoutSettings: TimeoutSettings; + #frames = new Map(); + #contextIdToContext = new Map(); + #isolatedWorlds = new Set(); + #mainFrame?: Frame; + #client: CDPSession; + + /** + * @internal + */ + get _timeoutSettings(): TimeoutSettings { + return this.#timeoutSettings; + } + + /** + * @internal + */ + get _client(): CDPSession { + return this.#client; + } constructor( client: CDPSession, @@ -82,64 +96,64 @@ export class FrameManager extends EventEmitter { timeoutSettings: TimeoutSettings ) { super(); - this._client = client; - this._page = page; - this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this); - this._timeoutSettings = timeoutSettings; - this.setupEventListeners(this._client); + this.#client = client; + this.#page = page; + this.#networkManager = new NetworkManager(client, ignoreHTTPSErrors, this); + this.#timeoutSettings = timeoutSettings; + this.setupEventListeners(this.#client); } private setupEventListeners(session: CDPSession) { session.on('Page.frameAttached', (event) => { - this._onFrameAttached(session, event.frameId, event.parentFrameId); + this.#onFrameAttached(session, event.frameId, event.parentFrameId); }); session.on('Page.frameNavigated', (event) => { - this._onFrameNavigated(event.frame); + this.#onFrameNavigated(event.frame); }); session.on('Page.navigatedWithinDocument', (event) => { - this._onFrameNavigatedWithinDocument(event.frameId, event.url); + this.#onFrameNavigatedWithinDocument(event.frameId, event.url); }); session.on( 'Page.frameDetached', (event: Protocol.Page.FrameDetachedEvent) => { - this._onFrameDetached( + this.#onFrameDetached( event.frameId, event.reason as Protocol.Page.FrameDetachedEventReason ); } ); session.on('Page.frameStartedLoading', (event) => { - this._onFrameStartedLoading(event.frameId); + this.#onFrameStartedLoading(event.frameId); }); session.on('Page.frameStoppedLoading', (event) => { - this._onFrameStoppedLoading(event.frameId); + this.#onFrameStoppedLoading(event.frameId); }); session.on('Runtime.executionContextCreated', (event) => { - this._onExecutionContextCreated(event.context, session); + this.#onExecutionContextCreated(event.context, session); }); session.on('Runtime.executionContextDestroyed', (event) => { - this._onExecutionContextDestroyed(event.executionContextId, session); + this.#onExecutionContextDestroyed(event.executionContextId, session); }); session.on('Runtime.executionContextsCleared', () => { - this._onExecutionContextsCleared(session); + this.#onExecutionContextsCleared(session); }); session.on('Page.lifecycleEvent', (event) => { - this._onLifecycleEvent(event); + this.#onLifecycleEvent(event); }); session.on('Target.attachedToTarget', async (event) => { - this._onAttachedToTarget(event); + this.#onAttachedToTarget(event); }); session.on('Target.detachedFromTarget', async (event) => { - this._onDetachedFromTarget(event); + this.#onDetachedFromTarget(event); }); } - async initialize(client: CDPSession = this._client): Promise { + async initialize(client: CDPSession = this.#client): Promise { try { const result = await Promise.all([ client.send('Page.enable'), client.send('Page.getFrameTree'), - client !== this._client + client !== this.#client ? client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: false, @@ -149,15 +163,15 @@ export class FrameManager extends EventEmitter { ]); const { frameTree } = result[1]; - this._handleFrameTree(client, frameTree); + this.#handleFrameTree(client, frameTree); await Promise.all([ client.send('Page.setLifecycleEventsEnabled', { enabled: true }), client .send('Runtime.enable') .then(() => this._ensureIsolatedWorld(client, UTILITY_WORLD_NAME)), // TODO: Network manager is not aware of OOP iframes yet. - client === this._client - ? this._networkManager.initialize() + client === this.#client + ? this.#networkManager.initialize() : Promise.resolve(), ]); } catch (error) { @@ -175,7 +189,7 @@ export class FrameManager extends EventEmitter { } networkManager(): NetworkManager { - return this._networkManager; + return this.#networkManager; } async navigateFrame( @@ -189,14 +203,14 @@ export class FrameManager extends EventEmitter { ): Promise { assertNoLegacyNavigationOptions(options); const { - referer = this._networkManager.extraHTTPHeaders()['referer'], + referer = this.#networkManager.extraHTTPHeaders()['referer'], waitUntil = ['load'], - timeout = this._timeoutSettings.navigationTimeout(), + timeout = this.#timeoutSettings.navigationTimeout(), } = options; const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout); let error = await Promise.race([ - navigate(this._client, url, referer, frame._id), + navigate(this.#client, url, referer, frame._id), watcher.timeoutOrTerminationPromise(), ]); if (!error) { @@ -244,7 +258,7 @@ export class FrameManager extends EventEmitter { assertNoLegacyNavigationOptions(options); const { waitUntil = ['load'], - timeout = this._timeoutSettings.navigationTimeout(), + timeout = this.#timeoutSettings.navigationTimeout(), } = options; const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout); const error = await Promise.race([ @@ -257,15 +271,13 @@ export class FrameManager extends EventEmitter { return await watcher.navigationResponse(); } - private async _onAttachedToTarget( - event: Protocol.Target.AttachedToTargetEvent - ) { + async #onAttachedToTarget(event: Protocol.Target.AttachedToTargetEvent) { if (event.targetInfo.type !== 'iframe') { return; } - const frame = this._frames.get(event.targetInfo.targetId); - const connection = Connection.fromSession(this._client); + const frame = this.#frames.get(event.targetInfo.targetId); + const connection = Connection.fromSession(this.#client); assert(connection); const session = connection.session(event.sessionId); assert(session); @@ -274,81 +286,79 @@ export class FrameManager extends EventEmitter { await this.initialize(session); } - private async _onDetachedFromTarget( - event: Protocol.Target.DetachedFromTargetEvent - ) { + async #onDetachedFromTarget(event: Protocol.Target.DetachedFromTargetEvent) { if (!event.targetId) return; - const frame = this._frames.get(event.targetId); + const frame = this.#frames.get(event.targetId); if (frame && frame.isOOPFrame()) { // When an OOP iframe is removed from the page, it // will only get a Target.detachedFromTarget event. - this._removeFramesRecursively(frame); + this.#removeFramesRecursively(frame); } } - _onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void { - const frame = this._frames.get(event.frameId); + #onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void { + const frame = this.#frames.get(event.frameId); if (!frame) return; frame._onLifecycleEvent(event.loaderId, event.name); this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame); } - _onFrameStartedLoading(frameId: string): void { - const frame = this._frames.get(frameId); + #onFrameStartedLoading(frameId: string): void { + const frame = this.#frames.get(frameId); if (!frame) return; frame._onLoadingStarted(); } - _onFrameStoppedLoading(frameId: string): void { - const frame = this._frames.get(frameId); + #onFrameStoppedLoading(frameId: string): void { + const frame = this.#frames.get(frameId); if (!frame) return; frame._onLoadingStopped(); this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame); } - _handleFrameTree( + #handleFrameTree( session: CDPSession, frameTree: Protocol.Page.FrameTree ): void { if (frameTree.frame.parentId) { - this._onFrameAttached( + this.#onFrameAttached( session, frameTree.frame.id, frameTree.frame.parentId ); } - this._onFrameNavigated(frameTree.frame); + this.#onFrameNavigated(frameTree.frame); if (!frameTree.childFrames) return; for (const child of frameTree.childFrames) { - this._handleFrameTree(session, child); + this.#handleFrameTree(session, child); } } page(): Page { - return this._page; + return this.#page; } mainFrame(): Frame { - assert(this._mainFrame, 'Requesting main frame too early!'); - return this._mainFrame; + assert(this.#mainFrame, 'Requesting main frame too early!'); + return this.#mainFrame; } frames(): Frame[] { - return Array.from(this._frames.values()); + return Array.from(this.#frames.values()); } frame(frameId: string): Frame | null { - return this._frames.get(frameId) || null; + return this.#frames.get(frameId) || null; } - _onFrameAttached( + #onFrameAttached( session: CDPSession, frameId: string, parentFrameId?: string ): void { - if (this._frames.has(frameId)) { - const frame = this._frames.get(frameId)!; + if (this.#frames.has(frameId)) { + const frame = this.#frames.get(frameId)!; if (session && frame.isOOPFrame()) { // If an OOP iframes becomes a normal iframe again // it is first attached to the parent page before @@ -358,18 +368,18 @@ export class FrameManager extends EventEmitter { return; } assert(parentFrameId); - const parentFrame = this._frames.get(parentFrameId); + const parentFrame = this.#frames.get(parentFrameId); assert(parentFrame); const frame = new Frame(this, parentFrame, frameId, session); - this._frames.set(frame._id, frame); + this.#frames.set(frame._id, frame); this.emit(FrameManagerEmittedEvents.FrameAttached, frame); } - _onFrameNavigated(framePayload: Protocol.Page.Frame): void { + #onFrameNavigated(framePayload: Protocol.Page.Frame): void { const isMainFrame = !framePayload.parentId; let frame = isMainFrame - ? this._mainFrame - : this._frames.get(framePayload.id); + ? this.#mainFrame + : this.#frames.get(framePayload.id); assert( isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame' @@ -378,21 +388,21 @@ export class FrameManager extends EventEmitter { // Detach all child frames first. if (frame) { for (const child of frame.childFrames()) - this._removeFramesRecursively(child); + this.#removeFramesRecursively(child); } // Update or create main frame. if (isMainFrame) { if (frame) { // Update frame id to retain frame identity on cross-process navigation. - this._frames.delete(frame._id); + this.#frames.delete(frame._id); frame._id = framePayload.id; } else { // Initial main frame navigation. - frame = new Frame(this, null, framePayload.id, this._client); + frame = new Frame(this, null, framePayload.id, this.#client); } - this._frames.set(framePayload.id, frame); - this._mainFrame = frame; + this.#frames.set(framePayload.id, frame); + this.#mainFrame = frame; } // Update frame payload. @@ -404,8 +414,8 @@ export class FrameManager extends EventEmitter { async _ensureIsolatedWorld(session: CDPSession, name: string): Promise { const key = `${session.id()}:${name}`; - if (this._isolatedWorlds.has(key)) return; - this._isolatedWorlds.add(key); + if (this.#isolatedWorlds.has(key)) return; + this.#isolatedWorlds.add(key); await session.send('Page.addScriptToEvaluateOnNewDocument', { source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`, @@ -414,7 +424,7 @@ export class FrameManager extends EventEmitter { // Frames might be removed before we send this. await Promise.all( this.frames() - .filter((frame) => frame._client === session) + .filter((frame) => frame._client() === session) .map((frame) => session .send('Page.createIsolatedWorld', { @@ -427,41 +437,41 @@ export class FrameManager extends EventEmitter { ); } - _onFrameNavigatedWithinDocument(frameId: string, url: string): void { - const frame = this._frames.get(frameId); + #onFrameNavigatedWithinDocument(frameId: string, url: string): void { + const frame = this.#frames.get(frameId); if (!frame) return; frame._navigatedWithinDocument(url); this.emit(FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame); this.emit(FrameManagerEmittedEvents.FrameNavigated, frame); } - _onFrameDetached( + #onFrameDetached( frameId: string, reason: Protocol.Page.FrameDetachedEventReason ): void { - const frame = this._frames.get(frameId); + const frame = this.#frames.get(frameId); if (reason === 'remove') { // Only remove the frame if the reason for the detached event is // an actual removement of the frame. // For frames that become OOP iframes, the reason would be 'swap'. - if (frame) this._removeFramesRecursively(frame); + if (frame) this.#removeFramesRecursively(frame); } else if (reason === 'swap') { this.emit(FrameManagerEmittedEvents.FrameSwapped, frame); } } - _onExecutionContextCreated( + #onExecutionContextCreated( contextPayload: Protocol.Runtime.ExecutionContextDescription, session: CDPSession ): void { const auxData = contextPayload.auxData as { frameId?: string } | undefined; const frameId = auxData && auxData.frameId; const frame = - typeof frameId === 'string' ? this._frames.get(frameId) : undefined; + typeof frameId === 'string' ? this.#frames.get(frameId) : undefined; let world: DOMWorld | undefined; if (frame) { // Only care about execution contexts created for the current session. - if (frame._client !== session) return; + if (frame._client() !== session) return; if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) { world = frame._mainWorld; @@ -476,51 +486,51 @@ export class FrameManager extends EventEmitter { } } const context = new ExecutionContext( - frame?._client || this._client, + frame?._client() || this.#client, contextPayload, world ); if (world) world._setContext(context); const key = `${session.id()}:${contextPayload.id}`; - this._contextIdToContext.set(key, context); + this.#contextIdToContext.set(key, context); } - private _onExecutionContextDestroyed( + #onExecutionContextDestroyed( executionContextId: number, session: CDPSession ): void { const key = `${session.id()}:${executionContextId}`; - const context = this._contextIdToContext.get(key); + const context = this.#contextIdToContext.get(key); if (!context) return; - this._contextIdToContext.delete(key); + this.#contextIdToContext.delete(key); if (context._world) context._world._setContext(null); } - private _onExecutionContextsCleared(session: CDPSession): void { - for (const [key, context] of this._contextIdToContext.entries()) { + #onExecutionContextsCleared(session: CDPSession): void { + for (const [key, context] of this.#contextIdToContext.entries()) { // Make sure to only clear execution contexts that belong // to the current session. if (context._client !== session) continue; if (context._world) context._world._setContext(null); - this._contextIdToContext.delete(key); + this.#contextIdToContext.delete(key); } } executionContextById( contextId: number, - session: CDPSession = this._client + session: CDPSession = this.#client ): ExecutionContext { const key = `${session.id()}:${contextId}`; - const context = this._contextIdToContext.get(key); + const context = this.#contextIdToContext.get(key); assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId); return context; } - private _removeFramesRecursively(frame: Frame): void { + #removeFramesRecursively(frame: Frame): void { for (const child of frame.childFrames()) - this._removeFramesRecursively(child); + this.#removeFramesRecursively(child); frame._detach(); - this._frames.delete(frame._id); + this.#frames.delete(frame._id); this.emit(FrameManagerEmittedEvents.FrameDetached, frame); } } @@ -646,18 +656,19 @@ export interface FrameAddStyleTagOptions { * @public */ export class Frame { + #parentFrame: Frame | null; + #url = ''; + #detached = false; + #client!: CDPSession; + /** * @internal */ _frameManager: FrameManager; - private _parentFrame: Frame | null; /** * @internal */ _id: string; - - private _url = ''; - private _detached = false; /** * @internal */ @@ -670,7 +681,6 @@ export class Frame { * @internal */ _hasStartedLoading = false; - /** * @internal */ @@ -687,10 +697,6 @@ export class Frame { * @internal */ _childFrames: Set; - /** - * @internal - */ - _client!: CDPSession; /** * @internal @@ -702,15 +708,15 @@ export class Frame { client: CDPSession ) { this._frameManager = frameManager; - this._parentFrame = parentFrame ?? null; - this._url = ''; + this.#parentFrame = parentFrame ?? null; + this.#url = ''; this._id = frameId; - this._detached = false; + this.#detached = false; this._loaderId = ''; this._childFrames = new Set(); - if (this._parentFrame) this._parentFrame._childFrames.add(this); + if (this.#parentFrame) this.#parentFrame._childFrames.add(this); this._updateClient(client); } @@ -719,15 +725,15 @@ export class Frame { * @internal */ _updateClient(client: CDPSession): void { - this._client = client; + this.#client = client; this._mainWorld = new DOMWorld( - this._client, + this.#client, this._frameManager, this, this._frameManager._timeoutSettings ); this._secondaryWorld = new DOMWorld( - this._client, + this.#client, this._frameManager, this, this._frameManager._timeoutSettings @@ -740,7 +746,7 @@ export class Frame { * @returns `true` if the frame is an OOP frame, or `false` otherwise. */ isOOPFrame(): boolean { - return this._client !== this._frameManager._client; + return this.#client !== this._frameManager._client; } /** @@ -825,8 +831,8 @@ export class Frame { /** * @internal */ - client(): CDPSession { - return this._client; + _client(): CDPSession { + return this.#client; } /** @@ -1008,14 +1014,14 @@ export class Frame { * @returns the frame's URL. */ url(): string { - return this._url; + return this.#url; } /** * @returns the parent `Frame`, if any. Detached and main frames return `null`. */ parentFrame(): Frame | null { - return this._parentFrame; + return this.#parentFrame; } /** @@ -1029,7 +1035,7 @@ export class Frame { * @returns `true` if the frame has been detached, or `false` otherwise. */ isDetached(): boolean { - return this._detached; + return this.#detached; } /** @@ -1407,14 +1413,14 @@ export class Frame { */ _navigated(framePayload: Protocol.Page.Frame): void { this._name = framePayload.name; - this._url = `${framePayload.url}${framePayload.urlFragment || ''}`; + this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`; } /** * @internal */ _navigatedWithinDocument(url: string): void { - this._url = url; + this.#url = url; } /** @@ -1447,11 +1453,11 @@ export class Frame { * @internal */ _detach(): void { - this._detached = true; + this.#detached = true; this._mainWorld._detach(); this._secondaryWorld._detach(); - if (this._parentFrame) this._parentFrame._childFrames.delete(this); - this._parentFrame = null; + if (this.#parentFrame) this.#parentFrame._childFrames.delete(this); + this.#parentFrame = null; } } diff --git a/src/common/HTTPRequest.ts b/src/common/HTTPRequest.ts index bbec19b8..5d970874 100644 --- a/src/common/HTTPRequest.ts +++ b/src/common/HTTPRequest.ts @@ -138,25 +138,25 @@ export class HTTPRequest { */ _redirectChain: HTTPRequest[]; - private _client: CDPSession; - private _isNavigationRequest: boolean; - private _allowInterception: boolean; - private _interceptionHandled = false; - private _url: string; - private _resourceType: ResourceType; + #client: CDPSession; + #isNavigationRequest: boolean; + #allowInterception: boolean; + #interceptionHandled = false; + #url: string; + #resourceType: ResourceType; - private _method: string; - private _postData?: string; - private _headers: Record = {}; - private _frame: Frame | null; - private _continueRequestOverrides: ContinueRequestOverrides; - private _responseForRequest: Partial | null = null; - private _abortErrorReason: Protocol.Network.ErrorReason | null = null; - private _interceptResolutionState: InterceptResolutionState = { + #method: string; + #postData?: string; + #headers: Record = {}; + #frame: Frame | null; + #continueRequestOverrides: ContinueRequestOverrides; + #responseForRequest: Partial | null = null; + #abortErrorReason: Protocol.Network.ErrorReason | null = null; + #interceptResolutionState: InterceptResolutionState = { action: InterceptResolutionAction.None, }; - private _interceptHandlers: Array<() => void | PromiseLike>; - private _initiator: Protocol.Network.Initiator; + #interceptHandlers: Array<() => void | PromiseLike>; + #initiator: Protocol.Network.Initiator; /** * @internal @@ -169,31 +169,31 @@ export class HTTPRequest { event: Protocol.Network.RequestWillBeSentEvent, redirectChain: HTTPRequest[] ) { - this._client = client; + this.#client = client; this._requestId = event.requestId; - this._isNavigationRequest = + this.#isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document'; this._interceptionId = interceptionId; - this._allowInterception = allowInterception; - this._url = event.request.url; - this._resourceType = (event.type || 'other').toLowerCase() as ResourceType; - this._method = event.request.method; - this._postData = event.request.postData; - this._frame = frame; + this.#allowInterception = allowInterception; + this.#url = event.request.url; + this.#resourceType = (event.type || 'other').toLowerCase() as ResourceType; + this.#method = event.request.method; + this.#postData = event.request.postData; + this.#frame = frame; this._redirectChain = redirectChain; - this._continueRequestOverrides = {}; - this._interceptHandlers = []; - this._initiator = event.initiator; + this.#continueRequestOverrides = {}; + this.#interceptHandlers = []; + this.#initiator = event.initiator; for (const [key, value] of Object.entries(event.request.headers)) - this._headers[key.toLowerCase()] = value; + this.#headers[key.toLowerCase()] = value; } /** * @returns the URL of the request */ url(): string { - return this._url; + return this.#url; } /** @@ -202,8 +202,8 @@ export class HTTPRequest { * `respond()` aren't called). */ continueRequestOverrides(): ContinueRequestOverrides { - assert(this._allowInterception, 'Request Interception is not enabled!'); - return this._continueRequestOverrides; + assert(this.#allowInterception, 'Request Interception is not enabled!'); + return this.#continueRequestOverrides; } /** @@ -211,16 +211,16 @@ export class HTTPRequest { * interception is allowed to respond (ie, `abort()` is not called). */ responseForRequest(): Partial | null { - assert(this._allowInterception, 'Request Interception is not enabled!'); - return this._responseForRequest; + assert(this.#allowInterception, 'Request Interception is not enabled!'); + return this.#responseForRequest; } /** * @returns the most recent reason for aborting the request */ abortErrorReason(): Protocol.Network.ErrorReason | null { - assert(this._allowInterception, 'Request Interception is not enabled!'); - return this._abortErrorReason; + assert(this.#allowInterception, 'Request Interception is not enabled!'); + return this.#abortErrorReason; } /** @@ -235,11 +235,11 @@ export class HTTPRequest { * `disabled`, `none`, or `already-handled`. */ interceptResolutionState(): InterceptResolutionState { - if (!this._allowInterception) + if (!this.#allowInterception) return { action: InterceptResolutionAction.Disabled }; - if (this._interceptionHandled) + if (this.#interceptionHandled) return { action: InterceptResolutionAction.AlreadyHandled }; - return { ...this._interceptResolutionState }; + return { ...this.#interceptResolutionState }; } /** @@ -247,7 +247,7 @@ export class HTTPRequest { * `false` otherwise. */ isInterceptResolutionHandled(): boolean { - return this._interceptionHandled; + return this.#interceptionHandled; } /** @@ -259,7 +259,7 @@ export class HTTPRequest { enqueueInterceptAction( pendingHandler: () => void | PromiseLike ): void { - this._interceptHandlers.push(pendingHandler); + this.#interceptHandlers.push(pendingHandler); } /** @@ -267,21 +267,21 @@ export class HTTPRequest { * the request interception. */ async finalizeInterceptions(): Promise { - await this._interceptHandlers.reduce( + await this.#interceptHandlers.reduce( (promiseChain, interceptAction) => promiseChain.then(interceptAction), Promise.resolve() ); const { action } = this.interceptResolutionState(); switch (action) { case 'abort': - return this._abort(this._abortErrorReason); + return this.#abort(this.#abortErrorReason); case 'respond': - if (this._responseForRequest === null) { + if (this.#responseForRequest === null) { throw new Error('Response is missing for the interception'); } - return this._respond(this._responseForRequest); + return this.#respond(this.#responseForRequest); case 'continue': - return this._continue(this._continueRequestOverrides); + return this.#continue(this.#continueRequestOverrides); } } @@ -290,21 +290,21 @@ export class HTTPRequest { * engine. */ resourceType(): ResourceType { - return this._resourceType; + return this.#resourceType; } /** * @returns the method used (`GET`, `POST`, etc.) */ method(): string { - return this._method; + return this.#method; } /** * @returns the request's post body, if any. */ postData(): string | undefined { - return this._postData; + return this.#postData; } /** @@ -312,7 +312,7 @@ export class HTTPRequest { * header names are lower-case. */ headers(): Record { - return this._headers; + return this.#headers; } /** @@ -328,21 +328,21 @@ export class HTTPRequest { * error pages. */ frame(): Frame | null { - return this._frame; + return this.#frame; } /** * @returns true if the request is the driver of the current frame's navigation. */ isNavigationRequest(): boolean { - return this._isNavigationRequest; + return this.#isNavigationRequest; } /** * @returns the initiator of the request. */ initiator(): Protocol.Network.Initiator { - return this._initiator; + return this.#initiator; } /** @@ -436,41 +436,39 @@ export class HTTPRequest { priority?: number ): Promise { // Request interception is not supported for data: urls. - if (this._url.startsWith('data:')) return; - assert(this._allowInterception, 'Request Interception is not enabled!'); - assert(!this._interceptionHandled, 'Request is already handled!'); + if (this.#url.startsWith('data:')) return; + assert(this.#allowInterception, 'Request Interception is not enabled!'); + assert(!this.#interceptionHandled, 'Request is already handled!'); if (priority === undefined) { - return this._continue(overrides); + return this.#continue(overrides); } - this._continueRequestOverrides = overrides; + this.#continueRequestOverrides = overrides; if ( - this._interceptResolutionState.priority === undefined || - priority > this._interceptResolutionState.priority + this.#interceptResolutionState.priority === undefined || + priority > this.#interceptResolutionState.priority ) { - this._interceptResolutionState = { + this.#interceptResolutionState = { action: InterceptResolutionAction.Continue, priority, }; return; } - if (priority === this._interceptResolutionState.priority) { + if (priority === this.#interceptResolutionState.priority) { if ( - this._interceptResolutionState.action === 'abort' || - this._interceptResolutionState.action === 'respond' + this.#interceptResolutionState.action === 'abort' || + this.#interceptResolutionState.action === 'respond' ) { return; } - this._interceptResolutionState.action = + this.#interceptResolutionState.action = InterceptResolutionAction.Continue; } return; } - private async _continue( - overrides: ContinueRequestOverrides = {} - ): Promise { + async #continue(overrides: ContinueRequestOverrides = {}): Promise { const { url, method, postData, headers } = overrides; - this._interceptionHandled = true; + this.#interceptionHandled = true; const postDataBinaryBase64 = postData ? Buffer.from(postData).toString('base64') @@ -480,7 +478,7 @@ export class HTTPRequest { throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.continueRequest' ); - await this._client + await this.#client .send('Fetch.continueRequest', { requestId: this._interceptionId, url, @@ -489,7 +487,7 @@ export class HTTPRequest { headers: headers ? headersArray(headers) : undefined, }) .catch((error) => { - this._interceptionHandled = false; + this.#interceptionHandled = false; return handleError(error); }); } @@ -530,33 +528,33 @@ export class HTTPRequest { priority?: number ): Promise { // Mocking responses for dataURL requests is not currently supported. - if (this._url.startsWith('data:')) return; - assert(this._allowInterception, 'Request Interception is not enabled!'); - assert(!this._interceptionHandled, 'Request is already handled!'); + if (this.#url.startsWith('data:')) return; + assert(this.#allowInterception, 'Request Interception is not enabled!'); + assert(!this.#interceptionHandled, 'Request is already handled!'); if (priority === undefined) { - return this._respond(response); + return this.#respond(response); } - this._responseForRequest = response; + this.#responseForRequest = response; if ( - this._interceptResolutionState.priority === undefined || - priority > this._interceptResolutionState.priority + this.#interceptResolutionState.priority === undefined || + priority > this.#interceptResolutionState.priority ) { - this._interceptResolutionState = { + this.#interceptResolutionState = { action: InterceptResolutionAction.Respond, priority, }; return; } - if (priority === this._interceptResolutionState.priority) { - if (this._interceptResolutionState.action === 'abort') { + if (priority === this.#interceptResolutionState.priority) { + if (this.#interceptResolutionState.action === 'abort') { return; } - this._interceptResolutionState.action = InterceptResolutionAction.Respond; + this.#interceptResolutionState.action = InterceptResolutionAction.Respond; } } - private async _respond(response: Partial): Promise { - this._interceptionHandled = true; + async #respond(response: Partial): Promise { + this.#interceptionHandled = true; const responseBody: Buffer | null = response.body && helper.isString(response.body) @@ -585,7 +583,7 @@ export class HTTPRequest { throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest' ); - await this._client + await this.#client .send('Fetch.fulfillRequest', { requestId: this._interceptionId, responseCode: status, @@ -594,7 +592,7 @@ export class HTTPRequest { body: responseBody ? responseBody.toString('base64') : undefined, }) .catch((error) => { - this._interceptionHandled = false; + this.#interceptionHandled = false; return handleError(error); }); } @@ -617,20 +615,20 @@ export class HTTPRequest { priority?: number ): Promise { // Request interception is not supported for data: urls. - if (this._url.startsWith('data:')) return; + if (this.#url.startsWith('data:')) return; const errorReason = errorReasons[errorCode]; assert(errorReason, 'Unknown error code: ' + errorCode); - assert(this._allowInterception, 'Request Interception is not enabled!'); - assert(!this._interceptionHandled, 'Request is already handled!'); + assert(this.#allowInterception, 'Request Interception is not enabled!'); + assert(!this.#interceptionHandled, 'Request is already handled!'); if (priority === undefined) { - return this._abort(errorReason); + return this.#abort(errorReason); } - this._abortErrorReason = errorReason; + this.#abortErrorReason = errorReason; if ( - this._interceptResolutionState.priority === undefined || - priority >= this._interceptResolutionState.priority + this.#interceptResolutionState.priority === undefined || + priority >= this.#interceptResolutionState.priority ) { - this._interceptResolutionState = { + this.#interceptResolutionState = { action: InterceptResolutionAction.Abort, priority, }; @@ -638,15 +636,15 @@ export class HTTPRequest { } } - private async _abort( + async #abort( errorReason: Protocol.Network.ErrorReason | null ): Promise { - this._interceptionHandled = true; + this.#interceptionHandled = true; if (this._interceptionId === undefined) throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.failRequest' ); - await this._client + await this.#client .send('Fetch.failRequest', { requestId: this._interceptionId, errorReason: errorReason || 'Failed', diff --git a/src/common/HTTPResponse.ts b/src/common/HTTPResponse.ts index 69783ab6..8a1784cc 100644 --- a/src/common/HTTPResponse.ts +++ b/src/common/HTTPResponse.ts @@ -44,20 +44,20 @@ interface CDPSession extends EventEmitter { * @public */ export class HTTPResponse { - private _client: CDPSession; - private _request: HTTPRequest; - private _contentPromise: Promise | null = null; - private _bodyLoadedPromise: Promise; - private _bodyLoadedPromiseFulfill: (err: Error | void) => void = () => {}; - private _remoteAddress: RemoteAddress; - private _status: number; - private _statusText: string; - private _url: string; - private _fromDiskCache: boolean; - private _fromServiceWorker: boolean; - private _headers: Record = {}; - private _securityDetails: SecurityDetails | null; - private _timing: Protocol.Network.ResourceTiming | null; + #client: CDPSession; + #request: HTTPRequest; + #contentPromise: Promise | null = null; + #bodyLoadedPromise: Promise; + #bodyLoadedPromiseFulfill: (err: Error | void) => void = () => {}; + #remoteAddress: RemoteAddress; + #status: number; + #statusText: string; + #url: string; + #fromDiskCache: boolean; + #fromServiceWorker: boolean; + #headers: Record = {}; + #securityDetails: SecurityDetails | null; + #timing: Protocol.Network.ResourceTiming | null; /** * @internal @@ -68,40 +68,37 @@ export class HTTPResponse { responsePayload: Protocol.Network.Response, extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null ) { - this._client = client; - this._request = request; + this.#client = client; + this.#request = request; - this._bodyLoadedPromise = new Promise((fulfill) => { - this._bodyLoadedPromiseFulfill = fulfill; + this.#bodyLoadedPromise = new Promise((fulfill) => { + this.#bodyLoadedPromiseFulfill = fulfill; }); - this._remoteAddress = { + this.#remoteAddress = { ip: responsePayload.remoteIPAddress, port: responsePayload.remotePort, }; - this._statusText = - this._parseStatusTextFromExtrInfo(extraInfo) || + this.#statusText = + this.#parseStatusTextFromExtrInfo(extraInfo) || responsePayload.statusText; - this._url = request.url(); - this._fromDiskCache = !!responsePayload.fromDiskCache; - this._fromServiceWorker = !!responsePayload.fromServiceWorker; + this.#url = request.url(); + this.#fromDiskCache = !!responsePayload.fromDiskCache; + this.#fromServiceWorker = !!responsePayload.fromServiceWorker; - this._status = extraInfo ? extraInfo.statusCode : responsePayload.status; + this.#status = extraInfo ? extraInfo.statusCode : responsePayload.status; const headers = extraInfo ? extraInfo.headers : responsePayload.headers; for (const [key, value] of Object.entries(headers)) { - this._headers[key.toLowerCase()] = value; + this.#headers[key.toLowerCase()] = value; } - this._securityDetails = responsePayload.securityDetails + this.#securityDetails = responsePayload.securityDetails ? new SecurityDetails(responsePayload.securityDetails) : null; - this._timing = responsePayload.timing || null; + this.#timing = responsePayload.timing || null; } - /** - * @internal - */ - _parseStatusTextFromExtrInfo( + #parseStatusTextFromExtrInfo( extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null ): string | undefined { if (!extraInfo || !extraInfo.headersText) return; @@ -119,9 +116,9 @@ export class HTTPResponse { */ _resolveBody(err: Error | null): void { if (err) { - return this._bodyLoadedPromiseFulfill(err); + return this.#bodyLoadedPromiseFulfill(err); } - return this._bodyLoadedPromiseFulfill(); + return this.#bodyLoadedPromiseFulfill(); } /** @@ -129,14 +126,14 @@ export class HTTPResponse { * server. */ remoteAddress(): RemoteAddress { - return this._remoteAddress; + return this.#remoteAddress; } /** * @returns The URL of the response. */ url(): string { - return this._url; + return this.#url; } /** @@ -144,14 +141,14 @@ export class HTTPResponse { */ ok(): boolean { // TODO: document === 0 case? - return this._status === 0 || (this._status >= 200 && this._status <= 299); + return this.#status === 0 || (this.#status >= 200 && this.#status <= 299); } /** * @returns The status code of the response (e.g., 200 for a success). */ status(): number { - return this._status; + return this.#status; } /** @@ -159,7 +156,7 @@ export class HTTPResponse { * success). */ statusText(): string { - return this._statusText; + return this.#statusText; } /** @@ -167,7 +164,7 @@ export class HTTPResponse { * header names are lower-case. */ headers(): Record { - return this._headers; + return this.#headers; } /** @@ -175,26 +172,26 @@ export class HTTPResponse { * secure connection, or `null` otherwise. */ securityDetails(): SecurityDetails | null { - return this._securityDetails; + return this.#securityDetails; } /** * @returns Timing information related to the response. */ timing(): Protocol.Network.ResourceTiming | null { - return this._timing; + return this.#timing; } /** * @returns Promise which resolves to a buffer with response body. */ buffer(): Promise { - if (!this._contentPromise) { - this._contentPromise = this._bodyLoadedPromise.then(async (error) => { + if (!this.#contentPromise) { + this.#contentPromise = this.#bodyLoadedPromise.then(async (error) => { if (error) throw error; try { - const response = await this._client.send('Network.getResponseBody', { - requestId: this._request._requestId, + const response = await this.#client.send('Network.getResponseBody', { + requestId: this.#request._requestId, }); return Buffer.from( response.body, @@ -214,7 +211,7 @@ export class HTTPResponse { } }); } - return this._contentPromise; + return this.#contentPromise; } /** @@ -243,7 +240,7 @@ export class HTTPResponse { * @returns A matching {@link HTTPRequest} object. */ request(): HTTPRequest { - return this._request; + return this.#request; } /** @@ -251,14 +248,14 @@ export class HTTPResponse { * cache or memory cache. */ fromCache(): boolean { - return this._fromDiskCache || this._request._fromMemoryCache; + return this.#fromDiskCache || this.#request._fromMemoryCache; } /** * @returns True if the response was served by a service worker. */ fromServiceWorker(): boolean { - return this._fromServiceWorker; + return this.#fromServiceWorker; } /** @@ -266,6 +263,6 @@ export class HTTPResponse { * navigating to error pages. */ frame(): Frame | null { - return this._request.frame(); + return this.#request.frame(); } } diff --git a/src/common/Input.ts b/src/common/Input.ts index 1e2794b8..f3a9fd0a 100644 --- a/src/common/Input.ts +++ b/src/common/Input.ts @@ -16,7 +16,11 @@ import { assert } from './assert.js'; import { CDPSession } from './Connection.js'; -import { keyDefinitions, KeyDefinition, KeyInput } from './USKeyboardLayout.js'; +import { + _keyDefinitions, + KeyDefinition, + KeyInput, +} from './USKeyboardLayout.js'; import { Protocol } from 'devtools-protocol'; import { Point } from './JSHandle.js'; @@ -64,14 +68,19 @@ type KeyDescription = Required< * @public */ export class Keyboard { - private _client: CDPSession; - /** @internal */ - _modifiers = 0; - private _pressedKeys = new Set(); + #client: CDPSession; + #pressedKeys = new Set(); - /** @internal */ + /** + * @internal + */ + _modifiers = 0; + + /** + * @internal + */ constructor(client: CDPSession) { - this._client = client; + this.#client = client; } /** @@ -103,14 +112,14 @@ export class Keyboard { key: KeyInput, options: { text?: string } = { text: undefined } ): Promise { - const description = this._keyDescriptionForString(key); + const description = this.#keyDescriptionForString(key); - const autoRepeat = this._pressedKeys.has(description.code); - this._pressedKeys.add(description.code); - this._modifiers |= this._modifierBit(description.key); + const autoRepeat = this.#pressedKeys.has(description.code); + this.#pressedKeys.add(description.code); + this._modifiers |= this.#modifierBit(description.key); const text = options.text === undefined ? description.text : options.text; - await this._client.send('Input.dispatchKeyEvent', { + await this.#client.send('Input.dispatchKeyEvent', { type: text ? 'keyDown' : 'rawKeyDown', modifiers: this._modifiers, windowsVirtualKeyCode: description.keyCode, @@ -124,7 +133,7 @@ export class Keyboard { }); } - private _modifierBit(key: string): number { + #modifierBit(key: string): number { if (key === 'Alt') return 1; if (key === 'Control') return 2; if (key === 'Meta') return 4; @@ -132,7 +141,7 @@ export class Keyboard { return 0; } - private _keyDescriptionForString(keyString: KeyInput): KeyDescription { + #keyDescriptionForString(keyString: KeyInput): KeyDescription { const shift = this._modifiers & 8; const description = { key: '', @@ -142,7 +151,7 @@ export class Keyboard { location: 0, }; - const definition = keyDefinitions[keyString]; + const definition = _keyDefinitions[keyString]; assert(definition, `Unknown key: "${keyString}"`); if (definition.key) description.key = definition.key; @@ -175,11 +184,11 @@ export class Keyboard { * for a list of all key names. */ async up(key: KeyInput): Promise { - const description = this._keyDescriptionForString(key); + const description = this.#keyDescriptionForString(key); - this._modifiers &= ~this._modifierBit(description.key); - this._pressedKeys.delete(description.code); - await this._client.send('Input.dispatchKeyEvent', { + this._modifiers &= ~this.#modifierBit(description.key); + this.#pressedKeys.delete(description.code); + await this.#client.send('Input.dispatchKeyEvent', { type: 'keyUp', modifiers: this._modifiers, key: description.key, @@ -205,11 +214,11 @@ export class Keyboard { * @param char - Character to send into the page. */ async sendCharacter(char: string): Promise { - await this._client.send('Input.insertText', { text: char }); + await this.#client.send('Input.insertText', { text: char }); } private charIsKey(char: string): char is KeyInput { - return !!keyDefinitions[char as KeyInput]; + return !!_keyDefinitions[char as KeyInput]; } /** @@ -356,18 +365,18 @@ export interface MouseWheelOptions { * @public */ export class Mouse { - private _client: CDPSession; - private _keyboard: Keyboard; - private _x = 0; - private _y = 0; - private _button: MouseButton | 'none' = 'none'; + #client: CDPSession; + #keyboard: Keyboard; + #x = 0; + #y = 0; + #button: MouseButton | 'none' = 'none'; /** * @internal */ constructor(client: CDPSession, keyboard: Keyboard) { - this._client = client; - this._keyboard = keyboard; + this.#client = client; + this.#keyboard = keyboard; } /** @@ -383,17 +392,17 @@ export class Mouse { options: { steps?: number } = {} ): Promise { const { steps = 1 } = options; - const fromX = this._x, - fromY = this._y; - this._x = x; - this._y = y; + const fromX = this.#x, + fromY = this.#y; + this.#x = x; + this.#y = y; for (let i = 1; i <= steps; i++) { - await this._client.send('Input.dispatchMouseEvent', { + await this.#client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', - button: this._button, - x: fromX + (this._x - fromX) * (i / steps), - y: fromY + (this._y - fromY) * (i / steps), - modifiers: this._keyboard._modifiers, + button: this.#button, + x: fromX + (this.#x - fromX) * (i / steps), + y: fromY + (this.#y - fromY) * (i / steps), + modifiers: this.#keyboard._modifiers, }); } } @@ -428,13 +437,13 @@ export class Mouse { */ async down(options: MouseOptions = {}): Promise { const { button = 'left', clickCount = 1 } = options; - this._button = button; - await this._client.send('Input.dispatchMouseEvent', { + this.#button = button; + await this.#client.send('Input.dispatchMouseEvent', { type: 'mousePressed', button, - x: this._x, - y: this._y, - modifiers: this._keyboard._modifiers, + x: this.#x, + y: this.#y, + modifiers: this.#keyboard._modifiers, clickCount, }); } @@ -445,13 +454,13 @@ export class Mouse { */ async up(options: MouseOptions = {}): Promise { const { button = 'left', clickCount = 1 } = options; - this._button = 'none'; - await this._client.send('Input.dispatchMouseEvent', { + this.#button = 'none'; + await this.#client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', button, - x: this._x, - y: this._y, - modifiers: this._keyboard._modifiers, + x: this.#x, + y: this.#y, + modifiers: this.#keyboard._modifiers, clickCount, }); } @@ -477,13 +486,13 @@ export class Mouse { */ async wheel(options: MouseWheelOptions = {}): Promise { const { deltaX = 0, deltaY = 0 } = options; - await this._client.send('Input.dispatchMouseEvent', { + await this.#client.send('Input.dispatchMouseEvent', { type: 'mouseWheel', - x: this._x, - y: this._y, + x: this.#x, + y: this.#y, deltaX, deltaY, - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, pointerType: 'mouse', }); } @@ -495,7 +504,7 @@ export class Mouse { */ async drag(start: Point, target: Point): Promise { const promise = new Promise((resolve) => { - this._client.once('Input.dragIntercepted', (event) => + this.#client.once('Input.dragIntercepted', (event) => resolve(event.data) ); }); @@ -511,11 +520,11 @@ export class Mouse { * @param data - drag data containing items and operations mask */ async dragEnter(target: Point, data: Protocol.Input.DragData): Promise { - await this._client.send('Input.dispatchDragEvent', { + await this.#client.send('Input.dispatchDragEvent', { type: 'dragEnter', x: target.x, y: target.y, - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, data, }); } @@ -526,11 +535,11 @@ export class Mouse { * @param data - drag data containing items and operations mask */ async dragOver(target: Point, data: Protocol.Input.DragData): Promise { - await this._client.send('Input.dispatchDragEvent', { + await this.#client.send('Input.dispatchDragEvent', { type: 'dragOver', x: target.x, y: target.y, - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, data, }); } @@ -541,11 +550,11 @@ export class Mouse { * @param data - drag data containing items and operations mask */ async drop(target: Point, data: Protocol.Input.DragData): Promise { - await this._client.send('Input.dispatchDragEvent', { + await this.#client.send('Input.dispatchDragEvent', { type: 'drop', x: target.x, y: target.y, - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, data, }); } @@ -580,15 +589,15 @@ export class Mouse { * @public */ export class Touchscreen { - private _client: CDPSession; - private _keyboard: Keyboard; + #client: CDPSession; + #keyboard: Keyboard; /** * @internal */ constructor(client: CDPSession, keyboard: Keyboard) { - this._client = client; - this._keyboard = keyboard; + this.#client = client; + this.#keyboard = keyboard; } /** @@ -598,15 +607,15 @@ export class Touchscreen { */ async tap(x: number, y: number): Promise { const touchPoints = [{ x: Math.round(x), y: Math.round(y) }]; - await this._client.send('Input.dispatchTouchEvent', { + await this.#client.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints, - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, }); - await this._client.send('Input.dispatchTouchEvent', { + await this.#client.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [], - modifiers: this._keyboard._modifiers, + modifiers: this.#keyboard._modifiers, }); } } diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index 0dcabd78..62d235c1 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -30,7 +30,7 @@ import { Frame, FrameManager } from './FrameManager.js'; import { debugError, helper } from './helper.js'; import { MouseButton } from './Input.js'; import { Page, ScreenshotOptions } from './Page.js'; -import { getQueryHandlerAndSelector } from './QueryHandler.js'; +import { _getQueryHandlerAndSelector } from './QueryHandler.js'; import { KeyInput } from './USKeyboardLayout.js'; /** @@ -62,7 +62,7 @@ export interface BoundingBox extends Point { /** * @internal */ -export function createJSHandle( +export function _createJSHandle( context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject ): JSHandle { @@ -103,22 +103,38 @@ const applyOffsetsToQuad = (quad: Point[], offsetX: number, offsetY: number) => * @public */ export class JSHandle { + #client: CDPSession; + #disposed = false; + #context: ExecutionContext; + #remoteObject: Protocol.Runtime.RemoteObject; + /** * @internal */ - _context: ExecutionContext; + get _client(): CDPSession { + return this.#client; + } + /** * @internal */ - _client: CDPSession; + get _disposed(): boolean { + return this.#disposed; + } + /** * @internal */ - _remoteObject: Protocol.Runtime.RemoteObject; + get _remoteObject(): Protocol.Runtime.RemoteObject { + return this.#remoteObject; + } + /** * @internal */ - _disposed = false; + get _context(): ExecutionContext { + return this.#context; + } /** * @internal @@ -128,15 +144,15 @@ export class JSHandle { client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject ) { - this._context = context; - this._client = client; - this._remoteObject = remoteObject; + this.#context = context; + this.#client = client; + this.#remoteObject = remoteObject; } /** Returns the execution context the handle belongs to. */ executionContext(): ExecutionContext { - return this._context; + return this.#context; } /** @@ -222,15 +238,15 @@ export class JSHandle { * ``` */ async getProperties(): Promise> { - assert(this._remoteObject.objectId); - const response = await this._client.send('Runtime.getProperties', { - objectId: this._remoteObject.objectId, + assert(this.#remoteObject.objectId); + const response = await this.#client.send('Runtime.getProperties', { + objectId: this.#remoteObject.objectId, ownProperties: true, }); const result = new Map(); for (const property of response.result) { if (!property.enumerable || !property.value) continue; - result.set(property.name, createJSHandle(this._context, property.value)); + result.set(property.name, _createJSHandle(this.#context, property.value)); } return result; } @@ -245,16 +261,16 @@ export class JSHandle { * **NOTE** The method throws if the referenced object is not stringifiable. */ async jsonValue(): Promise { - if (this._remoteObject.objectId) { - const response = await this._client.send('Runtime.callFunctionOn', { + if (this.#remoteObject.objectId) { + const response = await this.#client.send('Runtime.callFunctionOn', { functionDeclaration: 'function() { return this; }', - objectId: this._remoteObject.objectId, + objectId: this.#remoteObject.objectId, returnByValue: true, awaitPromise: true, }); return helper.valueFromRemoteObject(response.result) as T; } - return helper.valueFromRemoteObject(this._remoteObject) as T; + return helper.valueFromRemoteObject(this.#remoteObject) as T; } /** @@ -273,9 +289,9 @@ export class JSHandle { * successfully disposed of. */ async dispose(): Promise { - if (this._disposed) return; - this._disposed = true; - await helper.releaseObject(this._client, this._remoteObject); + if (this.#disposed) return; + this.#disposed = true; + await helper.releaseObject(this.#client, this.#remoteObject); } /** @@ -284,11 +300,11 @@ export class JSHandle { * @remarks Useful during debugging. */ toString(): string { - if (this._remoteObject.objectId) { - const type = this._remoteObject.subtype || this._remoteObject.type; + if (this.#remoteObject.objectId) { + const type = this.#remoteObject.subtype || this.#remoteObject.type; return 'JSHandle@' + type; } - return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject); + return 'JSHandle:' + helper.valueFromRemoteObject(this.#remoteObject); } } @@ -329,9 +345,9 @@ export class JSHandle { export class ElementHandle< ElementType extends Element = Element > extends JSHandle { - private _frame: Frame; - private _page: Page; - private _frameManager: FrameManager; + #frame: Frame; + #page: Page; + #frameManager: FrameManager; /** * @internal @@ -345,11 +361,9 @@ export class ElementHandle< frameManager: FrameManager ) { super(context, client, remoteObject); - this._client = client; - this._remoteObject = remoteObject; - this._frame = frame; - this._page = page; - this._frameManager = frameManager; + this.#frame = frame; + this.#page = page; + this.#frameManager = frameManager; } /** @@ -498,10 +512,10 @@ export class ElementHandle< objectId: this._remoteObject.objectId, }); if (typeof nodeInfo.node.frameId !== 'string') return null; - return this._frameManager.frame(nodeInfo.node.frameId); + return this.#frameManager.frame(nodeInfo.node.frameId); } - private async _scrollIntoViewIfNeeded(): Promise { + async #scrollIntoViewIfNeeded(): Promise { const error = await this.evaluate( async ( element: Element, @@ -541,13 +555,13 @@ export class ElementHandle< } return false; }, - this._page.isJavaScriptEnabled() + this.#page.isJavaScriptEnabled() ); if (error) throw new Error(error); } - private async _getOOPIFOffsets( + async #getOOPIFOffsets( frame: Frame ): Promise<{ offsetX: number; offsetY: number }> { let offsetX = 0; @@ -559,17 +573,19 @@ export class ElementHandle< currentFrame = parent; continue; } - const { backendNodeId } = await parent._client.send('DOM.getFrameOwner', { - frameId: currentFrame._id, - }); - const result = await parent._client.send('DOM.getBoxModel', { + const { backendNodeId } = await parent + ._client() + .send('DOM.getFrameOwner', { + frameId: currentFrame._id, + }); + const result = await parent._client().send('DOM.getBoxModel', { backendNodeId: backendNodeId, }); if (!result) { break; } const contentBoxQuad = result.model.content; - const topLeftCorner = this._fromProtocolQuad(contentBoxQuad)[0]; + const topLeftCorner = this.#fromProtocolQuad(contentBoxQuad)[0]; offsetX += topLeftCorner!.x; offsetY += topLeftCorner!.y; currentFrame = parent; @@ -587,7 +603,7 @@ export class ElementHandle< objectId: this._remoteObject.objectId, }) .catch(debugError), - this._page.client().send('Page.getLayoutMetrics'), + this.#page._client().send('Page.getLayoutMetrics'), ]); if (!result || !result.quads.length) throw new Error('Node is either not clickable or not an HTMLElement'); @@ -595,12 +611,12 @@ export class ElementHandle< // Fallback to `layoutViewport` in case of using Firefox. const { clientWidth, clientHeight } = layoutMetrics.cssLayoutViewport || layoutMetrics.layoutViewport; - const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame); + const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame); const quads = result.quads - .map((quad) => this._fromProtocolQuad(quad)) + .map((quad) => this.#fromProtocolQuad(quad)) .map((quad) => applyOffsetsToQuad(quad, offsetX, offsetY)) .map((quad) => - this._intersectQuadWithViewport(quad, clientWidth, clientHeight) + this.#intersectQuadWithViewport(quad, clientWidth, clientHeight) ) .filter((quad) => computeQuadArea(quad) > 1); if (!quads.length) @@ -641,7 +657,7 @@ export class ElementHandle< }; } - private _getBoxModel(): Promise { + #getBoxModel(): Promise { const params: Protocol.DOM.GetBoxModelRequest = { objectId: this._remoteObject.objectId, }; @@ -650,7 +666,7 @@ export class ElementHandle< .catch((error) => debugError(error)); } - private _fromProtocolQuad(quad: number[]): Point[] { + #fromProtocolQuad(quad: number[]): Point[] { return [ { x: quad[0]!, y: quad[1]! }, { x: quad[2]!, y: quad[3]! }, @@ -659,7 +675,7 @@ export class ElementHandle< ]; } - private _intersectQuadWithViewport( + #intersectQuadWithViewport( quad: Point[], width: number, height: number @@ -676,9 +692,9 @@ export class ElementHandle< * If the element is detached from DOM, the method throws an error. */ async hover(): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const { x, y } = await this.clickablePoint(); - await this._page.mouse.move(x, y); + await this.#page.mouse.move(x, y); } /** @@ -687,9 +703,9 @@ export class ElementHandle< * If the element is detached from DOM, the method throws an error. */ async click(options: ClickOptions = {}): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const { x, y } = await this.clickablePoint(options.offset); - await this._page.mouse.click(x, y, options); + await this.#page.mouse.click(x, y, options); } /** @@ -697,12 +713,12 @@ export class ElementHandle< */ async drag(target: Point): Promise { assert( - this._page.isDragInterceptionEnabled(), + this.#page.isDragInterceptionEnabled(), 'Drag Interception is not enabled!' ); - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const start = await this.clickablePoint(); - return await this._page.mouse.drag(start, target); + return await this.#page.mouse.drag(start, target); } /** @@ -711,9 +727,9 @@ export class ElementHandle< async dragEnter( data: Protocol.Input.DragData = { items: [], dragOperationsMask: 1 } ): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const target = await this.clickablePoint(); - await this._page.mouse.dragEnter(target, data); + await this.#page.mouse.dragEnter(target, data); } /** @@ -722,9 +738,9 @@ export class ElementHandle< async dragOver( data: Protocol.Input.DragData = { items: [], dragOperationsMask: 1 } ): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const target = await this.clickablePoint(); - await this._page.mouse.dragOver(target, data); + await this.#page.mouse.dragOver(target, data); } /** @@ -733,9 +749,9 @@ export class ElementHandle< async drop( data: Protocol.Input.DragData = { items: [], dragOperationsMask: 1 } ): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const destination = await this.clickablePoint(); - await this._page.mouse.drop(destination, data); + await this.#page.mouse.drop(destination, data); } /** @@ -745,10 +761,10 @@ export class ElementHandle< target: ElementHandle, options?: { delay: number } ): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const startPoint = await this.clickablePoint(); const targetPoint = await target.clickablePoint(); - await this._page.mouse.dragAndDrop(startPoint, targetPoint, options); + await this.#page.mouse.dragAndDrop(startPoint, targetPoint, options); } /** @@ -883,9 +899,9 @@ export class ElementHandle< * If the element is detached from DOM, the method throws an error. */ async tap(): Promise { - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); const { x, y } = await this.clickablePoint(); - await this._page.touchscreen.tap(x, y); + await this.#page.touchscreen.tap(x, y); } /** @@ -921,7 +937,7 @@ export class ElementHandle< */ async type(text: string, options?: { delay: number }): Promise { await this.focus(); - await this._page.keyboard.type(text, options); + await this.#page.keyboard.type(text, options); } /** @@ -940,7 +956,7 @@ export class ElementHandle< */ async press(key: KeyInput, options?: PressOptions): Promise { await this.focus(); - await this._page.keyboard.press(key, options); + await this.#page.keyboard.press(key, options); } /** @@ -948,11 +964,11 @@ export class ElementHandle< * or `null` if the element is not visible. */ async boundingBox(): Promise { - const result = await this._getBoxModel(); + const result = await this.#getBoxModel(); if (!result) return null; - const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame); + const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame); const quad = result.model.border; const x = Math.min(quad[0]!, quad[2]!, quad[4]!, quad[6]!); const y = Math.min(quad[1]!, quad[3]!, quad[5]!, quad[7]!); @@ -971,31 +987,31 @@ export class ElementHandle< * Each Point is an object `{x, y}`. Box points are sorted clock-wise. */ async boxModel(): Promise { - const result = await this._getBoxModel(); + const result = await this.#getBoxModel(); if (!result) return null; - const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame); + const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame); const { content, padding, border, margin, width, height } = result.model; return { content: applyOffsetsToQuad( - this._fromProtocolQuad(content), + this.#fromProtocolQuad(content), offsetX, offsetY ), padding: applyOffsetsToQuad( - this._fromProtocolQuad(padding), + this.#fromProtocolQuad(padding), offsetX, offsetY ), border: applyOffsetsToQuad( - this._fromProtocolQuad(border), + this.#fromProtocolQuad(border), offsetX, offsetY ), margin: applyOffsetsToQuad( - this._fromProtocolQuad(margin), + this.#fromProtocolQuad(margin), offsetX, offsetY ), @@ -1015,7 +1031,7 @@ export class ElementHandle< let boundingBox = await this.boundingBox(); assert(boundingBox, 'Node is either not visible or not an HTMLElement'); - const viewport = this._page.viewport(); + const viewport = this.#page.viewport(); assert(viewport); if ( @@ -1026,12 +1042,12 @@ export class ElementHandle< width: Math.max(viewport.width, Math.ceil(boundingBox.width)), height: Math.max(viewport.height, Math.ceil(boundingBox.height)), }; - await this._page.setViewport(Object.assign({}, viewport, newViewport)); + await this.#page.setViewport(Object.assign({}, viewport, newViewport)); needsViewportReset = true; } - await this._scrollIntoViewIfNeeded(); + await this.#scrollIntoViewIfNeeded(); boundingBox = await this.boundingBox(); assert(boundingBox, 'Node is either not visible or not an HTMLElement'); @@ -1047,7 +1063,7 @@ export class ElementHandle< clip.x += pageX; clip.y += pageY; - const imageData = await this._page.screenshot( + const imageData = await this.#page.screenshot( Object.assign( {}, { @@ -1057,7 +1073,7 @@ export class ElementHandle< ) ); - if (needsViewportReset) await this._page.setViewport(viewport); + if (needsViewportReset) await this.#page.setViewport(viewport); return imageData; } @@ -1073,7 +1089,7 @@ export class ElementHandle< selector: string ): Promise | null> { const { updatedSelector, queryHandler } = - getQueryHandlerAndSelector(selector); + _getQueryHandlerAndSelector(selector); assert( queryHandler.queryOne, 'Cannot handle queries for a single element with the given selector' @@ -1096,7 +1112,7 @@ export class ElementHandle< selector: string ): Promise>> { const { updatedSelector, queryHandler } = - getQueryHandlerAndSelector(selector); + _getQueryHandlerAndSelector(selector); assert( queryHandler.queryAll, 'Cannot handle queries for a multiple element with the given selector' @@ -1184,7 +1200,7 @@ export class ElementHandle< ...args: SerializableOrJSHandle[] ): Promise> { const { updatedSelector, queryHandler } = - getQueryHandlerAndSelector(selector); + _getQueryHandlerAndSelector(selector); assert(queryHandler.queryAllArray); const arrayHandle = await queryHandler.queryAllArray(this, updatedSelector); const result = await arrayHandle.evaluate>( diff --git a/src/common/LifecycleWatcher.ts b/src/common/LifecycleWatcher.ts index e62b8d03..6e247dd9 100644 --- a/src/common/LifecycleWatcher.ts +++ b/src/common/LifecycleWatcher.ts @@ -60,41 +60,41 @@ const noop = (): void => {}; * @internal */ export class LifecycleWatcher { - _expectedLifecycle: ProtocolLifeCycleEvent[]; - _frameManager: FrameManager; - _frame: Frame; - _timeout: number; - _navigationRequest: HTTPRequest | null = null; - _eventListeners: PuppeteerEventListener[]; + #expectedLifecycle: ProtocolLifeCycleEvent[]; + #frameManager: FrameManager; + #frame: Frame; + #timeout: number; + #navigationRequest: HTTPRequest | null = null; + #eventListeners: PuppeteerEventListener[]; - _sameDocumentNavigationCompleteCallback: (x?: Error) => void = noop; - _sameDocumentNavigationPromise = new Promise((fulfill) => { - this._sameDocumentNavigationCompleteCallback = fulfill; + #sameDocumentNavigationCompleteCallback: (x?: Error) => void = noop; + #sameDocumentNavigationPromise = new Promise((fulfill) => { + this.#sameDocumentNavigationCompleteCallback = fulfill; }); - _lifecycleCallback: () => void = noop; - _lifecyclePromise: Promise = new Promise((fulfill) => { - this._lifecycleCallback = fulfill; + #lifecycleCallback: () => void = noop; + #lifecyclePromise: Promise = new Promise((fulfill) => { + this.#lifecycleCallback = fulfill; }); - _newDocumentNavigationCompleteCallback: (x?: Error) => void = noop; - _newDocumentNavigationPromise: Promise = new Promise( + #newDocumentNavigationCompleteCallback: (x?: Error) => void = noop; + #newDocumentNavigationPromise: Promise = new Promise( (fulfill) => { - this._newDocumentNavigationCompleteCallback = fulfill; + this.#newDocumentNavigationCompleteCallback = fulfill; } ); - _terminationCallback: (x?: Error) => void = noop; - _terminationPromise: Promise = new Promise((fulfill) => { - this._terminationCallback = fulfill; + #terminationCallback: (x?: Error) => void = noop; + #terminationPromise: Promise = new Promise((fulfill) => { + this.#terminationCallback = fulfill; }); - _timeoutPromise: Promise; + #timeoutPromise: Promise; - _maximumTimer?: NodeJS.Timeout; - _hasSameDocumentNavigation?: boolean; - _newDocumentNavigation?: boolean; - _swapped?: boolean; + #maximumTimer?: NodeJS.Timeout; + #hasSameDocumentNavigation?: boolean; + #newDocumentNavigation?: boolean; + #swapped?: boolean; constructor( frameManager: FrameManager, @@ -104,137 +104,138 @@ export class LifecycleWatcher { ) { if (Array.isArray(waitUntil)) waitUntil = waitUntil.slice(); else if (typeof waitUntil === 'string') waitUntil = [waitUntil]; - this._expectedLifecycle = waitUntil.map((value) => { + this.#expectedLifecycle = waitUntil.map((value) => { const protocolEvent = puppeteerToProtocolLifecycle.get(value); assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value); return protocolEvent as ProtocolLifeCycleEvent; }); - this._frameManager = frameManager; - this._frame = frame; - this._timeout = timeout; - this._eventListeners = [ + this.#frameManager = frameManager; + this.#frame = frame; + this.#timeout = timeout; + this.#eventListeners = [ helper.addEventListener( frameManager._client, CDPSessionEmittedEvents.Disconnected, - this._terminate.bind( + this.#terminate.bind( this, new Error('Navigation failed because browser has disconnected!') ) ), helper.addEventListener( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.LifecycleEvent, - this._checkLifecycleComplete.bind(this) + this.#checkLifecycleComplete.bind(this) ), helper.addEventListener( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameNavigatedWithinDocument, - this._navigatedWithinDocument.bind(this) + this.#navigatedWithinDocument.bind(this) ), helper.addEventListener( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameNavigated, - this._navigated.bind(this) + this.#navigated.bind(this) ), helper.addEventListener( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameSwapped, - this._frameSwapped.bind(this) + this.#frameSwapped.bind(this) ), helper.addEventListener( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameDetached, - this._onFrameDetached.bind(this) + this.#onFrameDetached.bind(this) ), helper.addEventListener( - this._frameManager.networkManager(), + this.#frameManager.networkManager(), NetworkManagerEmittedEvents.Request, - this._onRequest.bind(this) + this.#onRequest.bind(this) ), ]; - this._timeoutPromise = this._createTimeoutPromise(); - this._checkLifecycleComplete(); + this.#timeoutPromise = this.#createTimeoutPromise(); + this.#checkLifecycleComplete(); } - _onRequest(request: HTTPRequest): void { - if (request.frame() !== this._frame || !request.isNavigationRequest()) + #onRequest(request: HTTPRequest): void { + if (request.frame() !== this.#frame || !request.isNavigationRequest()) return; - this._navigationRequest = request; + this.#navigationRequest = request; } - _onFrameDetached(frame: Frame): void { - if (this._frame === frame) { - this._terminationCallback.call( + #onFrameDetached(frame: Frame): void { + if (this.#frame === frame) { + this.#terminationCallback.call( null, new Error('Navigating frame was detached') ); return; } - this._checkLifecycleComplete(); + this.#checkLifecycleComplete(); } async navigationResponse(): Promise { // We may need to wait for ExtraInfo events before the request is complete. - return this._navigationRequest ? this._navigationRequest.response() : null; + return this.#navigationRequest ? this.#navigationRequest.response() : null; } - _terminate(error: Error): void { - this._terminationCallback.call(null, error); + #terminate(error: Error): void { + this.#terminationCallback.call(null, error); } sameDocumentNavigationPromise(): Promise { - return this._sameDocumentNavigationPromise; + return this.#sameDocumentNavigationPromise; } newDocumentNavigationPromise(): Promise { - return this._newDocumentNavigationPromise; + return this.#newDocumentNavigationPromise; } lifecyclePromise(): Promise { - return this._lifecyclePromise; + return this.#lifecyclePromise; } timeoutOrTerminationPromise(): Promise { - return Promise.race([this._timeoutPromise, this._terminationPromise]); + return Promise.race([this.#timeoutPromise, this.#terminationPromise]); } - _createTimeoutPromise(): Promise { - if (!this._timeout) return new Promise(() => {}); + async #createTimeoutPromise(): Promise { + if (!this.#timeout) return new Promise(noop); const errorMessage = - 'Navigation timeout of ' + this._timeout + ' ms exceeded'; - return new Promise( - (fulfill) => (this._maximumTimer = setTimeout(fulfill, this._timeout)) - ).then(() => new TimeoutError(errorMessage)); + 'Navigation timeout of ' + this.#timeout + ' ms exceeded'; + await new Promise( + (fulfill) => (this.#maximumTimer = setTimeout(fulfill, this.#timeout)) + ); + return new TimeoutError(errorMessage); } - _navigatedWithinDocument(frame: Frame): void { - if (frame !== this._frame) return; - this._hasSameDocumentNavigation = true; - this._checkLifecycleComplete(); + #navigatedWithinDocument(frame: Frame): void { + if (frame !== this.#frame) return; + this.#hasSameDocumentNavigation = true; + this.#checkLifecycleComplete(); } - _navigated(frame: Frame): void { - if (frame !== this._frame) return; - this._newDocumentNavigation = true; - this._checkLifecycleComplete(); + #navigated(frame: Frame): void { + if (frame !== this.#frame) return; + this.#newDocumentNavigation = true; + this.#checkLifecycleComplete(); } - _frameSwapped(frame: Frame): void { - if (frame !== this._frame) return; - this._swapped = true; - this._checkLifecycleComplete(); + #frameSwapped(frame: Frame): void { + if (frame !== this.#frame) return; + this.#swapped = true; + this.#checkLifecycleComplete(); } - _checkLifecycleComplete(): void { + #checkLifecycleComplete(): void { // We expect navigation to commit. - if (!checkLifecycle(this._frame, this._expectedLifecycle)) return; - this._lifecycleCallback(); - if (this._hasSameDocumentNavigation) - this._sameDocumentNavigationCompleteCallback(); - if (this._swapped || this._newDocumentNavigation) - this._newDocumentNavigationCompleteCallback(); + if (!checkLifecycle(this.#frame, this.#expectedLifecycle)) return; + this.#lifecycleCallback(); + if (this.#hasSameDocumentNavigation) + this.#sameDocumentNavigationCompleteCallback(); + if (this.#swapped || this.#newDocumentNavigation) + this.#newDocumentNavigationCompleteCallback(); function checkLifecycle( frame: Frame, @@ -256,7 +257,7 @@ export class LifecycleWatcher { } dispose(): void { - helper.removeEventListeners(this._eventListeners); - this._maximumTimer !== undefined && clearTimeout(this._maximumTimer); + helper.removeEventListeners(this.#eventListeners); + this.#maximumTimer !== undefined && clearTimeout(this.#maximumTimer); } } diff --git a/src/common/NetworkEventManager.ts b/src/common/NetworkEventManager.ts index 632a8f5a..c3644716 100644 --- a/src/common/NetworkEventManager.ts +++ b/src/common/NetworkEventManager.ts @@ -54,15 +54,15 @@ export class NetworkEventManager { * `_onRequestWillBeSent`, `_onRequestPaused`, `_onRequestPaused`, ... * (see crbug.com/1196004) */ - private _requestWillBeSentMap = new Map< + #requestWillBeSentMap = new Map< NetworkRequestId, Protocol.Network.RequestWillBeSentEvent >(); - private _requestPausedMap = new Map< + #requestPausedMap = new Map< NetworkRequestId, Protocol.Fetch.RequestPausedEvent >(); - private _httpRequestsMap = new Map(); + #httpRequestsMap = new Map(); /* * The below maps are used to reconcile Network.responseReceivedExtraInfo @@ -73,40 +73,37 @@ export class NetworkEventManager { * handle redirects, we have to make them Arrays to represent the chain of * events. */ - private _responseReceivedExtraInfoMap = new Map< + #responseReceivedExtraInfoMap = new Map< NetworkRequestId, Protocol.Network.ResponseReceivedExtraInfoEvent[] >(); - private _queuedRedirectInfoMap = new Map< - NetworkRequestId, - RedirectInfoList - >(); - private _queuedEventGroupMap = new Map(); + #queuedRedirectInfoMap = new Map(); + #queuedEventGroupMap = new Map(); forget(networkRequestId: NetworkRequestId): void { - this._requestWillBeSentMap.delete(networkRequestId); - this._requestPausedMap.delete(networkRequestId); - this._queuedEventGroupMap.delete(networkRequestId); - this._queuedRedirectInfoMap.delete(networkRequestId); - this._responseReceivedExtraInfoMap.delete(networkRequestId); + this.#requestWillBeSentMap.delete(networkRequestId); + this.#requestPausedMap.delete(networkRequestId); + this.#queuedEventGroupMap.delete(networkRequestId); + this.#queuedRedirectInfoMap.delete(networkRequestId); + this.#responseReceivedExtraInfoMap.delete(networkRequestId); } responseExtraInfo( networkRequestId: NetworkRequestId ): Protocol.Network.ResponseReceivedExtraInfoEvent[] { - if (!this._responseReceivedExtraInfoMap.has(networkRequestId)) { - this._responseReceivedExtraInfoMap.set(networkRequestId, []); + if (!this.#responseReceivedExtraInfoMap.has(networkRequestId)) { + this.#responseReceivedExtraInfoMap.set(networkRequestId, []); } - return this._responseReceivedExtraInfoMap.get( + return this.#responseReceivedExtraInfoMap.get( networkRequestId ) as Protocol.Network.ResponseReceivedExtraInfoEvent[]; } private queuedRedirectInfo(fetchRequestId: FetchRequestId): RedirectInfoList { - if (!this._queuedRedirectInfoMap.has(fetchRequestId)) { - this._queuedRedirectInfoMap.set(fetchRequestId, []); + if (!this.#queuedRedirectInfoMap.has(fetchRequestId)) { + this.#queuedRedirectInfoMap.set(fetchRequestId, []); } - return this._queuedRedirectInfoMap.get(fetchRequestId) as RedirectInfoList; + return this.#queuedRedirectInfoMap.get(fetchRequestId) as RedirectInfoList; } queueRedirectInfo( @@ -123,7 +120,7 @@ export class NetworkEventManager { } numRequestsInProgress(): number { - return [...this._httpRequestsMap].filter(([, request]) => { + return [...this.#httpRequestsMap].filter(([, request]) => { return !request.response(); }).length; } @@ -132,62 +129,62 @@ export class NetworkEventManager { networkRequestId: NetworkRequestId, event: Protocol.Network.RequestWillBeSentEvent ): void { - this._requestWillBeSentMap.set(networkRequestId, event); + this.#requestWillBeSentMap.set(networkRequestId, event); } getRequestWillBeSent( networkRequestId: NetworkRequestId ): Protocol.Network.RequestWillBeSentEvent | undefined { - return this._requestWillBeSentMap.get(networkRequestId); + return this.#requestWillBeSentMap.get(networkRequestId); } forgetRequestWillBeSent(networkRequestId: NetworkRequestId): void { - this._requestWillBeSentMap.delete(networkRequestId); + this.#requestWillBeSentMap.delete(networkRequestId); } getRequestPaused( networkRequestId: NetworkRequestId ): Protocol.Fetch.RequestPausedEvent | undefined { - return this._requestPausedMap.get(networkRequestId); + return this.#requestPausedMap.get(networkRequestId); } forgetRequestPaused(networkRequestId: NetworkRequestId): void { - this._requestPausedMap.delete(networkRequestId); + this.#requestPausedMap.delete(networkRequestId); } storeRequestPaused( networkRequestId: NetworkRequestId, event: Protocol.Fetch.RequestPausedEvent ): void { - this._requestPausedMap.set(networkRequestId, event); + this.#requestPausedMap.set(networkRequestId, event); } getRequest(networkRequestId: NetworkRequestId): HTTPRequest | undefined { - return this._httpRequestsMap.get(networkRequestId); + return this.#httpRequestsMap.get(networkRequestId); } storeRequest(networkRequestId: NetworkRequestId, request: HTTPRequest): void { - this._httpRequestsMap.set(networkRequestId, request); + this.#httpRequestsMap.set(networkRequestId, request); } forgetRequest(networkRequestId: NetworkRequestId): void { - this._httpRequestsMap.delete(networkRequestId); + this.#httpRequestsMap.delete(networkRequestId); } getQueuedEventGroup( networkRequestId: NetworkRequestId ): QueuedEventGroup | undefined { - return this._queuedEventGroupMap.get(networkRequestId); + return this.#queuedEventGroupMap.get(networkRequestId); } queueEventGroup( networkRequestId: NetworkRequestId, event: QueuedEventGroup ): void { - this._queuedEventGroupMap.set(networkRequestId, event); + this.#queuedEventGroupMap.set(networkRequestId, event); } forgetQueuedEventGroup(networkRequestId: NetworkRequestId): void { - this._queuedEventGroupMap.delete(networkRequestId); + this.#queuedEventGroupMap.delete(networkRequestId); } } diff --git a/src/common/NetworkManager.ts b/src/common/NetworkManager.ts index 1f3c748e..1d124b0e 100644 --- a/src/common/NetworkManager.ts +++ b/src/common/NetworkManager.ts @@ -79,19 +79,17 @@ interface FrameManager { * @internal */ export class NetworkManager extends EventEmitter { - _client: CDPSession; - _ignoreHTTPSErrors: boolean; - _frameManager: FrameManager; - - _networkEventManager = new NetworkEventManager(); - - _extraHTTPHeaders: Record = {}; - _credentials?: Credentials; - _attemptedAuthentications = new Set(); - _userRequestInterceptionEnabled = false; - _protocolRequestInterceptionEnabled = false; - _userCacheDisabled = false; - _emulatedNetworkConditions: InternalNetworkConditions = { + #client: CDPSession; + #ignoreHTTPSErrors: boolean; + #frameManager: FrameManager; + #networkEventManager = new NetworkEventManager(); + #extraHTTPHeaders: Record = {}; + #credentials?: Credentials; + #attemptedAuthentications = new Set(); + #userRequestInterceptionEnabled = false; + #protocolRequestInterceptionEnabled = false; + #userCacheDisabled = false; + #emulatedNetworkConditions: InternalNetworkConditions = { offline: false, upload: -1, download: -1, @@ -104,100 +102,100 @@ export class NetworkManager extends EventEmitter { frameManager: FrameManager ) { super(); - this._client = client; - this._ignoreHTTPSErrors = ignoreHTTPSErrors; - this._frameManager = frameManager; + this.#client = client; + this.#ignoreHTTPSErrors = ignoreHTTPSErrors; + this.#frameManager = frameManager; - this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); - this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); - this._client.on( + this.#client.on('Fetch.requestPaused', this.#onRequestPaused.bind(this)); + this.#client.on('Fetch.authRequired', this.#onAuthRequired.bind(this)); + this.#client.on( 'Network.requestWillBeSent', - this._onRequestWillBeSent.bind(this) + this.#onRequestWillBeSent.bind(this) ); - this._client.on( + this.#client.on( 'Network.requestServedFromCache', - this._onRequestServedFromCache.bind(this) + this.#onRequestServedFromCache.bind(this) ); - this._client.on( + this.#client.on( 'Network.responseReceived', - this._onResponseReceived.bind(this) + this.#onResponseReceived.bind(this) ); - this._client.on( + this.#client.on( 'Network.loadingFinished', - this._onLoadingFinished.bind(this) + this.#onLoadingFinished.bind(this) ); - this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); - this._client.on( + this.#client.on('Network.loadingFailed', this.#onLoadingFailed.bind(this)); + this.#client.on( 'Network.responseReceivedExtraInfo', - this._onResponseReceivedExtraInfo.bind(this) + this.#onResponseReceivedExtraInfo.bind(this) ); } async initialize(): Promise { - await this._client.send('Network.enable'); - if (this._ignoreHTTPSErrors) - await this._client.send('Security.setIgnoreCertificateErrors', { + await this.#client.send('Network.enable'); + if (this.#ignoreHTTPSErrors) + await this.#client.send('Security.setIgnoreCertificateErrors', { ignore: true, }); } async authenticate(credentials?: Credentials): Promise { - this._credentials = credentials; - await this._updateProtocolRequestInterception(); + this.#credentials = credentials; + await this.#updateProtocolRequestInterception(); } async setExtraHTTPHeaders( extraHTTPHeaders: Record ): Promise { - this._extraHTTPHeaders = {}; + this.#extraHTTPHeaders = {}; for (const key of Object.keys(extraHTTPHeaders)) { const value = extraHTTPHeaders[key]; assert( helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.` ); - this._extraHTTPHeaders[key.toLowerCase()] = value; + this.#extraHTTPHeaders[key.toLowerCase()] = value; } - await this._client.send('Network.setExtraHTTPHeaders', { - headers: this._extraHTTPHeaders, + await this.#client.send('Network.setExtraHTTPHeaders', { + headers: this.#extraHTTPHeaders, }); } extraHTTPHeaders(): Record { - return Object.assign({}, this._extraHTTPHeaders); + return Object.assign({}, this.#extraHTTPHeaders); } numRequestsInProgress(): number { - return this._networkEventManager.numRequestsInProgress(); + return this.#networkEventManager.numRequestsInProgress(); } async setOfflineMode(value: boolean): Promise { - this._emulatedNetworkConditions.offline = value; - await this._updateNetworkConditions(); + this.#emulatedNetworkConditions.offline = value; + await this.#updateNetworkConditions(); } async emulateNetworkConditions( networkConditions: NetworkConditions | null ): Promise { - this._emulatedNetworkConditions.upload = networkConditions + this.#emulatedNetworkConditions.upload = networkConditions ? networkConditions.upload : -1; - this._emulatedNetworkConditions.download = networkConditions + this.#emulatedNetworkConditions.download = networkConditions ? networkConditions.download : -1; - this._emulatedNetworkConditions.latency = networkConditions + this.#emulatedNetworkConditions.latency = networkConditions ? networkConditions.latency : 0; - await this._updateNetworkConditions(); + await this.#updateNetworkConditions(); } - async _updateNetworkConditions(): Promise { - await this._client.send('Network.emulateNetworkConditions', { - offline: this._emulatedNetworkConditions.offline, - latency: this._emulatedNetworkConditions.latency, - uploadThroughput: this._emulatedNetworkConditions.upload, - downloadThroughput: this._emulatedNetworkConditions.download, + async #updateNetworkConditions(): Promise { + await this.#client.send('Network.emulateNetworkConditions', { + offline: this.#emulatedNetworkConditions.offline, + latency: this.#emulatedNetworkConditions.latency, + uploadThroughput: this.#emulatedNetworkConditions.upload, + downloadThroughput: this.#emulatedNetworkConditions.download, }); } @@ -205,96 +203,96 @@ export class NetworkManager extends EventEmitter { userAgent: string, userAgentMetadata?: Protocol.Emulation.UserAgentMetadata ): Promise { - await this._client.send('Network.setUserAgentOverride', { + await this.#client.send('Network.setUserAgentOverride', { userAgent: userAgent, userAgentMetadata: userAgentMetadata, }); } async setCacheEnabled(enabled: boolean): Promise { - this._userCacheDisabled = !enabled; - await this._updateProtocolCacheDisabled(); + this.#userCacheDisabled = !enabled; + await this.#updateProtocolCacheDisabled(); } async setRequestInterception(value: boolean): Promise { - this._userRequestInterceptionEnabled = value; - await this._updateProtocolRequestInterception(); + this.#userRequestInterceptionEnabled = value; + await this.#updateProtocolRequestInterception(); } - async _updateProtocolRequestInterception(): Promise { - const enabled = this._userRequestInterceptionEnabled || !!this._credentials; - if (enabled === this._protocolRequestInterceptionEnabled) return; - this._protocolRequestInterceptionEnabled = enabled; + async #updateProtocolRequestInterception(): Promise { + const enabled = this.#userRequestInterceptionEnabled || !!this.#credentials; + if (enabled === this.#protocolRequestInterceptionEnabled) return; + this.#protocolRequestInterceptionEnabled = enabled; if (enabled) { await Promise.all([ - this._updateProtocolCacheDisabled(), - this._client.send('Fetch.enable', { + this.#updateProtocolCacheDisabled(), + this.#client.send('Fetch.enable', { handleAuthRequests: true, patterns: [{ urlPattern: '*' }], }), ]); } else { await Promise.all([ - this._updateProtocolCacheDisabled(), - this._client.send('Fetch.disable'), + this.#updateProtocolCacheDisabled(), + this.#client.send('Fetch.disable'), ]); } } - _cacheDisabled(): boolean { - return this._userCacheDisabled; + #cacheDisabled(): boolean { + return this.#userCacheDisabled; } - async _updateProtocolCacheDisabled(): Promise { - await this._client.send('Network.setCacheDisabled', { - cacheDisabled: this._cacheDisabled(), + async #updateProtocolCacheDisabled(): Promise { + await this.#client.send('Network.setCacheDisabled', { + cacheDisabled: this.#cacheDisabled(), }); } - _onRequestWillBeSent(event: Protocol.Network.RequestWillBeSentEvent): void { + #onRequestWillBeSent(event: Protocol.Network.RequestWillBeSentEvent): void { // Request interception doesn't happen for data URLs with Network Service. if ( - this._userRequestInterceptionEnabled && + this.#userRequestInterceptionEnabled && !event.request.url.startsWith('data:') ) { const { requestId: networkRequestId } = event; - this._networkEventManager.storeRequestWillBeSent(networkRequestId, event); + this.#networkEventManager.storeRequestWillBeSent(networkRequestId, event); /** * CDP may have sent a Fetch.requestPaused event already. Check for it. */ const requestPausedEvent = - this._networkEventManager.getRequestPaused(networkRequestId); + this.#networkEventManager.getRequestPaused(networkRequestId); if (requestPausedEvent) { const { requestId: fetchRequestId } = requestPausedEvent; - this._patchRequestEventHeaders(event, requestPausedEvent); - this._onRequest(event, fetchRequestId); - this._networkEventManager.forgetRequestPaused(networkRequestId); + this.#patchRequestEventHeaders(event, requestPausedEvent); + this.#onRequest(event, fetchRequestId); + this.#networkEventManager.forgetRequestPaused(networkRequestId); } return; } - this._onRequest(event, undefined); + this.#onRequest(event, undefined); } - _onAuthRequired(event: Protocol.Fetch.AuthRequiredEvent): void { + #onAuthRequired(event: Protocol.Fetch.AuthRequiredEvent): void { /* TODO(jacktfranklin): This is defined in protocol.d.ts but not * in an easily referrable way - we should look at exposing it. */ type AuthResponse = 'Default' | 'CancelAuth' | 'ProvideCredentials'; let response: AuthResponse = 'Default'; - if (this._attemptedAuthentications.has(event.requestId)) { + if (this.#attemptedAuthentications.has(event.requestId)) { response = 'CancelAuth'; - } else if (this._credentials) { + } else if (this.#credentials) { response = 'ProvideCredentials'; - this._attemptedAuthentications.add(event.requestId); + this.#attemptedAuthentications.add(event.requestId); } - const { username, password } = this._credentials || { + const { username, password } = this.#credentials || { username: undefined, password: undefined, }; - this._client + this.#client .send('Fetch.continueWithAuth', { requestId: event.requestId, authChallengeResponse: { response, username, password }, @@ -308,15 +306,13 @@ export class NetworkManager extends EventEmitter { * * CDP may send multiple Fetch.requestPaused * for the same Network.requestWillBeSent. - * - * */ - _onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void { + #onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void { if ( - !this._userRequestInterceptionEnabled && - this._protocolRequestInterceptionEnabled + !this.#userRequestInterceptionEnabled && + this.#protocolRequestInterceptionEnabled ) { - this._client + this.#client .send('Fetch.continueRequest', { requestId: event.requestId, }) @@ -331,7 +327,7 @@ export class NetworkManager extends EventEmitter { const requestWillBeSentEvent = (() => { const requestWillBeSentEvent = - this._networkEventManager.getRequestWillBeSent(networkRequestId); + this.#networkEventManager.getRequestWillBeSent(networkRequestId); // redirect requests have the same `requestId`, if ( @@ -339,21 +335,21 @@ export class NetworkManager extends EventEmitter { (requestWillBeSentEvent.request.url !== event.request.url || requestWillBeSentEvent.request.method !== event.request.method) ) { - this._networkEventManager.forgetRequestWillBeSent(networkRequestId); + this.#networkEventManager.forgetRequestWillBeSent(networkRequestId); return; } return requestWillBeSentEvent; })(); if (requestWillBeSentEvent) { - this._patchRequestEventHeaders(requestWillBeSentEvent, event); - this._onRequest(requestWillBeSentEvent, fetchRequestId); + this.#patchRequestEventHeaders(requestWillBeSentEvent, event); + this.#onRequest(requestWillBeSentEvent, fetchRequestId); } else { - this._networkEventManager.storeRequestPaused(networkRequestId, event); + this.#networkEventManager.storeRequestPaused(networkRequestId, event); } } - _patchRequestEventHeaders( + #patchRequestEventHeaders( requestWillBeSentEvent: Protocol.Network.RequestWillBeSentEvent, requestPausedEvent: Protocol.Fetch.RequestPausedEvent ): void { @@ -364,7 +360,7 @@ export class NetworkManager extends EventEmitter { }; } - _onRequest( + #onRequest( event: Protocol.Network.RequestWillBeSentEvent, fetchRequestId?: FetchRequestId ): void { @@ -379,11 +375,11 @@ export class NetworkManager extends EventEmitter { // response/requestfinished. let redirectResponseExtraInfo = null; if (event.redirectHasExtraInfo) { - redirectResponseExtraInfo = this._networkEventManager + redirectResponseExtraInfo = this.#networkEventManager .responseExtraInfo(event.requestId) .shift(); if (!redirectResponseExtraInfo) { - this._networkEventManager.queueRedirectInfo(event.requestId, { + this.#networkEventManager.queueRedirectInfo(event.requestId, { event, fetchRequestId, }); @@ -391,11 +387,11 @@ export class NetworkManager extends EventEmitter { } } - const request = this._networkEventManager.getRequest(event.requestId); + const request = this.#networkEventManager.getRequest(event.requestId); // If we connect late to the target, we could have missed the // requestWillBeSent event. if (request) { - this._handleRequestRedirect( + this.#handleRequestRedirect( request, event.redirectResponse, redirectResponseExtraInfo @@ -404,37 +400,37 @@ export class NetworkManager extends EventEmitter { } } const frame = event.frameId - ? this._frameManager.frame(event.frameId) + ? this.#frameManager.frame(event.frameId) : null; const request = new HTTPRequest( - this._client, + this.#client, frame, fetchRequestId, - this._userRequestInterceptionEnabled, + this.#userRequestInterceptionEnabled, event, redirectChain ); - this._networkEventManager.storeRequest(event.requestId, request); + this.#networkEventManager.storeRequest(event.requestId, request); this.emit(NetworkManagerEmittedEvents.Request, request); request.finalizeInterceptions(); } - _onRequestServedFromCache( + #onRequestServedFromCache( event: Protocol.Network.RequestServedFromCacheEvent ): void { - const request = this._networkEventManager.getRequest(event.requestId); + const request = this.#networkEventManager.getRequest(event.requestId); if (request) request._fromMemoryCache = true; this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request); } - _handleRequestRedirect( + #handleRequestRedirect( request: HTTPRequest, responsePayload: Protocol.Network.Response, extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null ): void { const response = new HTTPResponse( - this._client, + this.#client, request, responsePayload, extraInfo @@ -444,22 +440,22 @@ export class NetworkManager extends EventEmitter { response._resolveBody( new Error('Response body is unavailable for redirect responses') ); - this._forgetRequest(request, false); + this.#forgetRequest(request, false); this.emit(NetworkManagerEmittedEvents.Response, response); this.emit(NetworkManagerEmittedEvents.RequestFinished, request); } - _emitResponseEvent( + #emitResponseEvent( responseReceived: Protocol.Network.ResponseReceivedEvent, extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null ): void { - const request = this._networkEventManager.getRequest( + const request = this.#networkEventManager.getRequest( responseReceived.requestId ); // FileUpload sends a response without a matching request. if (!request) return; - const extraInfos = this._networkEventManager.responseExtraInfo( + const extraInfos = this.#networkEventManager.responseExtraInfo( responseReceived.requestId ); if (extraInfos.length) { @@ -472,7 +468,7 @@ export class NetworkManager extends EventEmitter { } const response = new HTTPResponse( - this._client, + this.#client, request, responseReceived.response, extraInfo @@ -481,88 +477,88 @@ export class NetworkManager extends EventEmitter { this.emit(NetworkManagerEmittedEvents.Response, response); } - _onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void { - const request = this._networkEventManager.getRequest(event.requestId); + #onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void { + const request = this.#networkEventManager.getRequest(event.requestId); let extraInfo = null; if (request && !request._fromMemoryCache && event.hasExtraInfo) { - extraInfo = this._networkEventManager + extraInfo = this.#networkEventManager .responseExtraInfo(event.requestId) .shift(); if (!extraInfo) { // Wait until we get the corresponding ExtraInfo event. - this._networkEventManager.queueEventGroup(event.requestId, { + this.#networkEventManager.queueEventGroup(event.requestId, { responseReceivedEvent: event, }); return; } } - this._emitResponseEvent(event, extraInfo); + this.#emitResponseEvent(event, extraInfo); } - _onResponseReceivedExtraInfo( + #onResponseReceivedExtraInfo( event: Protocol.Network.ResponseReceivedExtraInfoEvent ): void { // We may have skipped a redirect response/request pair due to waiting for // this ExtraInfo event. If so, continue that work now that we have the // request. - const redirectInfo = this._networkEventManager.takeQueuedRedirectInfo( + const redirectInfo = this.#networkEventManager.takeQueuedRedirectInfo( event.requestId ); if (redirectInfo) { - this._networkEventManager.responseExtraInfo(event.requestId).push(event); - this._onRequest(redirectInfo.event, redirectInfo.fetchRequestId); + this.#networkEventManager.responseExtraInfo(event.requestId).push(event); + this.#onRequest(redirectInfo.event, redirectInfo.fetchRequestId); return; } // We may have skipped response and loading events because we didn't have // this ExtraInfo event yet. If so, emit those events now. - const queuedEvents = this._networkEventManager.getQueuedEventGroup( + const queuedEvents = this.#networkEventManager.getQueuedEventGroup( event.requestId ); if (queuedEvents) { - this._networkEventManager.forgetQueuedEventGroup(event.requestId); - this._emitResponseEvent(queuedEvents.responseReceivedEvent, event); + this.#networkEventManager.forgetQueuedEventGroup(event.requestId); + this.#emitResponseEvent(queuedEvents.responseReceivedEvent, event); if (queuedEvents.loadingFinishedEvent) { - this._emitLoadingFinished(queuedEvents.loadingFinishedEvent); + this.#emitLoadingFinished(queuedEvents.loadingFinishedEvent); } if (queuedEvents.loadingFailedEvent) { - this._emitLoadingFailed(queuedEvents.loadingFailedEvent); + this.#emitLoadingFailed(queuedEvents.loadingFailedEvent); } return; } // Wait until we get another event that can use this ExtraInfo event. - this._networkEventManager.responseExtraInfo(event.requestId).push(event); + this.#networkEventManager.responseExtraInfo(event.requestId).push(event); } - _forgetRequest(request: HTTPRequest, events: boolean): void { + #forgetRequest(request: HTTPRequest, events: boolean): void { const requestId = request._requestId; const interceptionId = request._interceptionId; - this._networkEventManager.forgetRequest(requestId); + this.#networkEventManager.forgetRequest(requestId); interceptionId !== undefined && - this._attemptedAuthentications.delete(interceptionId); + this.#attemptedAuthentications.delete(interceptionId); if (events) { - this._networkEventManager.forget(requestId); + this.#networkEventManager.forget(requestId); } } - _onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void { + #onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void { // If the response event for this request is still waiting on a // corresponding ExtraInfo event, then wait to emit this event too. - const queuedEvents = this._networkEventManager.getQueuedEventGroup( + const queuedEvents = this.#networkEventManager.getQueuedEventGroup( event.requestId ); if (queuedEvents) { queuedEvents.loadingFinishedEvent = event; } else { - this._emitLoadingFinished(event); + this.#emitLoadingFinished(event); } } - _emitLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void { - const request = this._networkEventManager.getRequest(event.requestId); + #emitLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void { + const request = this.#networkEventManager.getRequest(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 if (!request) return; @@ -570,32 +566,32 @@ export class NetworkManager extends EventEmitter { // Under certain conditions we never get the Network.responseReceived // event from protocol. @see https://crbug.com/883475 if (request.response()) request.response()?._resolveBody(null); - this._forgetRequest(request, true); + this.#forgetRequest(request, true); this.emit(NetworkManagerEmittedEvents.RequestFinished, request); } - _onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void { + #onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void { // If the response event for this request is still waiting on a // corresponding ExtraInfo event, then wait to emit this event too. - const queuedEvents = this._networkEventManager.getQueuedEventGroup( + const queuedEvents = this.#networkEventManager.getQueuedEventGroup( event.requestId ); if (queuedEvents) { queuedEvents.loadingFailedEvent = event; } else { - this._emitLoadingFailed(event); + this.#emitLoadingFailed(event); } } - _emitLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void { - const request = this._networkEventManager.getRequest(event.requestId); + #emitLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void { + const request = this.#networkEventManager.getRequest(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 if (!request) return; request._failureText = event.errorText; const response = request.response(); if (response) response._resolveBody(null); - this._forgetRequest(request, true); + this.#forgetRequest(request, true); this.emit(NetworkManagerEmittedEvents.RequestFailed, request); } } diff --git a/src/common/PDFOptions.ts b/src/common/PDFOptions.ts index 378d5c53..1b7b58df 100644 --- a/src/common/PDFOptions.ts +++ b/src/common/PDFOptions.ts @@ -182,17 +182,19 @@ export interface PaperFormatDimensions { /** * @internal */ -export const paperFormats: Record = - { - letter: { width: 8.5, height: 11 }, - legal: { width: 8.5, height: 14 }, - tabloid: { width: 11, height: 17 }, - ledger: { width: 17, height: 11 }, - a0: { width: 33.1, height: 46.8 }, - a1: { width: 23.4, height: 33.1 }, - a2: { width: 16.54, height: 23.4 }, - a3: { width: 11.7, height: 16.54 }, - a4: { width: 8.27, height: 11.7 }, - a5: { width: 5.83, height: 8.27 }, - a6: { width: 4.13, height: 5.83 }, - } as const; +export const _paperFormats: Record< + LowerCasePaperFormat, + PaperFormatDimensions +> = { + letter: { width: 8.5, height: 11 }, + legal: { width: 8.5, height: 14 }, + tabloid: { width: 11, height: 17 }, + ledger: { width: 17, height: 11 }, + a0: { width: 33.1, height: 46.8 }, + a1: { width: 23.4, height: 33.1 }, + a2: { width: 16.54, height: 23.4 }, + a3: { width: 11.7, height: 16.54 }, + a4: { width: 8.27, height: 11.7 }, + a5: { width: 5.83, height: 8.27 }, + a6: { width: 4.13, height: 5.83 }, +} as const; diff --git a/src/common/Page.ts b/src/common/Page.ts index 8aa815b0..e58e1ac8 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -47,7 +47,7 @@ import { debugError, helper, isErrorLike } from './helper.js'; import { HTTPRequest } from './HTTPRequest.js'; import { HTTPResponse } from './HTTPResponse.js'; import { Keyboard, Mouse, MouseButton, Touchscreen } from './Input.js'; -import { createJSHandle, ElementHandle, JSHandle } from './JSHandle.js'; +import { _createJSHandle, ElementHandle, JSHandle } from './JSHandle.js'; import { PuppeteerLifeCycleEvent } from './LifecycleWatcher.js'; import { Credentials, @@ -56,7 +56,7 @@ import { } from './NetworkManager.js'; import { LowerCasePaperFormat, - paperFormats, + _paperFormats, PDFOptions, } from './PDFOptions.js'; import { Viewport } from './PuppeteerViewport.js'; @@ -420,7 +420,7 @@ export class Page extends EventEmitter { /** * @internal */ - static async create( + static async _create( client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, @@ -433,35 +433,35 @@ export class Page extends EventEmitter { ignoreHTTPSErrors, screenshotTaskQueue ); - await page._initialize(); + await page.#initialize(); if (defaultViewport) await page.setViewport(defaultViewport); return page; } - private _closed = false; - private _client: CDPSession; - private _target: Target; - private _keyboard: Keyboard; - private _mouse: Mouse; - private _timeoutSettings = new TimeoutSettings(); - private _touchscreen: Touchscreen; - private _accessibility: Accessibility; - private _frameManager: FrameManager; - private _emulationManager: EmulationManager; - private _tracing: Tracing; - private _pageBindings = new Map(); - private _coverage: Coverage; - private _javascriptEnabled = true; - private _viewport: Viewport | null; - private _screenshotTaskQueue: TaskQueue; - private _workers = new Map(); + #closed = false; + #client: CDPSession; + #target: Target; + #keyboard: Keyboard; + #mouse: Mouse; + #timeoutSettings = new TimeoutSettings(); + #touchscreen: Touchscreen; + #accessibility: Accessibility; + #frameManager: FrameManager; + #emulationManager: EmulationManager; + #tracing: Tracing; + #pageBindings = new Map(); + #coverage: Coverage; + #javascriptEnabled = true; + #viewport: Viewport | null; + #screenshotTaskQueue: TaskQueue; + #workers = new Map(); // TODO: improve this typedef - it's a function that takes a file chooser or // something? - private _fileChooserInterceptors = new Set(); + #fileChooserInterceptors = new Set(); - private _disconnectPromise?: Promise; - private _userDragInterceptionEnabled = false; - private _handlerMap = new WeakMap(); + #disconnectPromise?: Promise; + #userDragInterceptionEnabled = false; + #handlerMap = new WeakMap(); /** * @internal @@ -473,23 +473,23 @@ export class Page extends EventEmitter { screenshotTaskQueue: TaskQueue ) { super(); - this._client = client; - this._target = target; - this._keyboard = new Keyboard(client); - this._mouse = new Mouse(client, this._keyboard); - this._touchscreen = new Touchscreen(client, this._keyboard); - this._accessibility = new Accessibility(client); - this._frameManager = new FrameManager( + this.#client = client; + this.#target = target; + this.#keyboard = new Keyboard(client); + this.#mouse = new Mouse(client, this.#keyboard); + this.#touchscreen = new Touchscreen(client, this.#keyboard); + this.#accessibility = new Accessibility(client); + this.#frameManager = new FrameManager( client, this, ignoreHTTPSErrors, - this._timeoutSettings + this.#timeoutSettings ); - this._emulationManager = new EmulationManager(client); - this._tracing = new Tracing(client); - this._coverage = new Coverage(client); - this._screenshotTaskQueue = screenshotTaskQueue; - this._viewport = null; + this.#emulationManager = new EmulationManager(client); + this.#tracing = new Tracing(client); + this.#coverage = new Coverage(client); + this.#screenshotTaskQueue = screenshotTaskQueue; + this.#viewport = null; client.on( 'Target.attachedToTarget', @@ -503,10 +503,10 @@ export class Page extends EventEmitter { const worker = new WebWorker( session, event.targetInfo.url, - this._addConsoleMessage.bind(this), - this._handleException.bind(this) + this.#addConsoleMessage.bind(this), + this.#handleException.bind(this) ); - this._workers.set(event.sessionId, worker); + this.#workers.set(event.sessionId, worker); this.emit(PageEmittedEvents.WorkerCreated, worker); break; case 'iframe': @@ -527,23 +527,23 @@ export class Page extends EventEmitter { } ); client.on('Target.detachedFromTarget', (event) => { - const worker = this._workers.get(event.sessionId); + const worker = this.#workers.get(event.sessionId); if (!worker) return; - this._workers.delete(event.sessionId); + this.#workers.delete(event.sessionId); this.emit(PageEmittedEvents.WorkerDestroyed, worker); }); - this._frameManager.on(FrameManagerEmittedEvents.FrameAttached, (event) => + this.#frameManager.on(FrameManagerEmittedEvents.FrameAttached, (event) => this.emit(PageEmittedEvents.FrameAttached, event) ); - this._frameManager.on(FrameManagerEmittedEvents.FrameDetached, (event) => + this.#frameManager.on(FrameManagerEmittedEvents.FrameDetached, (event) => this.emit(PageEmittedEvents.FrameDetached, event) ); - this._frameManager.on(FrameManagerEmittedEvents.FrameNavigated, (event) => + this.#frameManager.on(FrameManagerEmittedEvents.FrameNavigated, (event) => this.emit(PageEmittedEvents.FrameNavigated, event) ); - const networkManager = this._frameManager.networkManager(); + const networkManager = this.#frameManager.networkManager(); networkManager.on(NetworkManagerEmittedEvents.Request, (event) => this.emit(PageEmittedEvents.Request, event) ); @@ -560,51 +560,51 @@ export class Page extends EventEmitter { networkManager.on(NetworkManagerEmittedEvents.RequestFinished, (event) => this.emit(PageEmittedEvents.RequestFinished, event) ); - this._fileChooserInterceptors = new Set(); + this.#fileChooserInterceptors = new Set(); client.on('Page.domContentEventFired', () => this.emit(PageEmittedEvents.DOMContentLoaded) ); client.on('Page.loadEventFired', () => this.emit(PageEmittedEvents.Load)); - client.on('Runtime.consoleAPICalled', (event) => this._onConsoleAPI(event)); - client.on('Runtime.bindingCalled', (event) => this._onBindingCalled(event)); - client.on('Page.javascriptDialogOpening', (event) => this._onDialog(event)); + client.on('Runtime.consoleAPICalled', (event) => this.#onConsoleAPI(event)); + client.on('Runtime.bindingCalled', (event) => this.#onBindingCalled(event)); + client.on('Page.javascriptDialogOpening', (event) => this.#onDialog(event)); client.on('Runtime.exceptionThrown', (exception) => - this._handleException(exception.exceptionDetails) + this.#handleException(exception.exceptionDetails) ); - client.on('Inspector.targetCrashed', () => this._onTargetCrashed()); - client.on('Performance.metrics', (event) => this._emitMetrics(event)); - client.on('Log.entryAdded', (event) => this._onLogEntryAdded(event)); - client.on('Page.fileChooserOpened', (event) => this._onFileChooser(event)); - this._target._isClosedPromise.then(() => { + client.on('Inspector.targetCrashed', () => this.#onTargetCrashed()); + client.on('Performance.metrics', (event) => this.#emitMetrics(event)); + client.on('Log.entryAdded', (event) => this.#onLogEntryAdded(event)); + client.on('Page.fileChooserOpened', (event) => this.#onFileChooser(event)); + this.#target._isClosedPromise.then(() => { this.emit(PageEmittedEvents.Close); - this._closed = true; + this.#closed = true; }); } - private async _initialize(): Promise { + async #initialize(): Promise { await Promise.all([ - this._frameManager.initialize(), - this._client.send('Target.setAutoAttach', { + this.#frameManager.initialize(), + this.#client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: false, flatten: true, }), - this._client.send('Performance.enable'), - this._client.send('Log.enable'), + this.#client.send('Performance.enable'), + this.#client.send('Log.enable'), ]); } - private async _onFileChooser( + async #onFileChooser( event: Protocol.Page.FileChooserOpenedEvent ): Promise { - if (!this._fileChooserInterceptors.size) return; - const frame = this._frameManager.frame(event.frameId); + if (!this.#fileChooserInterceptors.size) return; + const frame = this.#frameManager.frame(event.frameId); assert(frame); const context = await frame.executionContext(); const element = await context._adoptBackendNodeId(event.backendNodeId); - const interceptors = Array.from(this._fileChooserInterceptors); - this._fileChooserInterceptors.clear(); + const interceptors = Array.from(this.#fileChooserInterceptors); + this.#fileChooserInterceptors.clear(); const fileChooser = new FileChooser(element, event); for (const interceptor of interceptors) interceptor.call(null, fileChooser); } @@ -613,14 +613,14 @@ export class Page extends EventEmitter { * @returns `true` if drag events are being intercepted, `false` otherwise. */ isDragInterceptionEnabled(): boolean { - return this._userDragInterceptionEnabled; + return this.#userDragInterceptionEnabled; } /** * @returns `true` if the page has JavaScript enabled, `false` otherwise. */ public isJavaScriptEnabled(): boolean { - return this._javascriptEnabled; + return this.#javascriptEnabled; } /** @@ -635,14 +635,14 @@ export class Page extends EventEmitter { ): EventEmitter { if (eventName === 'request') { const wrap = - this._handlerMap.get(handler) || + this.#handlerMap.get(handler) || ((event: HTTPRequest) => { event.enqueueInterceptAction(() => handler(event as PageEventObject[K]) ); }); - this._handlerMap.set(handler, wrap); + this.#handlerMap.set(handler, wrap); return super.on(eventName, wrap); } @@ -663,7 +663,7 @@ export class Page extends EventEmitter { handler: (event: PageEventObject[K]) => void ): EventEmitter { if (eventName === 'request') { - handler = this._handlerMap.get(handler) || handler; + handler = this.#handlerMap.get(handler) || handler; } return super.off(eventName, handler); @@ -694,15 +694,15 @@ export class Page extends EventEmitter { async waitForFileChooser( options: WaitTimeoutOptions = {} ): Promise { - if (!this._fileChooserInterceptors.size) - await this._client.send('Page.setInterceptFileChooserDialog', { + if (!this.#fileChooserInterceptors.size) + await this.#client.send('Page.setInterceptFileChooserDialog', { enabled: true, }); - const { timeout = this._timeoutSettings.timeout() } = options; + const { timeout = this.#timeoutSettings.timeout() } = options; let callback!: (value: FileChooser | PromiseLike) => void; const promise = new Promise((x) => (callback = x)); - this._fileChooserInterceptors.add(callback); + this.#fileChooserInterceptors.add(callback); return helper .waitWithTimeout( promise, @@ -710,7 +710,7 @@ export class Page extends EventEmitter { timeout ) .catch((error) => { - this._fileChooserInterceptors.delete(callback); + this.#fileChooserInterceptors.delete(callback); throw error; }); } @@ -739,7 +739,7 @@ export class Page extends EventEmitter { throw new Error( `Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.` ); - await this._client.send('Emulation.setGeolocationOverride', { + await this.#client.send('Emulation.setGeolocationOverride', { longitude, latitude, accuracy, @@ -750,38 +750,37 @@ export class Page extends EventEmitter { * @returns A target this page was created from. */ target(): Target { - return this._target; + return this.#target; } /** - * Get the CDP session client the page belongs to. * @internal */ - client(): CDPSession { - return this._client; + _client(): CDPSession { + return this.#client; } /** * Get the browser the page belongs to. */ browser(): Browser { - return this._target.browser(); + return this.#target.browser(); } /** * Get the browser context that the page belongs to. */ browserContext(): BrowserContext { - return this._target.browserContext(); + return this.#target.browserContext(); } - private _onTargetCrashed(): void { + #onTargetCrashed(): void { this.emit('error', new Error('Page crashed!')); } - private _onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void { + #onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void { const { level, text, args, source, url, lineNumber } = event.entry; - if (args) args.map((arg) => helper.releaseObject(this._client, arg)); + if (args) args.map((arg) => helper.releaseObject(this.#client, arg)); if (source !== 'worker') this.emit( PageEmittedEvents.Console, @@ -795,34 +794,34 @@ export class Page extends EventEmitter { * Page is guaranteed to have a main frame which persists during navigations. */ mainFrame(): Frame { - return this._frameManager.mainFrame(); + return this.#frameManager.mainFrame(); } get keyboard(): Keyboard { - return this._keyboard; + return this.#keyboard; } get touchscreen(): Touchscreen { - return this._touchscreen; + return this.#touchscreen; } get coverage(): Coverage { - return this._coverage; + return this.#coverage; } get tracing(): Tracing { - return this._tracing; + return this.#tracing; } get accessibility(): Accessibility { - return this._accessibility; + return this.#accessibility; } /** * @returns An array of all frames attached to the page. */ frames(): Frame[] { - return this._frameManager.frames(); + return this.#frameManager.frames(); } /** @@ -834,7 +833,7 @@ export class Page extends EventEmitter { * NOTE: This does not contain ServiceWorkers */ workers(): WebWorker[] { - return Array.from(this._workers.values()); + return Array.from(this.#workers.values()); } /** @@ -870,7 +869,7 @@ export class Page extends EventEmitter { * NOTE: Enabling request interception disables page caching. */ async setRequestInterception(value: boolean): Promise { - return this._frameManager.networkManager().setRequestInterception(value); + return this.#frameManager.networkManager().setRequestInterception(value); } /** @@ -882,8 +881,8 @@ export class Page extends EventEmitter { * on the page, which can then be used to simulate drag-and-drop. */ async setDragInterception(enabled: boolean): Promise { - this._userDragInterceptionEnabled = enabled; - return this._client.send('Input.setInterceptDrags', { enabled }); + this.#userDragInterceptionEnabled = enabled; + return this.#client.send('Input.setInterceptDrags', { enabled }); } /** @@ -894,7 +893,7 @@ export class Page extends EventEmitter { * (#pageemulatenetworkconditionsnetworkconditions) */ setOfflineMode(enabled: boolean): Promise { - return this._frameManager.networkManager().setOfflineMode(enabled); + return this.#frameManager.networkManager().setOfflineMode(enabled); } /** @@ -921,7 +920,7 @@ export class Page extends EventEmitter { emulateNetworkConditions( networkConditions: NetworkConditions | null ): Promise { - return this._frameManager + return this.#frameManager .networkManager() .emulateNetworkConditions(networkConditions); } @@ -944,14 +943,14 @@ export class Page extends EventEmitter { * @param timeout - Maximum navigation time in milliseconds. */ setDefaultNavigationTimeout(timeout: number): void { - this._timeoutSettings.setDefaultNavigationTimeout(timeout); + this.#timeoutSettings.setDefaultNavigationTimeout(timeout); } /** * @param timeout - Maximum time in milliseconds. */ setDefaultTimeout(timeout: number): void { - this._timeoutSettings.setDefaultTimeout(timeout); + this.#timeoutSettings.setDefaultTimeout(timeout); } /** @@ -1247,7 +1246,7 @@ export class Page extends EventEmitter { */ async cookies(...urls: string[]): Promise { const originalCookies = ( - await this._client.send('Network.getCookies', { + await this.#client.send('Network.getCookies', { urls: urls.length ? urls : [this.url()], }) ).cookies; @@ -1271,7 +1270,7 @@ export class Page extends EventEmitter { for (const cookie of cookies) { const item = Object.assign({}, cookie); if (!cookie.url && pageURL.startsWith('http')) item.url = pageURL; - await this._client.send('Network.deleteCookies', item); + await this.#client.send('Network.deleteCookies', item); } } @@ -1299,7 +1298,7 @@ export class Page extends EventEmitter { }); await this.deleteCookie(...items); if (items.length) - await this._client.send('Network.setCookies', { cookies: items }); + await this.#client.send('Network.setCookies', { cookies: items }); } /** @@ -1396,7 +1395,7 @@ export class Page extends EventEmitter { name: string, puppeteerFunction: Function | { default: Function } ): Promise { - if (this._pageBindings.has(name)) + if (this.#pageBindings.has(name)) throw new Error( `Failed to add page binding with name ${name}: window['${name}'] already exists!` ); @@ -1412,11 +1411,11 @@ export class Page extends EventEmitter { ); } - this._pageBindings.set(name, exposedFunction); + this.#pageBindings.set(name, exposedFunction); const expression = helper.pageBindingInitString('exposedFun', name); - await this._client.send('Runtime.addBinding', { name: name }); - await this._client.send('Page.addScriptToEvaluateOnNewDocument', { + await this.#client.send('Runtime.addBinding', { name: name }); + await this.#client.send('Page.addScriptToEvaluateOnNewDocument', { source: expression, }); await Promise.all( @@ -1429,7 +1428,7 @@ export class Page extends EventEmitter { * @remarks To disable authentication, pass `null`. */ async authenticate(credentials: Credentials): Promise { - return this._frameManager.networkManager().authenticate(credentials); + return this.#frameManager.networkManager().authenticate(credentials); } /** @@ -1443,7 +1442,7 @@ export class Page extends EventEmitter { * @returns */ async setExtraHTTPHeaders(headers: Record): Promise { - return this._frameManager.networkManager().setExtraHTTPHeaders(headers); + return this.#frameManager.networkManager().setExtraHTTPHeaders(headers); } /** @@ -1456,7 +1455,7 @@ export class Page extends EventEmitter { userAgent: string, userAgentMetadata?: Protocol.Emulation.UserAgentMetadata ): Promise { - return this._frameManager + return this.#frameManager .networkManager() .setUserAgent(userAgent, userAgentMetadata); } @@ -1496,20 +1495,18 @@ export class Page extends EventEmitter { * in seconds since an arbitrary point in the past. */ async metrics(): Promise { - const response = await this._client.send('Performance.getMetrics'); - return this._buildMetricsObject(response.metrics); + const response = await this.#client.send('Performance.getMetrics'); + return this.#buildMetricsObject(response.metrics); } - private _emitMetrics(event: Protocol.Performance.MetricsEvent): void { + #emitMetrics(event: Protocol.Performance.MetricsEvent): void { this.emit(PageEmittedEvents.Metrics, { title: event.title, - metrics: this._buildMetricsObject(event.metrics), + metrics: this.#buildMetricsObject(event.metrics), }); } - private _buildMetricsObject( - metrics?: Protocol.Performance.Metric[] - ): Metrics { + #buildMetricsObject(metrics?: Protocol.Performance.Metric[]): Metrics { const result: Record< Protocol.Performance.Metric['name'], Protocol.Performance.Metric['value'] @@ -1522,16 +1519,14 @@ export class Page extends EventEmitter { return result; } - private _handleException( - exceptionDetails: Protocol.Runtime.ExceptionDetails - ): void { + #handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails): void { const message = helper.getExceptionMessage(exceptionDetails); const err = new Error(message); err.stack = ''; // Don't report clientside error with a node stack attached this.emit(PageEmittedEvents.PageError, err); } - private async _onConsoleAPI( + async #onConsoleAPI( event: Protocol.Runtime.ConsoleAPICalledEvent ): Promise { if (event.executionContextId === 0) { @@ -1550,15 +1545,15 @@ export class Page extends EventEmitter { // @see https://github.com/puppeteer/puppeteer/issues/3865 return; } - const context = this._frameManager.executionContextById( + const context = this.#frameManager.executionContextById( event.executionContextId, - this._client + this.#client ); - const values = event.args.map((arg) => createJSHandle(context, arg)); - this._addConsoleMessage(event.type, values, event.stackTrace); + const values = event.args.map((arg) => _createJSHandle(context, arg)); + this.#addConsoleMessage(event.type, values, event.stackTrace); } - private async _onBindingCalled( + async #onBindingCalled( event: Protocol.Runtime.BindingCalledEvent ): Promise { let payload: { type: string; name: string; seq: number; args: unknown[] }; @@ -1570,10 +1565,10 @@ export class Page extends EventEmitter { return; } const { type, name, seq, args } = payload; - if (type !== 'exposedFun' || !this._pageBindings.has(name)) return; + if (type !== 'exposedFun' || !this.#pageBindings.has(name)) return; let expression = null; try { - const pageBinding = this._pageBindings.get(name); + const pageBinding = this.#pageBindings.get(name); assert(pageBinding); const result = await pageBinding(...args); expression = helper.pageBindingDeliverResultString(name, seq, result); @@ -1592,7 +1587,7 @@ export class Page extends EventEmitter { error ); } - this._client + this.#client .send('Runtime.evaluate', { expression, contextId: event.executionContextId, @@ -1600,7 +1595,7 @@ export class Page extends EventEmitter { .catch(debugError); } - private _addConsoleMessage( + #addConsoleMessage( eventType: ConsoleMessageType, args: JSHandle[], stackTrace?: Protocol.Runtime.StackTrace @@ -1634,7 +1629,7 @@ export class Page extends EventEmitter { this.emit(PageEmittedEvents.Console, message); } - private _onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void { + #onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void { let dialogType = null; const validDialogTypes = new Set([ 'alert', @@ -1649,7 +1644,7 @@ export class Page extends EventEmitter { assert(dialogType, 'Unknown javascript dialog type: ' + event.type); const dialog = new Dialog( - this._client, + this.#client, dialogType, event.message, event.defaultPrompt @@ -1660,15 +1655,15 @@ export class Page extends EventEmitter { /** * Resets default white background */ - private async _resetDefaultBackgroundColor() { - await this._client.send('Emulation.setDefaultBackgroundColorOverride'); + async #resetDefaultBackgroundColor() { + await this.#client.send('Emulation.setDefaultBackgroundColorOverride'); } /** * Hides default white background */ - private async _setTransparentBackgroundColor(): Promise { - await this._client.send('Emulation.setDefaultBackgroundColorOverride', { + async #setTransparentBackgroundColor(): Promise { + await this.#client.send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 }, }); } @@ -1684,7 +1679,7 @@ export class Page extends EventEmitter { } async content(): Promise { - return await this._frameManager.mainFrame().content(); + return await this.#frameManager.mainFrame().content(); } /** @@ -1714,7 +1709,7 @@ export class Page extends EventEmitter { * more than 2 network connections for at least `500` ms. */ async setContent(html: string, options: WaitForOptions = {}): Promise { - await this._frameManager.mainFrame().setContent(html, options); + await this.#frameManager.mainFrame().setContent(html, options); } /** @@ -1777,7 +1772,7 @@ export class Page extends EventEmitter { url: string, options: WaitForOptions & { referer?: string } = {} ): Promise { - return await this._frameManager.mainFrame().goto(url, options); + return await this.#frameManager.mainFrame().goto(url, options); } /** @@ -1811,7 +1806,7 @@ export class Page extends EventEmitter { async reload(options?: WaitForOptions): Promise { const result = await Promise.all([ this.waitForNavigation(options), - this._client.send('Page.reload'), + this.#client.send('Page.reload'), ]); return result[0]; @@ -1844,17 +1839,17 @@ export class Page extends EventEmitter { async waitForNavigation( options: WaitForOptions = {} ): Promise { - return await this._frameManager.mainFrame().waitForNavigation(options); + return await this.#frameManager.mainFrame().waitForNavigation(options); } - private _sessionClosePromise(): Promise { - if (!this._disconnectPromise) - this._disconnectPromise = new Promise((fulfill) => - this._client.once(CDPSessionEmittedEvents.Disconnected, () => + #sessionClosePromise(): Promise { + if (!this.#disconnectPromise) + this.#disconnectPromise = new Promise((fulfill) => + this.#client.once(CDPSessionEmittedEvents.Disconnected, () => fulfill(new Error('Target closed')) ) ); - return this._disconnectPromise; + return this.#disconnectPromise; } /** @@ -1886,9 +1881,9 @@ export class Page extends EventEmitter { urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise), options: { timeout?: number } = {} ): Promise { - const { timeout = this._timeoutSettings.timeout() } = options; + const { timeout = this.#timeoutSettings.timeout() } = options; return helper.waitForEvent( - this._frameManager.networkManager(), + this.#frameManager.networkManager(), NetworkManagerEmittedEvents.Request, (request) => { if (helper.isString(urlOrPredicate)) @@ -1898,7 +1893,7 @@ export class Page extends EventEmitter { return false; }, timeout, - this._sessionClosePromise() + this.#sessionClosePromise() ); } @@ -1933,9 +1928,9 @@ export class Page extends EventEmitter { | ((res: HTTPResponse) => boolean | Promise), options: { timeout?: number } = {} ): Promise { - const { timeout = this._timeoutSettings.timeout() } = options; + const { timeout = this.#timeoutSettings.timeout() } = options; return helper.waitForEvent( - this._frameManager.networkManager(), + this.#frameManager.networkManager(), NetworkManagerEmittedEvents.Response, async (response) => { if (helper.isString(urlOrPredicate)) @@ -1945,7 +1940,7 @@ export class Page extends EventEmitter { return false; }, timeout, - this._sessionClosePromise() + this.#sessionClosePromise() ); } @@ -1956,10 +1951,10 @@ export class Page extends EventEmitter { async waitForNetworkIdle( options: { idleTime?: number; timeout?: number } = {} ): Promise { - const { idleTime = 500, timeout = this._timeoutSettings.timeout() } = + const { idleTime = 500, timeout = this.#timeoutSettings.timeout() } = options; - const networkManager = this._frameManager.networkManager(); + const networkManager = this.#frameManager.networkManager(); let idleResolveCallback: () => void; const idlePromise = new Promise((resolve) => { @@ -2009,7 +2004,7 @@ export class Page extends EventEmitter { await Promise.race([ idlePromise, ...eventPromises, - this._sessionClosePromise(), + this.#sessionClosePromise(), ]).then( (r) => { cleanup(); @@ -2043,7 +2038,7 @@ export class Page extends EventEmitter { urlOrPredicate: string | ((frame: Frame) => boolean | Promise), options: { timeout?: number } = {} ): Promise { - const { timeout = this._timeoutSettings.timeout() } = options; + const { timeout = this.#timeoutSettings.timeout() } = options; let predicate: (frame: Frame) => Promise; if (helper.isString(urlOrPredicate)) { @@ -2061,18 +2056,18 @@ export class Page extends EventEmitter { const eventRace: Promise = Promise.race([ helper.waitForEvent( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameAttached, predicate, timeout, - this._sessionClosePromise() + this.#sessionClosePromise() ), helper.waitForEvent( - this._frameManager, + this.#frameManager, FrameManagerEmittedEvents.FrameNavigated, predicate, timeout, - this._sessionClosePromise() + this.#sessionClosePromise() ), ...this.frames().map(async (frame) => { if (await predicate(frame)) { @@ -2114,7 +2109,7 @@ export class Page extends EventEmitter { * more than 2 network connections for at least `500` ms. */ async goBack(options: WaitForOptions = {}): Promise { - return this._go(-1, options); + return this.#go(-1, options); } /** @@ -2146,19 +2141,19 @@ export class Page extends EventEmitter { * more than 2 network connections for at least `500` ms. */ async goForward(options: WaitForOptions = {}): Promise { - return this._go(+1, options); + return this.#go(+1, options); } - private async _go( + async #go( delta: number, options: WaitForOptions ): Promise { - const history = await this._client.send('Page.getNavigationHistory'); + const history = await this.#client.send('Page.getNavigationHistory'); const entry = history.entries[history.currentIndex + delta]; if (!entry) return null; const result = await Promise.all([ this.waitForNavigation(options), - this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }), + this.#client.send('Page.navigateToHistoryEntry', { entryId: entry.id }), ]); return result[0]; } @@ -2167,7 +2162,7 @@ export class Page extends EventEmitter { * Brings page to front (activates tab). */ async bringToFront(): Promise { - await this._client.send('Page.bringToFront'); + await this.#client.send('Page.bringToFront'); } /** @@ -2211,9 +2206,9 @@ export class Page extends EventEmitter { * It will take full effect on the next navigation. */ async setJavaScriptEnabled(enabled: boolean): Promise { - if (this._javascriptEnabled === enabled) return; - this._javascriptEnabled = enabled; - await this._client.send('Emulation.setScriptExecutionDisabled', { + if (this.#javascriptEnabled === enabled) return; + this.#javascriptEnabled = enabled; + await this.#client.send('Emulation.setScriptExecutionDisabled', { value: !enabled, }); } @@ -2227,7 +2222,7 @@ export class Page extends EventEmitter { * before navigating to the domain. */ async setBypassCSP(enabled: boolean): Promise { - await this._client.send('Page.setBypassCSP', { enabled }); + await this.#client.send('Page.setBypassCSP', { enabled }); } /** @@ -2259,7 +2254,7 @@ export class Page extends EventEmitter { type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type ); - await this._client.send('Emulation.setEmulatedMedia', { + await this.#client.send('Emulation.setEmulatedMedia', { media: type || '', }); } @@ -2273,7 +2268,7 @@ export class Page extends EventEmitter { factor === null || factor >= 1, 'Throttling rate should be greater or equal to 1' ); - await this._client.send('Emulation.setCPUThrottlingRate', { + await this.#client.send('Emulation.setCPUThrottlingRate', { rate: factor !== null ? factor : 1, }); } @@ -2331,7 +2326,7 @@ export class Page extends EventEmitter { * ``` */ async emulateMediaFeatures(features?: MediaFeature[]): Promise { - if (!features) await this._client.send('Emulation.setEmulatedMedia', {}); + if (!features) await this.#client.send('Emulation.setEmulatedMedia', {}); if (Array.isArray(features)) { for (const mediaFeature of features) { const name = mediaFeature.name; @@ -2342,7 +2337,7 @@ export class Page extends EventEmitter { 'Unsupported media feature: ' + name ); } - await this._client.send('Emulation.setEmulatedMedia', { + await this.#client.send('Emulation.setEmulatedMedia', { features: features, }); } @@ -2356,7 +2351,7 @@ export class Page extends EventEmitter { */ async emulateTimezone(timezoneId?: string): Promise { try { - await this._client.send('Emulation.setTimezoneOverride', { + await this.#client.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId || '', }); } catch (error) { @@ -2390,12 +2385,12 @@ export class Page extends EventEmitter { isScreenUnlocked: boolean; }): Promise { if (overrides) { - await this._client.send('Emulation.setIdleOverride', { + await this.#client.send('Emulation.setIdleOverride', { isUserActive: overrides.isUserActive, isScreenUnlocked: overrides.isScreenUnlocked, }); } else { - await this._client.send('Emulation.clearIdleOverride'); + await this.#client.send('Emulation.clearIdleOverride'); } } @@ -2444,7 +2439,7 @@ export class Page extends EventEmitter { !type || visionDeficiencies.has(type), `Unsupported vision deficiency: ${type}` ); - await this._client.send('Emulation.setEmulatedVisionDeficiency', { + await this.#client.send('Emulation.setEmulatedVisionDeficiency', { type: type || 'none', }); } catch (error) { @@ -2492,8 +2487,8 @@ export class Page extends EventEmitter { * set the isMobile or hasTouch properties. */ async setViewport(viewport: Viewport): Promise { - const needsReload = await this._emulationManager.emulateViewport(viewport); - this._viewport = viewport; + const needsReload = await this.#emulationManager.emulateViewport(viewport); + this.#viewport = viewport; if (needsReload) await this.reload(); } @@ -2517,7 +2512,7 @@ export class Page extends EventEmitter { * `false`. */ viewport(): Viewport | null { - return this._viewport; + return this.#viewport; } /** @@ -2572,7 +2567,7 @@ export class Page extends EventEmitter { pageFunction: T, ...args: SerializableOrJSHandle[] ): Promise>> { - return this._frameManager.mainFrame().evaluate(pageFunction, ...args); + return this.#frameManager.mainFrame().evaluate(pageFunction, ...args); } /** @@ -2611,7 +2606,7 @@ export class Page extends EventEmitter { ...args: unknown[] ): Promise { const source = helper.evaluationString(pageFunction, ...args); - await this._client.send('Page.addScriptToEvaluateOnNewDocument', { + await this.#client.send('Page.addScriptToEvaluateOnNewDocument', { source, }); } @@ -2622,7 +2617,7 @@ export class Page extends EventEmitter { * @param enabled - sets the `enabled` state of cache */ async setCacheEnabled(enabled = true): Promise { - await this._frameManager.networkManager().setCacheEnabled(enabled); + await this.#frameManager.networkManager().setCacheEnabled(enabled); } /** @@ -2756,17 +2751,17 @@ export class Page extends EventEmitter { 'Expected options.clip.height not to be 0.' ); } - return this._screenshotTaskQueue.postTask(() => - this._screenshotTask(screenshotType, options) + return this.#screenshotTaskQueue.postTask(() => + this.#screenshotTask(screenshotType, options) ); } - private async _screenshotTask( + async #screenshotTask( format: Protocol.Page.CaptureScreenshotRequestFormat, options: ScreenshotOptions = {} ): Promise { - await this._client.send('Target.activateTarget', { - targetId: this._target._targetId, + await this.#client.send('Target.activateTarget', { + targetId: this.#target._targetId, }); let clip = options.clip ? processClip(options.clip) : undefined; let { captureBeyondViewport = true } = options; @@ -2774,7 +2769,7 @@ export class Page extends EventEmitter { typeof captureBeyondViewport === 'boolean' ? captureBeyondViewport : true; if (options.fullPage) { - const metrics = await this._client.send('Page.getLayoutMetrics'); + const metrics = await this.#client.send('Page.getLayoutMetrics'); // Fallback to `contentSize` in case of using Firefox. const { width, height } = metrics.cssContentSize || metrics.contentSize; @@ -2786,12 +2781,12 @@ export class Page extends EventEmitter { isMobile = false, deviceScaleFactor = 1, isLandscape = false, - } = this._viewport || {}; + } = this.#viewport || {}; const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' }; - await this._client.send('Emulation.setDeviceMetricsOverride', { + await this.#client.send('Emulation.setDeviceMetricsOverride', { mobile: isMobile, width, height, @@ -2803,21 +2798,21 @@ export class Page extends EventEmitter { const shouldSetDefaultBackground = options.omitBackground && (format === 'png' || format === 'webp'); if (shouldSetDefaultBackground) { - await this._setTransparentBackgroundColor(); + await this.#setTransparentBackgroundColor(); } - const result = await this._client.send('Page.captureScreenshot', { + const result = await this.#client.send('Page.captureScreenshot', { format, quality: options.quality, clip, captureBeyondViewport, }); if (shouldSetDefaultBackground) { - await this._resetDefaultBackgroundColor(); + await this.#resetDefaultBackgroundColor(); } - if (options.fullPage && this._viewport) - await this.setViewport(this._viewport); + if (options.fullPage && this.#viewport) + await this.setViewport(this.#viewport); const buffer = options.encoding === 'base64' @@ -2887,7 +2882,7 @@ export class Page extends EventEmitter { let paperHeight = 11; if (options.format) { const format = - paperFormats[options.format.toLowerCase() as LowerCasePaperFormat]; + _paperFormats[options.format.toLowerCase() as LowerCasePaperFormat]; assert(format, 'Unknown paper format: ' + options.format); paperWidth = format.width; paperHeight = format.height; @@ -2903,10 +2898,10 @@ export class Page extends EventEmitter { const marginRight = convertPrintParameterToInches(margin.right) || 0; if (omitBackground) { - await this._setTransparentBackgroundColor(); + await this.#setTransparentBackgroundColor(); } - const printCommandPromise = this._client.send('Page.printToPDF', { + const printCommandPromise = this.#client.send('Page.printToPDF', { transferMode: 'ReturnAsStream', landscape, displayHeaderFooter, @@ -2931,11 +2926,11 @@ export class Page extends EventEmitter { ); if (omitBackground) { - await this._resetDefaultBackgroundColor(); + await this.#resetDefaultBackgroundColor(); } assert(result.stream, '`stream` is missing from `Page.printToPDF'); - return helper.getReadableFromProtocolStream(this._client, result.stream); + return helper.getReadableFromProtocolStream(this.#client, result.stream); } /** @@ -2962,18 +2957,19 @@ export class Page extends EventEmitter { async close( options: { runBeforeUnload?: boolean } = { runBeforeUnload: undefined } ): Promise { + const connection = this.#client.connection(); assert( - !!this._client._connection, + connection, 'Protocol error: Connection closed. Most likely the page has been closed.' ); const runBeforeUnload = !!options.runBeforeUnload; if (runBeforeUnload) { - await this._client.send('Page.close'); + await this.#client.send('Page.close'); } else { - await this._client._connection.send('Target.closeTarget', { - targetId: this._target._targetId, + await connection.send('Target.closeTarget', { + targetId: this.#target._targetId, }); - await this._target._isClosedPromise; + await this.#target._isClosedPromise; } } @@ -2982,11 +2978,11 @@ export class Page extends EventEmitter { * @returns */ isClosed(): boolean { - return this._closed; + return this.#closed; } get mouse(): Mouse { - return this._mouse; + return this.#mouse; } /** diff --git a/src/common/Puppeteer.ts b/src/common/Puppeteer.ts index 327ac102..a576790b 100644 --- a/src/common/Puppeteer.ts +++ b/src/common/Puppeteer.ts @@ -15,17 +15,20 @@ */ import { puppeteerErrors, PuppeteerErrors } from './Errors.js'; import { ConnectionTransport } from './ConnectionTransport.js'; -import { devicesMap, DevicesMap } from './DeviceDescriptors.js'; +import { _devicesMap, DevicesMap } from './DeviceDescriptors.js'; import { Browser } from './Browser.js'; import { - registerCustomQueryHandler, - unregisterCustomQueryHandler, - customQueryHandlerNames, - clearCustomQueryHandlers, + _registerCustomQueryHandler, + _unregisterCustomQueryHandler, + _customQueryHandlerNames, + _clearCustomQueryHandlers, CustomQueryHandler, } from './QueryHandler.js'; import { Product } from './Product.js'; -import { connectToBrowser, BrowserConnectOptions } from './BrowserConnector.js'; +import { + _connectToBrowser, + BrowserConnectOptions, +} from './BrowserConnector.js'; import { PredefinedNetworkConditions, networkConditions, @@ -33,6 +36,7 @@ import { /** * Settings that are common to the Puppeteer class, regardless of environment. + * * @internal */ export interface CommonPuppeteerSettings { @@ -85,7 +89,7 @@ export class Puppeteer { * @returns Promise which resolves to browser instance. */ connect(options: ConnectOptions): Promise { - return connectToBrowser(options); + return _connectToBrowser(options); } /** @@ -110,7 +114,7 @@ export class Puppeteer { * */ get devices(): DevicesMap { - return devicesMap; + return _devicesMap; } /** @@ -182,27 +186,27 @@ export class Puppeteer { name: string, queryHandler: CustomQueryHandler ): void { - registerCustomQueryHandler(name, queryHandler); + _registerCustomQueryHandler(name, queryHandler); } /** * @param name - The name of the query handler to unregistered. */ unregisterCustomQueryHandler(name: string): void { - unregisterCustomQueryHandler(name); + _unregisterCustomQueryHandler(name); } /** * @returns a list with the names of all registered custom query handlers. */ customQueryHandlerNames(): string[] { - return customQueryHandlerNames(); + return _customQueryHandlerNames(); } /** * Clears all registered handlers. */ clearCustomQueryHandlers(): void { - clearCustomQueryHandlers(); + _clearCustomQueryHandlers(); } } diff --git a/src/common/QueryHandler.ts b/src/common/QueryHandler.ts index 3ba97dc9..650c09cf 100644 --- a/src/common/QueryHandler.ts +++ b/src/common/QueryHandler.ts @@ -16,7 +16,7 @@ import { WaitForSelectorOptions, DOMWorld } from './DOMWorld.js'; import { ElementHandle, JSHandle } from './JSHandle.js'; -import { ariaHandler } from './AriaQueryHandler.js'; +import { _ariaHandler } from './AriaQueryHandler.js'; /** * @internal @@ -76,7 +76,7 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler { domWorld: DOMWorld, selector: string, options: WaitForSelectorOptions - ) => domWorld.waitForSelectorInPage(queryOne, selector, options); + ) => domWorld._waitForSelectorInPage(queryOne, selector, options); } if (handler.queryAll) { @@ -161,20 +161,20 @@ const pierceHandler = makeQueryHandler({ }, }); -const _builtInHandlers = new Map([ - ['aria', ariaHandler], +const builtInHandlers = new Map([ + ['aria', _ariaHandler], ['pierce', pierceHandler], ]); -const _queryHandlers = new Map(_builtInHandlers); +const queryHandlers = new Map(builtInHandlers); /** * @internal */ -export function registerCustomQueryHandler( +export function _registerCustomQueryHandler( name: string, handler: CustomQueryHandler ): void { - if (_queryHandlers.get(name)) + if (queryHandlers.get(name)) throw new Error(`A custom query handler named "${name}" already exists`); const isValidName = /^[a-zA-Z]+$/.test(name); @@ -183,38 +183,36 @@ export function registerCustomQueryHandler( const internalHandler = makeQueryHandler(handler); - _queryHandlers.set(name, internalHandler); + queryHandlers.set(name, internalHandler); } /** * @internal */ -export function unregisterCustomQueryHandler(name: string): void { - if (_queryHandlers.has(name) && !_builtInHandlers.has(name)) { - _queryHandlers.delete(name); +export function _unregisterCustomQueryHandler(name: string): void { + if (queryHandlers.has(name) && !builtInHandlers.has(name)) { + queryHandlers.delete(name); } } /** * @internal */ -export function customQueryHandlerNames(): string[] { - return [..._queryHandlers.keys()].filter( - (name) => !_builtInHandlers.has(name) - ); +export function _customQueryHandlerNames(): string[] { + return [...queryHandlers.keys()].filter((name) => !builtInHandlers.has(name)); } /** * @internal */ -export function clearCustomQueryHandlers(): void { - customQueryHandlerNames().forEach(unregisterCustomQueryHandler); +export function _clearCustomQueryHandlers(): void { + _customQueryHandlerNames().forEach(_unregisterCustomQueryHandler); } /** * @internal */ -export function getQueryHandlerAndSelector(selector: string): { +export function _getQueryHandlerAndSelector(selector: string): { updatedSelector: string; queryHandler: InternalQueryHandler; } { @@ -225,7 +223,7 @@ export function getQueryHandlerAndSelector(selector: string): { const index = selector.indexOf('/'); const name = selector.slice(0, index); const updatedSelector = selector.slice(index + 1); - const queryHandler = _queryHandlers.get(name); + const queryHandler = queryHandlers.get(name); if (!queryHandler) throw new Error( `Query set to use "${name}", but no query handler of that name was found` diff --git a/src/common/SecurityDetails.ts b/src/common/SecurityDetails.ts index aceba1a3..770b6e64 100644 --- a/src/common/SecurityDetails.ts +++ b/src/common/SecurityDetails.ts @@ -23,30 +23,30 @@ import { Protocol } from 'devtools-protocol'; * @public */ export class SecurityDetails { - private _subjectName: string; - private _issuer: string; - private _validFrom: number; - private _validTo: number; - private _protocol: string; - private _sanList: string[]; + #subjectName: string; + #issuer: string; + #validFrom: number; + #validTo: number; + #protocol: string; + #sanList: string[]; /** * @internal */ constructor(securityPayload: Protocol.Network.SecurityDetails) { - this._subjectName = securityPayload.subjectName; - this._issuer = securityPayload.issuer; - this._validFrom = securityPayload.validFrom; - this._validTo = securityPayload.validTo; - this._protocol = securityPayload.protocol; - this._sanList = securityPayload.sanList; + this.#subjectName = securityPayload.subjectName; + this.#issuer = securityPayload.issuer; + this.#validFrom = securityPayload.validFrom; + this.#validTo = securityPayload.validTo; + this.#protocol = securityPayload.protocol; + this.#sanList = securityPayload.sanList; } /** * @returns The name of the issuer of the certificate. */ issuer(): string { - return this._issuer; + return this.#issuer; } /** @@ -54,7 +54,7 @@ export class SecurityDetails { * marking the start of the certificate's validity. */ validFrom(): number { - return this._validFrom; + return this.#validFrom; } /** @@ -62,27 +62,27 @@ export class SecurityDetails { * marking the end of the certificate's validity. */ validTo(): number { - return this._validTo; + return this.#validTo; } /** * @returns The security protocol being used, e.g. "TLS 1.2". */ protocol(): string { - return this._protocol; + return this.#protocol; } /** * @returns The name of the subject to which the certificate was issued. */ subjectName(): string { - return this._subjectName; + return this.#subjectName; } /** * @returns The list of {@link https://en.wikipedia.org/wiki/Subject_Alternative_Name | subject alternative names (SANs)} of the certificate. */ subjectAlternativeNames(): string[] { - return this._sanList; + return this.#sanList; } } diff --git a/src/common/Target.ts b/src/common/Target.ts index d36232e2..7bd8e0cb 100644 --- a/src/common/Target.ts +++ b/src/common/Target.ts @@ -26,15 +26,15 @@ import { TaskQueue } from './TaskQueue.js'; * @public */ export class Target { - private _targetInfo: Protocol.Target.TargetInfo; - private _browserContext: BrowserContext; + #browserContext: BrowserContext; + #targetInfo: Protocol.Target.TargetInfo; + #sessionFactory: () => Promise; + #ignoreHTTPSErrors: boolean; + #defaultViewport?: Viewport; + #pagePromise?: Promise; + #workerPromise?: Promise; + #screenshotTaskQueue: TaskQueue; - private _sessionFactory: () => Promise; - private _ignoreHTTPSErrors: boolean; - private _defaultViewport?: Viewport; - private _pagePromise?: Promise; - private _workerPromise?: Promise; - private _screenshotTaskQueue: TaskQueue; /** * @internal */ @@ -76,22 +76,22 @@ export class Target { screenshotTaskQueue: TaskQueue, isPageTargetCallback: IsPageTargetCallback ) { - this._targetInfo = targetInfo; - this._browserContext = browserContext; + this.#targetInfo = targetInfo; + this.#browserContext = browserContext; this._targetId = targetInfo.targetId; - this._sessionFactory = sessionFactory; - this._ignoreHTTPSErrors = ignoreHTTPSErrors; - this._defaultViewport = defaultViewport ?? undefined; - this._screenshotTaskQueue = screenshotTaskQueue; + this.#sessionFactory = sessionFactory; + this.#ignoreHTTPSErrors = ignoreHTTPSErrors; + this.#defaultViewport = defaultViewport ?? undefined; + this.#screenshotTaskQueue = screenshotTaskQueue; this._isPageTargetCallback = isPageTargetCallback; this._initializedPromise = new Promise( (fulfill) => (this._initializedCallback = fulfill) ).then(async (success) => { if (!success) return false; const opener = this.opener(); - if (!opener || !opener._pagePromise || this.type() !== 'page') + if (!opener || !opener.#pagePromise || this.type() !== 'page') return true; - const openerPage = await opener._pagePromise; + const openerPage = await opener.#pagePromise; if (!openerPage.listenerCount(PageEmittedEvents.Popup)) return true; const popupPage = await this.page(); openerPage.emit(PageEmittedEvents.Popup, popupPage); @@ -101,8 +101,8 @@ export class Target { (fulfill) => (this._closedCallback = fulfill) ); this._isInitialized = - !this._isPageTargetCallback(this._targetInfo) || - this._targetInfo.url !== ''; + !this._isPageTargetCallback(this.#targetInfo) || + this.#targetInfo.url !== ''; if (this._isInitialized) this._initializedCallback(true); } @@ -110,32 +110,32 @@ export class Target { * Creates a Chrome Devtools Protocol session attached to the target. */ createCDPSession(): Promise { - return this._sessionFactory(); + return this.#sessionFactory(); } /** * @internal */ _getTargetInfo(): Protocol.Target.TargetInfo { - return this._targetInfo; + return this.#targetInfo; } /** * If the target is not of type `"page"` or `"background_page"`, returns `null`. */ async page(): Promise { - if (this._isPageTargetCallback(this._targetInfo) && !this._pagePromise) { - this._pagePromise = this._sessionFactory().then((client) => - Page.create( + if (this._isPageTargetCallback(this.#targetInfo) && !this.#pagePromise) { + this.#pagePromise = this.#sessionFactory().then((client) => + Page._create( client, this, - this._ignoreHTTPSErrors, - this._defaultViewport ?? null, - this._screenshotTaskQueue + this.#ignoreHTTPSErrors, + this.#defaultViewport ?? null, + this.#screenshotTaskQueue ) ); } - return (await this._pagePromise) ?? null; + return (await this.#pagePromise) ?? null; } /** @@ -143,27 +143,27 @@ export class Target { */ async worker(): Promise { if ( - this._targetInfo.type !== 'service_worker' && - this._targetInfo.type !== 'shared_worker' + this.#targetInfo.type !== 'service_worker' && + this.#targetInfo.type !== 'shared_worker' ) return null; - if (!this._workerPromise) { + if (!this.#workerPromise) { // TODO(einbinder): Make workers send their console logs. - this._workerPromise = this._sessionFactory().then( + this.#workerPromise = this.#sessionFactory().then( (client) => new WebWorker( client, - this._targetInfo.url, + this.#targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */ ) ); } - return this._workerPromise; + return this.#workerPromise; } url(): string { - return this._targetInfo.url; + return this.#targetInfo.url; } /** @@ -181,7 +181,7 @@ export class Target { | 'other' | 'browser' | 'webview' { - const type = this._targetInfo.type; + const type = this.#targetInfo.type; if ( type === 'page' || type === 'background_page' || @@ -198,21 +198,21 @@ export class Target { * Get the browser the target belongs to. */ browser(): Browser { - return this._browserContext.browser(); + return this.#browserContext.browser(); } /** * Get the browser context the target belongs to. */ browserContext(): BrowserContext { - return this._browserContext; + return this.#browserContext; } /** * Get the target that opened this target. Top-level targets return `null`. */ opener(): Target | undefined { - const { openerId } = this._targetInfo; + const { openerId } = this.#targetInfo; if (!openerId) return; return this.browser()._targets.get(openerId); } @@ -221,12 +221,12 @@ export class Target { * @internal */ _targetInfoChanged(targetInfo: Protocol.Target.TargetInfo): void { - this._targetInfo = targetInfo; + this.#targetInfo = targetInfo; if ( !this._isInitialized && - (!this._isPageTargetCallback(this._targetInfo) || - this._targetInfo.url !== '') + (!this._isPageTargetCallback(this.#targetInfo) || + this.#targetInfo.url !== '') ) { this._isInitialized = true; this._initializedCallback(true); diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index bf1ffbc0..eabff2b0 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -15,15 +15,15 @@ */ export class TaskQueue { - private _chain: Promise; + #chain: Promise; constructor() { - this._chain = Promise.resolve(); + this.#chain = Promise.resolve(); } postTask(task: () => Promise): Promise { - const result = this._chain.then(task); - this._chain = result.then( + const result = this.#chain.then(task); + this.#chain = result.then( () => undefined, () => undefined ); diff --git a/src/common/TimeoutSettings.ts b/src/common/TimeoutSettings.ts index 9c441498..aa2ae527 100644 --- a/src/common/TimeoutSettings.ts +++ b/src/common/TimeoutSettings.ts @@ -20,31 +20,31 @@ const DEFAULT_TIMEOUT = 30000; * @internal */ export class TimeoutSettings { - _defaultTimeout: number | null; - _defaultNavigationTimeout: number | null; + #defaultTimeout: number | null; + #defaultNavigationTimeout: number | null; constructor() { - this._defaultTimeout = null; - this._defaultNavigationTimeout = null; + this.#defaultTimeout = null; + this.#defaultNavigationTimeout = null; } setDefaultTimeout(timeout: number): void { - this._defaultTimeout = timeout; + this.#defaultTimeout = timeout; } setDefaultNavigationTimeout(timeout: number): void { - this._defaultNavigationTimeout = timeout; + this.#defaultNavigationTimeout = timeout; } navigationTimeout(): number { - if (this._defaultNavigationTimeout !== null) - return this._defaultNavigationTimeout; - if (this._defaultTimeout !== null) return this._defaultTimeout; + if (this.#defaultNavigationTimeout !== null) + return this.#defaultNavigationTimeout; + if (this.#defaultTimeout !== null) return this.#defaultTimeout; return DEFAULT_TIMEOUT; } timeout(): number { - if (this._defaultTimeout !== null) return this._defaultTimeout; + if (this.#defaultTimeout !== null) return this.#defaultTimeout; return DEFAULT_TIMEOUT; } } diff --git a/src/common/Tracing.ts b/src/common/Tracing.ts index e8c4b1b9..eae77a7a 100644 --- a/src/common/Tracing.ts +++ b/src/common/Tracing.ts @@ -42,26 +42,27 @@ export interface TracingOptions { * @public */ export class Tracing { - _client: CDPSession; - _recording = false; - _path?: string; + #client: CDPSession; + #recording = false; + #path?: string; /** * @internal */ constructor(client: CDPSession) { - this._client = client; + this.#client = client; } /** * Starts a trace for the current page. * @remarks * Only one trace can be active at a time per browser. + * * @param options - Optional `TracingOptions`. */ async start(options: TracingOptions = {}): Promise { assert( - !this._recording, + !this.#recording, 'Cannot start recording trace while already recording trace.' ); @@ -91,9 +92,9 @@ export class Tracing { .map((cat) => cat.slice(1)); const includedCategories = categories.filter((cat) => !cat.startsWith('-')); - this._path = path; - this._recording = true; - await this._client.send('Tracing.start', { + this.#path = path; + this.#recording = true; + await this.#client.send('Tracing.start', { transferMode: 'ReturnAsStream', traceConfig: { excludedCategories, @@ -113,13 +114,13 @@ export class Tracing { resolve = x; reject = y; }); - this._client.once('Tracing.tracingComplete', async (event) => { + this.#client.once('Tracing.tracingComplete', async (event) => { try { const readable = await helper.getReadableFromProtocolStream( - this._client, + this.#client, event.stream ); - const buffer = await helper.getReadableAsBuffer(readable, this._path); + const buffer = await helper.getReadableAsBuffer(readable, this.#path); resolve(buffer ?? undefined); } catch (error) { if (isErrorLike(error)) { @@ -129,8 +130,8 @@ export class Tracing { } } }); - await this._client.send('Tracing.end'); - this._recording = false; + await this.#client.send('Tracing.end'); + this.#recording = false; return contentPromise; } } diff --git a/src/common/USKeyboardLayout.ts b/src/common/USKeyboardLayout.ts index feb3a1f7..6353e4f2 100644 --- a/src/common/USKeyboardLayout.ts +++ b/src/common/USKeyboardLayout.ts @@ -294,7 +294,7 @@ export type KeyInput = /** * @internal */ -export const keyDefinitions: Readonly> = { +export const _keyDefinitions: Readonly> = { '0': { keyCode: 48, key: '0', code: 'Digit0' }, '1': { keyCode: 49, key: '1', code: 'Digit1' }, '2': { keyCode: 50, key: '2', code: 'Digit2' }, diff --git a/src/common/WebWorker.ts b/src/common/WebWorker.ts index 17860f94..96627b91 100644 --- a/src/common/WebWorker.ts +++ b/src/common/WebWorker.ts @@ -61,10 +61,10 @@ type JSHandleFactory = (obj: Protocol.Runtime.RemoteObject) => JSHandle; * @public */ export class WebWorker extends EventEmitter { - _client: CDPSession; - _url: string; - _executionContextPromise: Promise; - _executionContextCallback!: (value: ExecutionContext) => void; + #client: CDPSession; + #url: string; + #executionContextPromise: Promise; + #executionContextCallback!: (value: ExecutionContext) => void; /** * @@ -77,31 +77,31 @@ export class WebWorker extends EventEmitter { exceptionThrown: ExceptionThrownCallback ) { super(); - this._client = client; - this._url = url; - this._executionContextPromise = new Promise( - (x) => (this._executionContextCallback = x) + this.#client = client; + this.#url = url; + this.#executionContextPromise = new Promise( + (x) => (this.#executionContextCallback = x) ); let jsHandleFactory: JSHandleFactory; - this._client.once('Runtime.executionContextCreated', async (event) => { + this.#client.once('Runtime.executionContextCreated', async (event) => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type jsHandleFactory = (remoteObject) => new JSHandle(executionContext, client, remoteObject); const executionContext = new ExecutionContext(client, event.context); - this._executionContextCallback(executionContext); + this.#executionContextCallback(executionContext); }); // This might fail if the target is closed before we receive all execution contexts. - this._client.send('Runtime.enable').catch(debugError); - this._client.on('Runtime.consoleAPICalled', (event) => + this.#client.send('Runtime.enable').catch(debugError); + this.#client.on('Runtime.consoleAPICalled', (event) => consoleAPICalled( event.type, event.args.map(jsHandleFactory), event.stackTrace ) ); - this._client.on('Runtime.exceptionThrown', (exception) => + this.#client.on('Runtime.exceptionThrown', (exception) => exceptionThrown(exception.exceptionDetails) ); } @@ -110,7 +110,7 @@ export class WebWorker extends EventEmitter { * @returns The URL of this web worker. */ url(): string { - return this._url; + return this.#url; } /** @@ -118,7 +118,7 @@ export class WebWorker extends EventEmitter { * @returns The ExecutionContext the web worker runs in. */ async executionContext(): Promise { - return this._executionContextPromise; + return this.#executionContextPromise; } /** @@ -139,7 +139,7 @@ export class WebWorker extends EventEmitter { pageFunction: Function | string, ...args: any[] ): Promise { - return (await this._executionContextPromise).evaluate( + return (await this.#executionContextPromise).evaluate( pageFunction, ...args ); @@ -161,7 +161,7 @@ export class WebWorker extends EventEmitter { pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[] ): Promise { - return (await this._executionContextPromise).evaluateHandle( + return (await this.#executionContextPromise).evaluateHandle( pageFunction, ...args ); diff --git a/src/node/BrowserFetcher.ts b/src/node/BrowserFetcher.ts index 9c43b879..c9372f47 100644 --- a/src/node/BrowserFetcher.ts +++ b/src/node/BrowserFetcher.ts @@ -103,7 +103,7 @@ function archiveName( /** * @internal */ -function downloadURL( +function _downloadURL( product: Product, platform: Platform, host: string, @@ -118,26 +118,24 @@ function downloadURL( return url; } -/** - * @internal - */ function handleArm64(): void { - fs.stat('/usr/bin/chromium-browser', function (_err, stats) { - if (stats === undefined) { - fs.stat('/usr/bin/chromium', function (_err, stats) { - if (stats === undefined) { - console.error( - 'The chromium binary is not available for arm64.' + - '\nIf you are on Ubuntu, you can install with: ' + - '\n\n sudo apt install chromium\n' + - '\n\n sudo apt install chromium-browser\n' - ); - throw new Error(); - } - }); - } - }); + let exists = fs.existsSync('/usr/bin/chromium-browser'); + if (exists) { + return; + } + exists = fs.existsSync('/usr/bin/chromium'); + if (exists) { + return; + } + console.error( + 'The chromium binary is not available for arm64.' + + '\nIf you are on Ubuntu, you can install with: ' + + '\n\n sudo apt install chromium\n' + + '\n\n sudo apt install chromium-browser\n' + ); + throw new Error(); } + const readdirAsync = promisify(fs.readdir.bind(fs)); const mkdirAsync = promisify(fs.mkdir.bind(fs)); const unlinkAsync = promisify(fs.unlink.bind(fs)); @@ -195,49 +193,49 @@ export interface BrowserFetcherRevisionInfo { */ export class BrowserFetcher { - private _product: Product; - private _downloadsFolder: string; - private _downloadHost: string; - private _platform: Platform; + #product: Product; + #downloadsFolder: string; + #downloadHost: string; + #platform: Platform; /** * @internal */ constructor(projectRoot: string, options: BrowserFetcherOptions = {}) { - this._product = (options.product || 'chrome').toLowerCase() as Product; + this.#product = (options.product || 'chrome').toLowerCase() as Product; assert( - this._product === 'chrome' || this._product === 'firefox', + this.#product === 'chrome' || this.#product === 'firefox', `Unknown product: "${options.product}"` ); - this._downloadsFolder = + this.#downloadsFolder = options.path || - path.join(projectRoot, browserConfig[this._product].destination); - this._downloadHost = options.host || browserConfig[this._product].host; + path.join(projectRoot, browserConfig[this.#product].destination); + this.#downloadHost = options.host || browserConfig[this.#product].host; if (options.platform) { - this._platform = options.platform; + this.#platform = options.platform; } else { const platform = os.platform(); switch (platform) { case 'darwin': - switch (this._product) { + switch (this.#product) { case 'chrome': - this._platform = + this.#platform = os.arch() === 'arm64' && PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM ? 'mac_arm' : 'mac'; break; case 'firefox': - this._platform = 'mac'; + this.#platform = 'mac'; break; } break; case 'linux': - this._platform = 'linux'; + this.#platform = 'linux'; break; case 'win32': - this._platform = os.arch() === 'x64' ? 'win64' : 'win32'; + this.#platform = os.arch() === 'x64' ? 'win64' : 'win32'; return; default: assert(false, 'Unsupported platform: ' + platform); @@ -245,8 +243,8 @@ export class BrowserFetcher { } assert( - downloadURLs[this._product][this._platform], - 'Unsupported platform: ' + this._platform + downloadURLs[this.#product][this.#platform], + 'Unsupported platform: ' + this.#platform ); } @@ -255,7 +253,7 @@ export class BrowserFetcher { * `win32` or `win64`. */ platform(): Platform { - return this._platform; + return this.#platform; } /** @@ -263,14 +261,14 @@ export class BrowserFetcher { * `firefox`. */ product(): Product { - return this._product; + return this.#product; } /** * @returns The download host being used. */ host(): string { - return this._downloadHost; + return this.#downloadHost; } /** @@ -282,10 +280,10 @@ export class BrowserFetcher { * from the host. */ canDownload(revision: string): Promise { - const url = downloadURL( - this._product, - this._platform, - this._downloadHost, + const url = _downloadURL( + this.#product, + this.#platform, + this.#downloadHost, revision ); return new Promise((resolve) => { @@ -318,19 +316,19 @@ export class BrowserFetcher { revision: string, progressCallback: (x: number, y: number) => void = (): void => {} ): Promise { - const url = downloadURL( - this._product, - this._platform, - this._downloadHost, + const url = _downloadURL( + this.#product, + this.#platform, + this.#downloadHost, revision ); const fileName = url.split('/').pop(); assert(fileName, `A malformed download URL was found: ${url}.`); - const archivePath = path.join(this._downloadsFolder, fileName); - const outputPath = this._getFolderPath(revision); + const archivePath = path.join(this.#downloadsFolder, fileName); + const outputPath = this.#getFolderPath(revision); if (await existsAsync(outputPath)) return this.revisionInfo(revision); - if (!(await existsAsync(this._downloadsFolder))) - await mkdirAsync(this._downloadsFolder); + if (!(await existsAsync(this.#downloadsFolder))) + await mkdirAsync(this.#downloadsFolder); // Use system Chromium builds on Linux ARM devices if (os.platform() !== 'darwin' && os.arch() === 'arm64') { @@ -338,7 +336,7 @@ export class BrowserFetcher { return; } try { - await downloadFile(url, archivePath, progressCallback); + await _downloadFile(url, archivePath, progressCallback); await install(archivePath, outputPath); } finally { if (await existsAsync(archivePath)) await unlinkAsync(archivePath); @@ -355,15 +353,15 @@ export class BrowserFetcher { * available locally on disk. */ async localRevisions(): Promise { - if (!(await existsAsync(this._downloadsFolder))) return []; - const fileNames = await readdirAsync(this._downloadsFolder); + if (!(await existsAsync(this.#downloadsFolder))) return []; + const fileNames = await readdirAsync(this.#downloadsFolder); return fileNames - .map((fileName) => parseFolderPath(this._product, fileName)) + .map((fileName) => parseFolderPath(this.#product, fileName)) .filter( ( entry ): entry is { product: string; platform: string; revision: string } => - (entry && entry.platform === this._platform) ?? false + (entry && entry.platform === this.#platform) ?? false ) .map((entry) => entry.revision); } @@ -376,7 +374,7 @@ export class BrowserFetcher { * throws if the revision has not been downloaded. */ async remove(revision: string): Promise { - const folderPath = this._getFolderPath(revision); + const folderPath = this.#getFolderPath(revision); assert( await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded` @@ -389,33 +387,33 @@ export class BrowserFetcher { * @returns The revision info for the given revision. */ revisionInfo(revision: string): BrowserFetcherRevisionInfo { - const folderPath = this._getFolderPath(revision); + const folderPath = this.#getFolderPath(revision); let executablePath = ''; - if (this._product === 'chrome') { - if (this._platform === 'mac' || this._platform === 'mac_arm') + if (this.#product === 'chrome') { + if (this.#platform === 'mac' || this.#platform === 'mac_arm') executablePath = path.join( folderPath, - archiveName(this._product, this._platform, revision), + archiveName(this.#product, this.#platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium' ); - else if (this._platform === 'linux') + else if (this.#platform === 'linux') executablePath = path.join( folderPath, - archiveName(this._product, this._platform, revision), + archiveName(this.#product, this.#platform, revision), 'chrome' ); - else if (this._platform === 'win32' || this._platform === 'win64') + else if (this.#platform === 'win32' || this.#platform === 'win64') executablePath = path.join( folderPath, - archiveName(this._product, this._platform, revision), + archiveName(this.#product, this.#platform, revision), 'chrome.exe' ); - else throw new Error('Unsupported platform: ' + this._platform); - } else if (this._product === 'firefox') { - if (this._platform === 'mac' || this._platform === 'mac_arm') + else throw new Error('Unsupported platform: ' + this.#platform); + } else if (this.#product === 'firefox') { + if (this.#platform === 'mac' || this.#platform === 'mac_arm') executablePath = path.join( folderPath, 'Firefox Nightly.app', @@ -423,16 +421,16 @@ export class BrowserFetcher { 'MacOS', 'firefox' ); - else if (this._platform === 'linux') + else if (this.#platform === 'linux') executablePath = path.join(folderPath, 'firefox', 'firefox'); - else if (this._platform === 'win32' || this._platform === 'win64') + else if (this.#platform === 'win32' || this.#platform === 'win64') executablePath = path.join(folderPath, 'firefox', 'firefox.exe'); - else throw new Error('Unsupported platform: ' + this._platform); - } else throw new Error('Unsupported product: ' + this._product); - const url = downloadURL( - this._product, - this._platform, - this._downloadHost, + else throw new Error('Unsupported platform: ' + this.#platform); + } else throw new Error('Unsupported product: ' + this.#product); + const url = _downloadURL( + this.#product, + this.#platform, + this.#downloadHost, revision ); const local = fs.existsSync(folderPath); @@ -442,7 +440,7 @@ export class BrowserFetcher { folderPath, local, url, - product: this._product, + product: this.#product, }); return { revision, @@ -450,15 +448,12 @@ export class BrowserFetcher { folderPath, local, url, - product: this._product, + product: this.#product, }; } - /** - * @internal - */ - _getFolderPath(revision: string): string { - return path.resolve(this._downloadsFolder, `${this._platform}-${revision}`); + #getFolderPath(revision: string): string { + return path.resolve(this.#downloadsFolder, `${this.#platform}-${revision}`); } } @@ -477,7 +472,7 @@ function parseFolderPath( /** * @internal */ -function downloadFile( +function _downloadFile( url: string, destinationPath: string, progressCallback?: (x: number, y: number) => void @@ -524,10 +519,10 @@ function install(archivePath: string, folderPath: string): Promise { if (archivePath.endsWith('.zip')) return extractZip(archivePath, { dir: folderPath }); else if (archivePath.endsWith('.tar.bz2')) - return extractTar(archivePath, folderPath); + return _extractTar(archivePath, folderPath); else if (archivePath.endsWith('.dmg')) return mkdirAsync(folderPath).then(() => - installDMG(archivePath, folderPath) + _installDMG(archivePath, folderPath) ); else throw new Error(`Unsupported archive format: ${archivePath}`); } @@ -535,7 +530,7 @@ function install(archivePath: string, folderPath: string): Promise { /** * @internal */ -function extractTar(tarPath: string, folderPath: string): Promise { +function _extractTar(tarPath: string, folderPath: string): Promise { return new Promise((fulfill, reject) => { const tarStream = tar.extract(folderPath); tarStream.on('error', reject); @@ -548,7 +543,7 @@ function extractTar(tarPath: string, folderPath: string): Promise { /** * @internal */ -function installDMG(dmgPath: string, folderPath: string): Promise { +function _installDMG(dmgPath: string, folderPath: string): Promise { let mountPath: string | undefined; return new Promise((fulfill, reject): void => { diff --git a/src/node/BrowserRunner.ts b/src/node/BrowserRunner.ts index ea330c8f..a5dadb41 100644 --- a/src/node/BrowserRunner.ts +++ b/src/node/BrowserRunner.ts @@ -50,19 +50,18 @@ Please check your open processes and ensure that the browser processes that Pupp If you think this is a bug, please report it on the Puppeteer issue tracker.`; export class BrowserRunner { - private _product: Product; - private _executablePath: string; - private _processArguments: string[]; - private _userDataDir: string; - private _isTempUserDataDir?: boolean; + #product: Product; + #executablePath: string; + #processArguments: string[]; + #userDataDir: string; + #isTempUserDataDir?: boolean; + #closed = true; + #listeners: PuppeteerEventListener[] = []; + #processClosing!: Promise; proc?: childProcess.ChildProcess; connection?: Connection; - private _closed = true; - private _listeners: PuppeteerEventListener[] = []; - private _processClosing!: Promise; - constructor( product: Product, executablePath: string, @@ -70,11 +69,11 @@ export class BrowserRunner { userDataDir: string, isTempUserDataDir?: boolean ) { - this._product = product; - this._executablePath = executablePath; - this._processArguments = processArguments; - this._userDataDir = userDataDir; - this._isTempUserDataDir = isTempUserDataDir; + this.#product = product; + this.#executablePath = executablePath; + this.#processArguments = processArguments; + this.#userDataDir = userDataDir; + this.#isTempUserDataDir = isTempUserDataDir; } start(options: LaunchOptions): void { @@ -90,11 +89,11 @@ export class BrowserRunner { } assert(!this.proc, 'This process has previously been started.'); debugLauncher( - `Calling ${this._executablePath} ${this._processArguments.join(' ')}` + `Calling ${this.#executablePath} ${this.#processArguments.join(' ')}` ); this.proc = childProcess.spawn( - this._executablePath, - this._processArguments, + this.#executablePath, + this.#processArguments, { // On non-windows platforms, `detached: true` makes child process a // leader of a new process group, making it possible to kill child @@ -109,32 +108,32 @@ export class BrowserRunner { this.proc.stderr?.pipe(process.stderr); this.proc.stdout?.pipe(process.stdout); } - this._closed = false; - this._processClosing = new Promise((fulfill, reject) => { + this.#closed = false; + this.#processClosing = new Promise((fulfill, reject) => { this.proc!.once('exit', async () => { - this._closed = true; + this.#closed = true; // Cleanup as processes exit. - if (this._isTempUserDataDir) { + if (this.#isTempUserDataDir) { try { - await removeFolderAsync(this._userDataDir); + await removeFolderAsync(this.#userDataDir); fulfill(); } catch (error) { debugError(error); reject(error); } } else { - if (this._product === 'firefox') { + if (this.#product === 'firefox') { try { // When an existing user profile has been used remove the user // preferences file and restore possibly backuped preferences. - await unlinkAsync(path.join(this._userDataDir, 'user.js')); + await unlinkAsync(path.join(this.#userDataDir, 'user.js')); const prefsBackupPath = path.join( - this._userDataDir, + this.#userDataDir, 'prefs.js.puppeteer' ); if (fs.existsSync(prefsBackupPath)) { - const prefsPath = path.join(this._userDataDir, 'prefs.js'); + const prefsPath = path.join(this.#userDataDir, 'prefs.js'); await unlinkAsync(prefsPath); await renameAsync(prefsBackupPath, prefsPath); } @@ -148,29 +147,29 @@ export class BrowserRunner { } }); }); - this._listeners = [ + this.#listeners = [ helper.addEventListener(process, 'exit', this.kill.bind(this)), ]; if (handleSIGINT) - this._listeners.push( + this.#listeners.push( helper.addEventListener(process, 'SIGINT', () => { this.kill(); process.exit(130); }) ); if (handleSIGTERM) - this._listeners.push( + this.#listeners.push( helper.addEventListener(process, 'SIGTERM', this.close.bind(this)) ); if (handleSIGHUP) - this._listeners.push( + this.#listeners.push( helper.addEventListener(process, 'SIGHUP', this.close.bind(this)) ); } close(): Promise { - if (this._closed) return Promise.resolve(); - if (this._isTempUserDataDir) { + if (this.#closed) return Promise.resolve(); + if (this.#isTempUserDataDir) { this.kill(); } else if (this.connection) { // Attempt to close the browser gracefully @@ -181,8 +180,8 @@ export class BrowserRunner { } // Cleanup this listener last, as that makes sure the full callback runs. If we // perform this earlier, then the previous function calls would not happen. - helper.removeEventListeners(this._listeners); - return this._processClosing; + helper.removeEventListeners(this.#listeners); + return this.#processClosing; } kill(): void { @@ -226,14 +225,14 @@ export class BrowserRunner { // Attempt to remove temporary profile directory to avoid littering. try { - if (this._isTempUserDataDir) { - removeFolder.sync(this._userDataDir); + if (this.#isTempUserDataDir) { + removeFolder.sync(this.#userDataDir); } } catch (error) {} // Cleanup this listener last, as that makes sure the full callback runs. If we // perform this earlier, then the previous function calls would not happen. - helper.removeEventListeners(this._listeners); + helper.removeEventListeners(this.#listeners); } async setupConnection(options: { diff --git a/src/node/Launcher.ts b/src/node/Launcher.ts index 5dc35941..fd7f2fdf 100644 --- a/src/node/Launcher.ts +++ b/src/node/Launcher.ts @@ -52,8 +52,17 @@ export interface ProductLauncher { * @internal */ class ChromeLauncher implements ProductLauncher { + /** + * @internal + */ _projectRoot: string | undefined; + /** + * @internal + */ _preferredRevision: string; + /** + * @internal + */ _isPuppeteerCore: boolean; constructor( @@ -175,7 +184,7 @@ class ChromeLauncher implements ProductLauncher { slowMo, preferredRevision: this._preferredRevision, }); - browser = await Browser.create( + browser = await Browser._create( connection, [], ignoreHTTPSErrors, @@ -273,8 +282,17 @@ class ChromeLauncher implements ProductLauncher { * @internal */ class FirefoxLauncher implements ProductLauncher { + /** + * @internal + */ _projectRoot: string | undefined; + /** + * @internal + */ _preferredRevision: string; + /** + * @internal + */ _isPuppeteerCore: boolean; constructor( @@ -393,7 +411,7 @@ class FirefoxLauncher implements ProductLauncher { slowMo, preferredRevision: this._preferredRevision, }); - browser = await Browser.create( + browser = await Browser._create( connection, [], ignoreHTTPSErrors, diff --git a/src/node/NodeWebSocketTransport.ts b/src/node/NodeWebSocketTransport.ts index b2886889..45276c84 100644 --- a/src/node/NodeWebSocketTransport.ts +++ b/src/node/NodeWebSocketTransport.ts @@ -50,27 +50,27 @@ export class NodeWebSocketTransport implements ConnectionTransport { }); } - private _ws: NodeWebSocket; + #ws: NodeWebSocket; onmessage?: (message: NodeWebSocket.Data) => void; onclose?: () => void; constructor(ws: NodeWebSocket) { - this._ws = ws; - this._ws.addEventListener('message', (event) => { + this.#ws = ws; + this.#ws.addEventListener('message', (event) => { if (this.onmessage) this.onmessage.call(null, event.data); }); - this._ws.addEventListener('close', () => { + this.#ws.addEventListener('close', () => { if (this.onclose) this.onclose.call(null); }); // Silently ignore all errors - we don't know what to do with them. - this._ws.addEventListener('error', () => {}); + this.#ws.addEventListener('error', () => {}); } send(message: string): void { - this._ws.send(message); + this.#ws.send(message); } close(): void { - this._ws.close(); + this.#ws.close(); } } diff --git a/src/node/PipeTransport.ts b/src/node/PipeTransport.ts index 2a8bfb15..a3f8a821 100644 --- a/src/node/PipeTransport.ts +++ b/src/node/PipeTransport.ts @@ -22,11 +22,11 @@ import { ConnectionTransport } from '../common/ConnectionTransport.js'; import { assert } from '../common/assert.js'; export class PipeTransport implements ConnectionTransport { - _pipeWrite: NodeJS.WritableStream; - _eventListeners: PuppeteerEventListener[]; + #pipeWrite: NodeJS.WritableStream; + #eventListeners: PuppeteerEventListener[]; - _isClosed = false; - _pendingMessage = ''; + #isClosed = false; + #pendingMessage = ''; onclose?: () => void; onmessage?: (value: string) => void; @@ -35,10 +35,10 @@ export class PipeTransport implements ConnectionTransport { pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream ) { - this._pipeWrite = pipeWrite; - this._eventListeners = [ + this.#pipeWrite = pipeWrite; + this.#eventListeners = [ helper.addEventListener(pipeRead, 'data', (buffer) => - this._dispatch(buffer) + this.#dispatch(buffer) ), helper.addEventListener(pipeRead, 'close', () => { if (this.onclose) { @@ -51,21 +51,21 @@ export class PipeTransport implements ConnectionTransport { } send(message: string): void { - assert(!this._isClosed, '`PipeTransport` is closed.'); + assert(!this.#isClosed, '`PipeTransport` is closed.'); - this._pipeWrite.write(message); - this._pipeWrite.write('\0'); + this.#pipeWrite.write(message); + this.#pipeWrite.write('\0'); } - _dispatch(buffer: Buffer): void { - assert(!this._isClosed, '`PipeTransport` is closed.'); + #dispatch(buffer: Buffer): void { + assert(!this.#isClosed, '`PipeTransport` is closed.'); let end = buffer.indexOf('\0'); if (end === -1) { - this._pendingMessage += buffer.toString(); + this.#pendingMessage += buffer.toString(); return; } - const message = this._pendingMessage + buffer.toString(undefined, 0, end); + const message = this.#pendingMessage + buffer.toString(undefined, 0, end); if (this.onmessage) { this.onmessage.call(null, message); } @@ -79,11 +79,11 @@ export class PipeTransport implements ConnectionTransport { start = end + 1; end = buffer.indexOf('\0', start); } - this._pendingMessage = buffer.toString(undefined, start); + this.#pendingMessage = buffer.toString(undefined, start); } close(): void { - this._isClosed = true; - helper.removeEventListeners(this._eventListeners); + this.#isClosed = true; + helper.removeEventListeners(this.#eventListeners); } } diff --git a/src/node/Puppeteer.ts b/src/node/Puppeteer.ts index 58f04065..3f2e69df 100644 --- a/src/node/Puppeteer.ts +++ b/src/node/Puppeteer.ts @@ -77,12 +77,10 @@ export interface PuppeteerLaunchOptions * @public */ export class PuppeteerNode extends Puppeteer { - private _lazyLauncher?: ProductLauncher; - private _projectRoot?: string; - private __productName?: Product; - /** - * @internal - */ + #lazyLauncher?: ProductLauncher; + #projectRoot?: string; + #productName?: Product; + _preferredRevision: string; /** @@ -98,8 +96,8 @@ export class PuppeteerNode extends Puppeteer { const { projectRoot, preferredRevision, productName, ...commonSettings } = settings; super(commonSettings); - this._projectRoot = projectRoot; - this.__productName = productName; + this.#projectRoot = projectRoot; + this.#productName = productName; this._preferredRevision = preferredRevision; this.connect = this.connect.bind(this); @@ -126,13 +124,15 @@ export class PuppeteerNode extends Puppeteer { * @internal */ get _productName(): Product | undefined { - return this.__productName; + return this.#productName; } - // don't need any TSDoc here - because the getter is internal the setter is too. + /** + * @internal + */ set _productName(name: Product | undefined) { - if (this.__productName !== name) this._changedProduct = true; - this.__productName = name; + if (this.#productName !== name) this._changedProduct = true; + this.#productName = name; } /** @@ -184,8 +184,8 @@ export class PuppeteerNode extends Puppeteer { */ get _launcher(): ProductLauncher { if ( - !this._lazyLauncher || - this._lazyLauncher.product !== this._productName || + !this.#lazyLauncher || + this.#lazyLauncher.product !== this._productName || this._changedProduct ) { switch (this._productName) { @@ -197,14 +197,14 @@ export class PuppeteerNode extends Puppeteer { this._preferredRevision = PUPPETEER_REVISIONS.chromium; } this._changedProduct = false; - this._lazyLauncher = Launcher( - this._projectRoot, + this.#lazyLauncher = Launcher( + this.#projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName ); } - return this._lazyLauncher; + return this.#lazyLauncher; } /** @@ -234,11 +234,11 @@ export class PuppeteerNode extends Puppeteer { * @returns A new BrowserFetcher instance. */ createBrowserFetcher(options: BrowserFetcherOptions): BrowserFetcher { - if (!this._projectRoot) { + if (!this.#projectRoot) { throw new Error( '_projectRoot is undefined. Unable to create a BrowserFetcher.' ); } - return new BrowserFetcher(this._projectRoot, options); + return new BrowserFetcher(this.#projectRoot, options); } } diff --git a/test/fixtures.spec.ts b/test/fixtures.spec.ts index e2c76f67..9f5c6213 100644 --- a/test/fixtures.spec.ts +++ b/test/fixtures.spec.ts @@ -26,7 +26,7 @@ describe('Fixtures', function () { const { defaultBrowserOptions, puppeteerPath } = getTestState(); let dumpioData = ''; - const { spawn } = require('child_process'); + const { spawn } = await import('child_process'); const options = Object.assign({}, defaultBrowserOptions, { pipe: true, dumpio: true, @@ -44,7 +44,7 @@ describe('Fixtures', function () { const { defaultBrowserOptions, puppeteerPath } = getTestState(); let dumpioData = ''; - const { spawn } = require('child_process'); + const { spawn } = await import('child_process'); const options = Object.assign({}, defaultBrowserOptions, { dumpio: true }); const res = spawn('node', [ path.join(__dirname, 'fixtures', 'dumpio.js'), @@ -58,7 +58,7 @@ describe('Fixtures', function () { it('should close the browser when the node process closes', async () => { const { defaultBrowserOptions, puppeteerPath, puppeteer } = getTestState(); - const { spawn, execSync } = require('child_process'); + const { spawn, execSync } = await import('child_process'); const options = Object.assign({}, defaultBrowserOptions, { // Disable DUMPIO to cleanly read stdout. dumpio: false, diff --git a/test/frame.spec.ts b/test/frame.spec.ts index 94bf5721..f731ac12 100644 --- a/test/frame.spec.ts +++ b/test/frame.spec.ts @@ -292,7 +292,7 @@ describe('Frame specs', function () { describe('Frame.client', function () { it('should return the client instance', async () => { const { page } = getTestState(); - expect(page.mainFrame().client()).toBeInstanceOf(CDPSession); + expect(page.mainFrame()._client()).toBeInstanceOf(CDPSession); }); }); }); diff --git a/test/headful.spec.ts b/test/headful.spec.ts index 54c3b044..dbdc7aa4 100644 --- a/test/headful.spec.ts +++ b/test/headful.spec.ts @@ -134,7 +134,7 @@ describeChromeOnly('headful tests', function () { const browser = await puppeteer.connect({ browserWSEndpoint, - isPageTarget: (target) => { + _isPageTarget(target) { return ( target.type === 'other' && target.url.startsWith('devtools://') ); diff --git a/test/jshandle.spec.ts b/test/jshandle.spec.ts index ae1c186e..32ab08d8 100644 --- a/test/jshandle.spec.ts +++ b/test/jshandle.spec.ts @@ -52,18 +52,17 @@ describe('JSHandle', function () { const isFive = await page.evaluate((e) => Object.is(e, 5), aHandle); expect(isFive).toBeTruthy(); }); - it('should warn on nested object handles', async () => { + it('should warn about recursive objects', async () => { const { page } = getTestState(); - const aHandle = await page.evaluateHandle(() => document.body); + const test: { obj?: unknown } = {}; + test.obj = test; let error = null; await page // @ts-expect-error we are deliberately passing a bad type here (nested object) - .evaluateHandle((opts) => opts.elem.querySelector('p'), { - elem: aHandle, - }) + .evaluateHandle((opts) => opts.elem, { test }) .catch((error_) => (error = error_)); - expect(error.message).toContain('Are you passing a nested JSHandle?'); + expect(error.message).toContain('Recursive objects are not allowed.'); }); it('should accept object handle to unserializable value', async () => { const { page } = getTestState(); diff --git a/test/oopif.spec.ts b/test/oopif.spec.ts index ba5ce9da..0732ca36 100644 --- a/test/oopif.spec.ts +++ b/test/oopif.spec.ts @@ -21,12 +21,17 @@ import { describeChromeOnly, itFailsFirefox, } from './mocha-utils'; // eslint-disable-line import/extensions +import { + Browser, + BrowserContext, + Page, +} from '../lib/cjs/puppeteer/api-docs-entry.js'; describeChromeOnly('OOPIF', function () { /* We use a special browser for this test as we need the --site-per-process flag */ - let browser; - let context; - let page; + let browser: Browser; + let context: BrowserContext; + let page: Page; before(async () => { const { puppeteer, defaultBrowserOptions } = getTestState(); @@ -433,8 +438,8 @@ describeChromeOnly('OOPIF', function () { }); }); -function oopifs(context) { +function oopifs(context: BrowserContext) { return context .targets() - .filter((target) => target._targetInfo.type === 'iframe'); + .filter((target) => target._getTargetInfo().type === 'iframe'); } diff --git a/test/page.spec.ts b/test/page.spec.ts index 88fddc4c..6ba199ac 100644 --- a/test/page.spec.ts +++ b/test/page.spec.ts @@ -1971,7 +1971,7 @@ describe('Page', function () { describe('Page.client', function () { it('should return the client instance', async () => { const { page } = getTestState(); - expect(page.client()).toBeInstanceOf(CDPSession); + expect(page._client()).toBeInstanceOf(CDPSession); }); }); });