refactor: use RxJS for waitForHttp (#11133)

This commit is contained in:
Nikolay Vitkov 2023-10-13 12:35:43 +02:00 committed by GitHub
parent 2c7d802d87
commit 8290dc9de9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 178 deletions

View File

@ -40,11 +40,10 @@ import type {BidiNetworkManager} from '../bidi/NetworkManager.js';
import type {Accessibility} from '../cdp/Accessibility.js'; import type {Accessibility} from '../cdp/Accessibility.js';
import type {Coverage} from '../cdp/Coverage.js'; import type {Coverage} from '../cdp/Coverage.js';
import type {DeviceRequestPrompt} from '../cdp/DeviceRequestPrompt.js'; import type {DeviceRequestPrompt} from '../cdp/DeviceRequestPrompt.js';
import { import type {
NetworkManagerEvent, NetworkManager as CdpNetworkManager,
type NetworkManager as CdpNetworkManager, Credentials,
type Credentials, NetworkConditions,
type NetworkConditions,
} from '../cdp/NetworkManager.js'; } from '../cdp/NetworkManager.js';
import type {Tracing} from '../cdp/Tracing.js'; import type {Tracing} from '../cdp/Tracing.js';
import type {WebWorker} from '../cdp/WebWorker.js'; import type {WebWorker} from '../cdp/WebWorker.js';
@ -58,12 +57,14 @@ import {
type Handler, type Handler,
} from '../common/EventEmitter.js'; } from '../common/EventEmitter.js';
import type {FileChooser} from '../common/FileChooser.js'; import type {FileChooser} from '../common/FileChooser.js';
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
import { import {
paperFormats, paperFormats,
type LowerCasePaperFormat, type LowerCasePaperFormat,
type ParsedPDFOptions, type ParsedPDFOptions,
type PDFOptions, type PDFOptions,
} from '../common/PDFOptions.js'; } from '../common/PDFOptions.js';
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import type { import type {
Awaitable, Awaitable,
EvaluateFunc, EvaluateFunc,
@ -603,6 +604,10 @@ export abstract class Page extends EventEmitter<PageEvents> {
* @internal * @internal
*/ */
_isDragging = false; _isDragging = false;
/**
* @internal
*/
_timeoutSettings = new TimeoutSettings();
#requestHandlers = new WeakMap<Handler<HTTPRequest>, Handler<HTTPRequest>>(); #requestHandlers = new WeakMap<Handler<HTTPRequest>, Handler<HTTPRequest>>();

View File

@ -16,12 +16,11 @@
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {NetworkManagerEvent} from '../cdp/NetworkManager.js'; import {EventEmitter, EventSubscription} from '../common/EventEmitter.js';
import { import {
EventEmitter, NetworkManagerEvent,
EventSubscription, type NetworkManagerEvents,
type EventType, } from '../common/NetworkManagerEvents.js';
} from '../common/EventEmitter.js';
import {DisposableStack} from '../util/disposable.js'; import {DisposableStack} from '../util/disposable.js';
import type {BidiConnection} from './Connection.js'; import type {BidiConnection} from './Connection.js';
@ -33,18 +32,7 @@ import type {BidiPage} from './Page.js';
/** /**
* @internal * @internal
*/ */
export interface BidiNetworkManagerEvents extends Record<EventType, unknown> { export class BidiNetworkManager extends EventEmitter<NetworkManagerEvents> {
[NetworkManagerEvent.Request]: BidiHTTPRequest;
[NetworkManagerEvent.RequestServedFromCache]: BidiHTTPRequest;
[NetworkManagerEvent.Response]: BidiHTTPResponse;
[NetworkManagerEvent.RequestFailed]: BidiHTTPRequest;
[NetworkManagerEvent.RequestFinished]: BidiHTTPRequest;
}
/**
* @internal
*/
export class BidiNetworkManager extends EventEmitter<BidiNetworkManagerEvents> {
#connection: BidiConnection; #connection: BidiConnection;
#page: BidiPage; #page: BidiPage;
#subscriptions = new DisposableStack(); #subscriptions = new DisposableStack();

View File

@ -33,7 +33,6 @@ import {Accessibility} from '../cdp/Accessibility.js';
import {Coverage} from '../cdp/Coverage.js'; import {Coverage} from '../cdp/Coverage.js';
import {EmulationManager as CdpEmulationManager} from '../cdp/EmulationManager.js'; import {EmulationManager as CdpEmulationManager} from '../cdp/EmulationManager.js';
import {FrameTree} from '../cdp/FrameTree.js'; import {FrameTree} from '../cdp/FrameTree.js';
import {NetworkManagerEvent} from '../cdp/NetworkManager.js';
import {Tracing} from '../cdp/Tracing.js'; import {Tracing} from '../cdp/Tracing.js';
import { import {
ConsoleMessage, ConsoleMessage,
@ -45,15 +44,14 @@ import {
TimeoutError, TimeoutError,
} from '../common/Errors.js'; } from '../common/Errors.js';
import type {Handler} from '../common/EventEmitter.js'; import type {Handler} from '../common/EventEmitter.js';
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
import type {PDFOptions} from '../common/PDFOptions.js'; import type {PDFOptions} from '../common/PDFOptions.js';
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import type {Awaitable} from '../common/types.js'; import type {Awaitable} from '../common/types.js';
import { import {
debugError, debugError,
evaluationString, evaluationString,
isString,
validateDialogType, validateDialogType,
waitForEvent, waitForHTTP,
waitWithTimeout, waitWithTimeout,
} from '../common/util.js'; } from '../common/util.js';
import type {Viewport} from '../common/Viewport.js'; import type {Viewport} from '../common/Viewport.js';
@ -87,7 +85,6 @@ import {BidiSerializer} from './Serializer.js';
*/ */
export class BidiPage extends Page { export class BidiPage extends Page {
#accessibility: Accessibility; #accessibility: Accessibility;
#timeoutSettings = new TimeoutSettings();
#connection: BidiConnection; #connection: BidiConnection;
#frameTree = new FrameTree<BidiFrame>(); #frameTree = new FrameTree<BidiFrame>();
#networkManager: BidiNetworkManager; #networkManager: BidiNetworkManager;
@ -184,7 +181,7 @@ export class BidiPage extends Page {
const frame = new BidiFrame( const frame = new BidiFrame(
this, this,
this.#browsingContext, this.#browsingContext,
this.#timeoutSettings, this._timeoutSettings,
this.#browsingContext.parent this.#browsingContext.parent
); );
this.#frameTree.addFrame(frame); this.#frameTree.addFrame(frame);
@ -356,7 +353,7 @@ export class BidiPage extends Page {
const frame = new BidiFrame( const frame = new BidiFrame(
this, this,
context, context,
this.#timeoutSettings, this._timeoutSettings,
context.parent context.parent
); );
this.#frameTree.addFrame(frame); this.#frameTree.addFrame(frame);
@ -490,7 +487,7 @@ export class BidiPage extends Page {
): Promise<BidiHTTPResponse | null> { ): Promise<BidiHTTPResponse | null> {
const { const {
waitUntil = 'load', waitUntil = 'load',
timeout = this.#timeoutSettings.navigationTimeout(), timeout = this._timeoutSettings.navigationTimeout(),
} = options; } = options;
const readinessState = lifeCycleToReadinessState.get( const readinessState = lifeCycleToReadinessState.get(
@ -519,15 +516,15 @@ export class BidiPage extends Page {
} }
override setDefaultNavigationTimeout(timeout: number): void { override setDefaultNavigationTimeout(timeout: number): void {
this.#timeoutSettings.setDefaultNavigationTimeout(timeout); this._timeoutSettings.setDefaultNavigationTimeout(timeout);
} }
override setDefaultTimeout(timeout: number): void { override setDefaultTimeout(timeout: number): void {
this.#timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
} }
override getDefaultTimeout(): number { override getDefaultTimeout(): number {
return this.#timeoutSettings.timeout(); return this._timeoutSettings.timeout();
} }
override isJavaScriptEnabled(): boolean { override isJavaScriptEnabled(): boolean {
@ -691,21 +688,13 @@ export class BidiPage extends Page {
| ((req: BidiHTTPRequest) => boolean | Promise<boolean>), | ((req: BidiHTTPRequest) => boolean | Promise<boolean>),
options: {timeout?: number} = {} options: {timeout?: number} = {}
): Promise<BidiHTTPRequest> { ): Promise<BidiHTTPRequest> {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this._timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForHTTP(
this.#networkManager, this.#networkManager,
NetworkManagerEvent.Request, NetworkManagerEvent.Request,
async request => { urlOrPredicate,
if (isString(urlOrPredicate)) {
return urlOrPredicate === request.url();
}
if (typeof urlOrPredicate === 'function') {
return !!(await urlOrPredicate(request));
}
return false;
},
timeout, timeout,
this.#closedDeferred.valueOrThrow() this.#closedDeferred
); );
} }
@ -715,28 +704,20 @@ export class BidiPage extends Page {
| ((res: BidiHTTPResponse) => boolean | Promise<boolean>), | ((res: BidiHTTPResponse) => boolean | Promise<boolean>),
options: {timeout?: number} = {} options: {timeout?: number} = {}
): Promise<BidiHTTPResponse> { ): Promise<BidiHTTPResponse> {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this._timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForHTTP(
this.#networkManager, this.#networkManager,
NetworkManagerEvent.Response, NetworkManagerEvent.Response,
async response => { urlOrPredicate,
if (isString(urlOrPredicate)) {
return urlOrPredicate === response.url();
}
if (typeof urlOrPredicate === 'function') {
return !!(await urlOrPredicate(response));
}
return false;
},
timeout, timeout,
this.#closedDeferred.valueOrThrow() this.#closedDeferred
); );
} }
override async waitForNetworkIdle( override async waitForNetworkIdle(
options: {idleTime?: number; timeout?: number} = {} options: {idleTime?: number; timeout?: number} = {}
): Promise<void> { ): Promise<void> {
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options; const {idleTime = 500, timeout = this._timeoutSettings.timeout()} = options;
await this._waitForNetworkIdle( await this._waitForNetworkIdle(
this.#networkManager, this.#networkManager,

View File

@ -28,16 +28,18 @@ import {
type ResponseForRequest, type ResponseForRequest,
STATUS_TEXTS, STATUS_TEXTS,
} from '../api/HTTPRequest.js'; } from '../api/HTTPRequest.js';
import type {HTTPResponse} from '../api/HTTPResponse.js';
import type {ProtocolError} from '../common/Errors.js'; import type {ProtocolError} from '../common/Errors.js';
import {debugError, isString} from '../common/util.js'; import {debugError, isString} from '../common/util.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import type {CdpHTTPResponse} from './HTTPResponse.js';
/** /**
* @internal * @internal
*/ */
export class CdpHTTPRequest extends HTTPRequest { export class CdpHTTPRequest extends HTTPRequest {
declare _redirectChain: CdpHTTPRequest[]; declare _redirectChain: CdpHTTPRequest[];
declare _response: CdpHTTPResponse | null;
#client: CDPSession; #client: CDPSession;
#isNavigationRequest: boolean; #isNavigationRequest: boolean;
@ -191,7 +193,7 @@ export class CdpHTTPRequest extends HTTPRequest {
return this.#headers; return this.#headers;
} }
override response(): HTTPResponse | null { override response(): CdpHTTPResponse | null {
return this._response; return this._response;
} }

View File

@ -17,17 +17,18 @@
import type Protocol from 'devtools-protocol'; import type Protocol from 'devtools-protocol';
import {type Frame, FrameEvent} from '../api/Frame.js'; import {type Frame, FrameEvent} from '../api/Frame.js';
import type {HTTPRequest} from '../api/HTTPRequest.js';
import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {HTTPResponse} from '../api/HTTPResponse.js';
import type {TimeoutError} from '../common/Errors.js'; import type {TimeoutError} from '../common/Errors.js';
import {EventSubscription} from '../common/EventEmitter.js'; import {EventSubscription} from '../common/EventEmitter.js';
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {DisposableStack} from '../util/disposable.js'; import {DisposableStack} from '../util/disposable.js';
import type {CdpFrame} from './Frame.js'; import type {CdpFrame} from './Frame.js';
import {FrameManagerEvent} from './FrameManager.js'; import {FrameManagerEvent} from './FrameManager.js';
import type {CdpHTTPRequest} from './HTTPRequest.js'; import type {NetworkManager} from './NetworkManager.js';
import {type NetworkManager, NetworkManagerEvent} from './NetworkManager.js';
/** /**
* @public * @public
@ -78,7 +79,7 @@ export class LifecycleWatcher {
#expectedLifecycle: ProtocolLifeCycleEvent[]; #expectedLifecycle: ProtocolLifeCycleEvent[];
#frame: CdpFrame; #frame: CdpFrame;
#timeout: number; #timeout: number;
#navigationRequest: CdpHTTPRequest | null = null; #navigationRequest: HTTPRequest | null = null;
#subscriptions = new DisposableStack(); #subscriptions = new DisposableStack();
#initialLoaderId: string; #initialLoaderId: string;
@ -184,7 +185,7 @@ export class LifecycleWatcher {
this.#checkLifecycleComplete(); this.#checkLifecycleComplete();
} }
#onRequest(request: CdpHTTPRequest): void { #onRequest(request: HTTPRequest): void {
if (request.frame() !== this.#frame || !request.isNavigationRequest()) { if (request.frame() !== this.#frame || !request.isNavigationRequest()) {
return; return;
} }
@ -199,7 +200,7 @@ export class LifecycleWatcher {
} }
} }
#onRequestFailed(request: CdpHTTPRequest): void { #onRequestFailed(request: HTTPRequest): void {
if (this.#navigationRequest?._requestId !== request._requestId) { if (this.#navigationRequest?._requestId !== request._requestId) {
return; return;
} }

View File

@ -22,9 +22,10 @@ import type {CDPSessionEvents} from '../api/CDPSession.js';
import type {HTTPRequest} from '../api/HTTPRequest.js'; import type {HTTPRequest} from '../api/HTTPRequest.js';
import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {HTTPResponse} from '../api/HTTPResponse.js';
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter} from '../common/EventEmitter.js';
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
import type {CdpFrame} from './Frame.js'; import type {CdpFrame} from './Frame.js';
import {NetworkManager, NetworkManagerEvent} from './NetworkManager.js'; import {NetworkManager} from './NetworkManager.js';
// TODO: develop a helper to generate fake network events for attributes that // TODO: develop a helper to generate fake network events for attributes that
// are not relevant for the network manager to make tests shorter. // are not relevant for the network manager to make tests shorter.

View File

@ -18,11 +18,11 @@ import type {Protocol} from 'devtools-protocol';
import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js';
import type {Frame} from '../api/Frame.js'; import type {Frame} from '../api/Frame.js';
import {EventEmitter, EventSubscription} from '../common/EventEmitter.js';
import { import {
EventEmitter, NetworkManagerEvent,
EventSubscription, type NetworkManagerEvents,
type EventType, } from '../common/NetworkManagerEvents.js';
} from '../common/EventEmitter.js';
import {debugError, isString} from '../common/util.js'; import {debugError, isString} from '../common/util.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {DisposableStack} from '../util/disposable.js'; import {DisposableStack} from '../util/disposable.js';
@ -61,34 +61,6 @@ export interface InternalNetworkConditions extends NetworkConditions {
offline: boolean; offline: boolean;
} }
/**
* We use symbols to prevent any external parties listening to these events.
* They are internal to Puppeteer.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace NetworkManagerEvent {
export const Request = Symbol('NetworkManager.Request');
export const RequestServedFromCache = Symbol(
'NetworkManager.RequestServedFromCache'
);
export const Response = Symbol('NetworkManager.Response');
export const RequestFailed = Symbol('NetworkManager.RequestFailed');
export const RequestFinished = Symbol('NetworkManager.RequestFinished');
}
/**
* @internal
*/
export interface CdpNetworkManagerEvents extends Record<EventType, unknown> {
[NetworkManagerEvent.Request]: CdpHTTPRequest;
[NetworkManagerEvent.RequestServedFromCache]: CdpHTTPRequest | undefined;
[NetworkManagerEvent.Response]: CdpHTTPResponse;
[NetworkManagerEvent.RequestFailed]: CdpHTTPRequest;
[NetworkManagerEvent.RequestFinished]: CdpHTTPRequest;
}
/** /**
* @internal * @internal
*/ */
@ -99,7 +71,7 @@ export interface FrameProvider {
/** /**
* @internal * @internal
*/ */
export class NetworkManager extends EventEmitter<CdpNetworkManagerEvents> { export class NetworkManager extends EventEmitter<NetworkManagerEvents> {
#ignoreHTTPSErrors: boolean; #ignoreHTTPSErrors: boolean;
#frameManager: FrameProvider; #frameManager: FrameProvider;
#networkEventManager = new NetworkEventManager(); #networkEventManager = new NetworkEventManager();

View File

@ -43,8 +43,8 @@ import {
} from '../common/ConsoleMessage.js'; } from '../common/ConsoleMessage.js';
import {TargetCloseError} from '../common/Errors.js'; import {TargetCloseError} from '../common/Errors.js';
import {FileChooser} from '../common/FileChooser.js'; import {FileChooser} from '../common/FileChooser.js';
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
import type {PDFOptions} from '../common/PDFOptions.js'; import type {PDFOptions} from '../common/PDFOptions.js';
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import type {BindingPayload, HandleFor} from '../common/types.js'; import type {BindingPayload, HandleFor} from '../common/types.js';
import { import {
createClientError, createClientError,
@ -52,11 +52,10 @@ import {
evaluationString, evaluationString,
getReadableAsBuffer, getReadableAsBuffer,
getReadableFromProtocolStream, getReadableFromProtocolStream,
isString,
pageBindingInitString, pageBindingInitString,
validateDialogType, validateDialogType,
valueFromRemoteObject, valueFromRemoteObject,
waitForEvent, waitForHTTP,
waitWithTimeout, waitWithTimeout,
} from '../common/util.js'; } from '../common/util.js';
import type {Viewport} from '../common/Viewport.js'; import type {Viewport} from '../common/Viewport.js';
@ -79,11 +78,7 @@ import type {CdpFrame} from './Frame.js';
import {FrameManager, FrameManagerEvent} from './FrameManager.js'; import {FrameManager, FrameManagerEvent} from './FrameManager.js';
import {CdpKeyboard, CdpMouse, CdpTouchscreen} from './Input.js'; import {CdpKeyboard, CdpMouse, CdpTouchscreen} from './Input.js';
import {MAIN_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD} from './IsolatedWorlds.js';
import { import type {Credentials, NetworkConditions} from './NetworkManager.js';
NetworkManagerEvent,
type Credentials,
type NetworkConditions,
} from './NetworkManager.js';
import type {CdpTarget} from './Target.js'; import type {CdpTarget} from './Target.js';
import {TargetManagerEvent} from './TargetManager.js'; import {TargetManagerEvent} from './TargetManager.js';
import {Tracing} from './Tracing.js'; import {Tracing} from './Tracing.js';
@ -121,7 +116,6 @@ export class CdpPage extends Page {
#target: CdpTarget; #target: CdpTarget;
#keyboard: CdpKeyboard; #keyboard: CdpKeyboard;
#mouse: CdpMouse; #mouse: CdpMouse;
#timeoutSettings = new TimeoutSettings();
#touchscreen: CdpTouchscreen; #touchscreen: CdpTouchscreen;
#accessibility: Accessibility; #accessibility: Accessibility;
#frameManager: FrameManager; #frameManager: FrameManager;
@ -239,7 +233,7 @@ export class CdpPage extends Page {
client, client,
this, this,
ignoreHTTPSErrors, ignoreHTTPSErrors,
this.#timeoutSettings this._timeoutSettings
); );
this.#emulationManager = new EmulationManager(client); this.#emulationManager = new EmulationManager(client);
this.#tracing = new Tracing(client); this.#tracing = new Tracing(client);
@ -402,7 +396,7 @@ export class CdpPage extends Page {
options: WaitTimeoutOptions = {} options: WaitTimeoutOptions = {}
): Promise<FileChooser> { ): Promise<FileChooser> {
const needsEnable = this.#fileChooserDeferreds.size === 0; const needsEnable = this.#fileChooserDeferreds.size === 0;
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this._timeoutSettings.timeout()} = options;
const deferred = Deferred.create<FileChooser>({ const deferred = Deferred.create<FileChooser>({
message: `Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`, message: `Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`,
timeout, timeout,
@ -522,15 +516,15 @@ export class CdpPage extends Page {
} }
override setDefaultNavigationTimeout(timeout: number): void { override setDefaultNavigationTimeout(timeout: number): void {
this.#timeoutSettings.setDefaultNavigationTimeout(timeout); this._timeoutSettings.setDefaultNavigationTimeout(timeout);
} }
override setDefaultTimeout(timeout: number): void { override setDefaultTimeout(timeout: number): void {
this.#timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
} }
override getDefaultTimeout(): number { override getDefaultTimeout(): number {
return this.#timeoutSettings.timeout(); return this._timeoutSettings.timeout();
} }
override async queryObjects<Prototype>( override async queryObjects<Prototype>(
@ -876,21 +870,13 @@ export class CdpPage extends Page {
urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise<boolean>), urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise<boolean>),
options: {timeout?: number} = {} options: {timeout?: number} = {}
): Promise<HTTPRequest> { ): Promise<HTTPRequest> {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this._timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForHTTP(
this.#frameManager.networkManager, this.#frameManager.networkManager,
NetworkManagerEvent.Request, NetworkManagerEvent.Request,
async request => { urlOrPredicate,
if (isString(urlOrPredicate)) {
return urlOrPredicate === request.url();
}
if (typeof urlOrPredicate === 'function') {
return !!(await urlOrPredicate(request));
}
return false;
},
timeout, timeout,
this.#sessionCloseDeferred.valueOrThrow() this.#sessionCloseDeferred
); );
} }
@ -900,28 +886,20 @@ export class CdpPage extends Page {
| ((res: HTTPResponse) => boolean | Promise<boolean>), | ((res: HTTPResponse) => boolean | Promise<boolean>),
options: {timeout?: number} = {} options: {timeout?: number} = {}
): Promise<HTTPResponse> { ): Promise<HTTPResponse> {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this._timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForHTTP(
this.#frameManager.networkManager, this.#frameManager.networkManager,
NetworkManagerEvent.Response, NetworkManagerEvent.Response,
async response => { urlOrPredicate,
if (isString(urlOrPredicate)) {
return urlOrPredicate === response.url();
}
if (typeof urlOrPredicate === 'function') {
return !!(await urlOrPredicate(response));
}
return false;
},
timeout, timeout,
this.#sessionCloseDeferred.valueOrThrow() this.#sessionCloseDeferred
); );
} }
override async waitForNetworkIdle( override async waitForNetworkIdle(
options: {idleTime?: number; timeout?: number} = {} options: {idleTime?: number; timeout?: number} = {}
): Promise<void> { ): Promise<void> {
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options; const {idleTime = 500, timeout = this._timeoutSettings.timeout()} = options;
await this._waitForNetworkIdle( await this._waitForNetworkIdle(
this.#frameManager.networkManager, this.#frameManager.networkManager,

View File

@ -0,0 +1,48 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {HTTPRequest} from '../api/HTTPRequest.js';
import type {HTTPResponse} from '../api/HTTPResponse.js';
import type {EventType} from './EventEmitter.js';
/**
* We use symbols to prevent any external parties listening to these events.
* They are internal to Puppeteer.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace NetworkManagerEvent {
export const Request = Symbol('NetworkManager.Request');
export const RequestServedFromCache = Symbol(
'NetworkManager.RequestServedFromCache'
);
export const Response = Symbol('NetworkManager.Response');
export const RequestFailed = Symbol('NetworkManager.RequestFailed');
export const RequestFinished = Symbol('NetworkManager.RequestFinished');
}
/**
* @internal
*/
export interface NetworkManagerEvents extends Record<EventType, unknown> {
[NetworkManagerEvent.Request]: HTTPRequest;
[NetworkManagerEvent.RequestServedFromCache]: HTTPRequest | undefined;
[NetworkManagerEvent.Response]: HTTPResponse;
[NetworkManagerEvent.RequestFailed]: HTTPRequest;
[NetworkManagerEvent.RequestFinished]: HTTPRequest;
}

View File

@ -15,6 +15,7 @@
*/ */
export * from './BrowserWebSocketTransport.js'; export * from './BrowserWebSocketTransport.js';
export * from './common.js';
export * from './Configuration.js'; export * from './Configuration.js';
export * from './ConnectionTransport.js'; export * from './ConnectionTransport.js';
export * from './ConsoleMessage.js'; export * from './ConsoleMessage.js';
@ -23,13 +24,15 @@ export * from './Debug.js';
export * from './Device.js'; export * from './Device.js';
export * from './Errors.js'; export * from './Errors.js';
export * from './EventEmitter.js'; export * from './EventEmitter.js';
export * from './fetch.js';
export * from './FileChooser.js'; export * from './FileChooser.js';
export * from './GetQueryHandler.js'; export * from './GetQueryHandler.js';
export * from './HandleIterator.js'; export * from './HandleIterator.js';
export * from './LazyArg.js'; export * from './LazyArg.js';
export * from './NetworkManagerEvents.js';
export * from './PDFOptions.js'; export * from './PDFOptions.js';
export * from './PQueryHandler.js';
export * from './PierceQueryHandler.js'; export * from './PierceQueryHandler.js';
export * from './PQueryHandler.js';
export * from './Product.js'; export * from './Product.js';
export * from './QueryHandler.js'; export * from './QueryHandler.js';
export * from './ScriptInjector.js'; export * from './ScriptInjector.js';
@ -37,11 +40,9 @@ export * from './SecurityDetails.js';
export * from './TaskQueue.js'; export * from './TaskQueue.js';
export * from './TextQueryHandler.js'; export * from './TextQueryHandler.js';
export * from './TimeoutSettings.js'; export * from './TimeoutSettings.js';
export * from './types.js';
export * from './USKeyboardLayout.js'; export * from './USKeyboardLayout.js';
export * from './util.js';
export * from './Viewport.js'; export * from './Viewport.js';
export * from './WaitTask.js'; export * from './WaitTask.js';
export * from './XPathQueryHandler.js'; export * from './XPathQueryHandler.js';
export * from './common.js';
export * from './fetch.js';
export * from './types.js';
export * from './util.js';

View File

@ -24,6 +24,11 @@ import {
NEVER, NEVER,
timer, timer,
type Observable, type Observable,
firstValueFrom,
fromEvent,
filterAsync,
from,
raceWith,
} from '../../third_party/rxjs/rxjs.js'; } from '../../third_party/rxjs/rxjs.js';
import type {CDPSession} from '../api/CDPSession.js'; import type {CDPSession} from '../api/CDPSession.js';
import type {Page} from '../api/Page.js'; import type {Page} from '../api/Page.js';
@ -34,8 +39,8 @@ import {isErrorLike} from '../util/ErrorLike.js';
import {debug} from './Debug.js'; import {debug} from './Debug.js';
import {TimeoutError} from './Errors.js'; import {TimeoutError} from './Errors.js';
import {EventSubscription} from './EventEmitter.js'; import type {EventEmitter, EventType} from './EventEmitter.js';
import type {Awaitable} from './types.js'; import type {NetworkManagerEvents} from './NetworkManagerEvents.js';
/** /**
* @internal * @internal
@ -326,39 +331,6 @@ export const isDate = (obj: unknown): obj is Date => {
return typeof obj === 'object' && obj?.constructor === Date; return typeof obj === 'object' && obj?.constructor === Date;
}; };
/**
* @internal
*/
export async function waitForEvent<T>(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
emitter: any,
eventName: string | symbol,
predicate: (event: T) => Awaitable<boolean>,
timeout: number,
abortPromise: Promise<Error> | Deferred<Error>
): Promise<T> {
const deferred = Deferred.create<T>({
message: `Timeout exceeded while waiting for event ${String(eventName)}`,
timeout,
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
using _ = new EventSubscription(emitter, eventName, async (event: any) => {
if (await predicate(event)) {
deferred.resolve(event);
}
});
try {
const response = await Deferred.race<T | Error>([deferred, abortPromise]);
if (isErrorLike(response)) {
throw response;
}
return response;
} catch (error) {
throw error;
}
}
/** /**
* @internal * @internal
*/ */
@ -632,3 +604,32 @@ export const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
export function getSourceUrlComment(url: string): string { export function getSourceUrlComment(url: string): string {
return `//# sourceURL=${url}`; return `//# sourceURL=${url}`;
} }
/**
* @internal
*/
export async function waitForHTTP<T extends {url(): string}>(
networkManager: EventEmitter<NetworkManagerEvents>,
eventName: EventType,
urlOrPredicate: string | ((res: T) => boolean | Promise<boolean>),
/** Time after the function will timeout */
ms: number,
cancelation: Deferred<never>
): Promise<T> {
return await firstValueFrom(
(
fromEvent(networkManager, eventName as unknown as string) as Observable<T>
).pipe(
filterAsync(async http => {
if (isString(urlOrPredicate)) {
return urlOrPredicate === http.url();
}
if (typeof urlOrPredicate === 'function') {
return !!(await urlOrPredicate(http));
}
return false;
}),
raceWith(timeout(ms), from(cancelation.valueOrThrow()))
)
);
}