diff --git a/docs/api/puppeteer.httprequest.abort.md b/docs/api/puppeteer.httprequest.abort.md index 8e4d4d5db88..bc50ef03d02 100644 --- a/docs/api/puppeteer.httprequest.abort.md +++ b/docs/api/puppeteer.httprequest.abort.md @@ -10,7 +10,7 @@ Aborts a request. ```typescript class HTTPRequest { - abstract abort(errorCode?: ErrorCode, priority?: number): Promise; + abort(errorCode?: ErrorCode, priority?: number): Promise; } ``` diff --git a/docs/api/puppeteer.httprequest.aborterrorreason.md b/docs/api/puppeteer.httprequest.aborterrorreason.md index 2abe6d25f70..ed4c782d923 100644 --- a/docs/api/puppeteer.httprequest.aborterrorreason.md +++ b/docs/api/puppeteer.httprequest.aborterrorreason.md @@ -10,7 +10,7 @@ The most recent reason for aborting the request ```typescript class HTTPRequest { - abstract abortErrorReason(): Protocol.Network.ErrorReason | null; + abortErrorReason(): Protocol.Network.ErrorReason | null; } ``` diff --git a/docs/api/puppeteer.httprequest.continue.md b/docs/api/puppeteer.httprequest.continue.md index f19584e8130..54164d3058c 100644 --- a/docs/api/puppeteer.httprequest.continue.md +++ b/docs/api/puppeteer.httprequest.continue.md @@ -10,7 +10,7 @@ Continues request with optional request overrides. ```typescript class HTTPRequest { - abstract continue( + continue( overrides?: ContinueRequestOverrides, priority?: number ): Promise; diff --git a/docs/api/puppeteer.httprequest.continuerequestoverrides.md b/docs/api/puppeteer.httprequest.continuerequestoverrides.md index b04b26c3916..832d430deeb 100644 --- a/docs/api/puppeteer.httprequest.continuerequestoverrides.md +++ b/docs/api/puppeteer.httprequest.continuerequestoverrides.md @@ -10,7 +10,7 @@ The `ContinueRequestOverrides` that will be used if the interception is allowed ```typescript class HTTPRequest { - abstract continueRequestOverrides(): ContinueRequestOverrides; + continueRequestOverrides(): ContinueRequestOverrides; } ``` diff --git a/docs/api/puppeteer.httprequest.enqueueinterceptaction.md b/docs/api/puppeteer.httprequest.enqueueinterceptaction.md index 376c4e70294..5f848787eb9 100644 --- a/docs/api/puppeteer.httprequest.enqueueinterceptaction.md +++ b/docs/api/puppeteer.httprequest.enqueueinterceptaction.md @@ -10,7 +10,7 @@ Adds an async request handler to the processing queue. Deferred handlers are not ```typescript class HTTPRequest { - abstract enqueueInterceptAction( + enqueueInterceptAction( pendingHandler: () => void | PromiseLike ): void; } diff --git a/docs/api/puppeteer.httprequest.finalizeinterceptions.md b/docs/api/puppeteer.httprequest.finalizeinterceptions.md index 661e16310e9..3245e3ce3ce 100644 --- a/docs/api/puppeteer.httprequest.finalizeinterceptions.md +++ b/docs/api/puppeteer.httprequest.finalizeinterceptions.md @@ -10,7 +10,7 @@ Awaits pending interception handlers and then decides how to fulfill the request ```typescript class HTTPRequest { - abstract finalizeInterceptions(): Promise; + finalizeInterceptions(): Promise; } ``` diff --git a/docs/api/puppeteer.httprequest.interceptresolutionstate.md b/docs/api/puppeteer.httprequest.interceptresolutionstate.md index 524104e5e6c..2adca7ac17b 100644 --- a/docs/api/puppeteer.httprequest.interceptresolutionstate.md +++ b/docs/api/puppeteer.httprequest.interceptresolutionstate.md @@ -14,7 +14,7 @@ InterceptResolutionAction is one of: `abort`, `respond`, `continue`, `disabled`, ```typescript class HTTPRequest { - abstract interceptResolutionState(): InterceptResolutionState; + interceptResolutionState(): InterceptResolutionState; } ``` diff --git a/docs/api/puppeteer.httprequest.isinterceptresolutionhandled.md b/docs/api/puppeteer.httprequest.isinterceptresolutionhandled.md index 31403fa967f..2807a9cfb16 100644 --- a/docs/api/puppeteer.httprequest.isinterceptresolutionhandled.md +++ b/docs/api/puppeteer.httprequest.isinterceptresolutionhandled.md @@ -10,7 +10,7 @@ Is `true` if the intercept resolution has already been handled, `false` otherwis ```typescript class HTTPRequest { - abstract isInterceptResolutionHandled(): boolean; + isInterceptResolutionHandled(): boolean; } ``` diff --git a/docs/api/puppeteer.httprequest.respond.md b/docs/api/puppeteer.httprequest.respond.md index dc28c5a5505..087fa3395d5 100644 --- a/docs/api/puppeteer.httprequest.respond.md +++ b/docs/api/puppeteer.httprequest.respond.md @@ -10,7 +10,7 @@ Fulfills a request with the given response. ```typescript class HTTPRequest { - abstract respond( + respond( response: Partial, priority?: number ): Promise; diff --git a/docs/api/puppeteer.httprequest.responseforrequest.md b/docs/api/puppeteer.httprequest.responseforrequest.md index 581b6c24534..168b83131b1 100644 --- a/docs/api/puppeteer.httprequest.responseforrequest.md +++ b/docs/api/puppeteer.httprequest.responseforrequest.md @@ -10,7 +10,7 @@ The `ResponseForRequest` that gets used if the interception is allowed to respon ```typescript class HTTPRequest { - abstract responseForRequest(): Partial | null; + responseForRequest(): Partial | null; } ``` diff --git a/packages/puppeteer-core/src/api/HTTPRequest.ts b/packages/puppeteer-core/src/api/HTTPRequest.ts index 012fea61222..674abc61f2e 100644 --- a/packages/puppeteer-core/src/api/HTTPRequest.ts +++ b/packages/puppeteer-core/src/api/HTTPRequest.ts @@ -7,6 +7,7 @@ import type {Protocol} from 'devtools-protocol'; import type {ProtocolError} from '../common/Errors.js'; import {debugError} from '../common/util.js'; +import {assert} from '../util/assert.js'; import type {CDPSession} from './CDPSession.js'; import type {Frame} from './Frame.js'; @@ -120,6 +121,29 @@ export abstract class HTTPRequest { */ _redirectChain: HTTPRequest[] = []; + /** + * @internal + */ + protected interception: { + enabled: boolean; + handled: boolean; + handlers: Array<() => void | PromiseLike>; + resolutionState: InterceptResolutionState; + requestOverrides: ContinueRequestOverrides; + response: Partial | null; + abortReason: Protocol.Network.ErrorReason | null; + } = { + enabled: false, + handled: false, + handlers: [], + resolutionState: { + action: InterceptResolutionAction.None, + }, + requestOverrides: {}, + response: null, + abortReason: null, + }; + /** * Warning! Using this client can break Puppeteer. Use with caution. * @@ -142,18 +166,27 @@ export abstract class HTTPRequest { * if the interception is allowed to continue (ie, `abort()` and * `respond()` aren't called). */ - abstract continueRequestOverrides(): ContinueRequestOverrides; + continueRequestOverrides(): ContinueRequestOverrides { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.requestOverrides; + } /** * The `ResponseForRequest` that gets used if the * interception is allowed to respond (ie, `abort()` is not called). */ - abstract responseForRequest(): Partial | null; + responseForRequest(): Partial | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.response; + } /** * The most recent reason for aborting the request */ - abstract abortErrorReason(): Protocol.Network.ErrorReason | null; + abortErrorReason(): Protocol.Network.ErrorReason | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.abortReason; + } /** * An InterceptResolutionState object describing the current resolution @@ -166,13 +199,23 @@ export abstract class HTTPRequest { * InterceptResolutionAction is one of: `abort`, `respond`, `continue`, * `disabled`, `none`, or `already-handled`. */ - abstract interceptResolutionState(): InterceptResolutionState; + interceptResolutionState(): InterceptResolutionState { + if (!this.interception.enabled) { + return {action: InterceptResolutionAction.Disabled}; + } + if (this.interception.handled) { + return {action: InterceptResolutionAction.AlreadyHandled}; + } + return {...this.interception.resolutionState}; + } /** * Is `true` if the intercept resolution has already been handled, * `false` otherwise. */ - abstract isInterceptResolutionHandled(): boolean; + isInterceptResolutionHandled(): boolean { + return this.interception.handled; + } /** * Adds an async request handler to the processing queue. @@ -180,15 +223,51 @@ export abstract class HTTPRequest { * but they are guaranteed to resolve before the request interception * is finalized. */ - abstract enqueueInterceptAction( + enqueueInterceptAction( pendingHandler: () => void | PromiseLike - ): void; + ): void { + this.interception.handlers.push(pendingHandler); + } + + /** + * @internal + */ + abstract _abort( + errorReason: Protocol.Network.ErrorReason | null + ): Promise; + + /** + * @internal + */ + abstract _respond(response: Partial): Promise; + + /** + * @internal + */ + abstract _continue(overrides: ContinueRequestOverrides): Promise; /** * Awaits pending interception handlers and then decides how to fulfill * the request interception. */ - abstract finalizeInterceptions(): Promise; + async finalizeInterceptions(): Promise { + await this.interception.handlers.reduce((promiseChain, interceptAction) => { + return promiseChain.then(interceptAction); + }, Promise.resolve()); + this.interception.handlers = []; // TODO: verify this is correct top let gc run + const {action} = this.interceptResolutionState(); + switch (action) { + case 'abort': + return await this._abort(this.interception.abortReason); + case 'respond': + if (this.interception.response === null) { + throw new Error('Response is missing for the interception'); + } + return await this._respond(this.interception.response); + case 'continue': + return await this._continue(this.interception.requestOverrides); + } + } /** * Contains the request's resource type as it was perceived by the rendering @@ -326,10 +405,42 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract continue( - overrides?: ContinueRequestOverrides, + async continue( + overrides: ContinueRequestOverrides = {}, priority?: number - ): Promise; + ): Promise { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._continue(overrides); + } + this.interception.requestOverrides = overrides; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Continue, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if ( + this.interception.resolutionState.action === 'abort' || + this.interception.resolutionState.action === 'respond' + ) { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Continue; + } + return; + } /** * Fulfills a request with the given response. @@ -363,10 +474,38 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract respond( + async respond( response: Partial, priority?: number - ): Promise; + ): Promise { + // Mocking responses for dataURL requests is not currently supported. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._respond(response); + } + this.interception.response = response; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Respond, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if (this.interception.resolutionState.action === 'abort') { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Respond; + } + } /** * Aborts a request. @@ -382,7 +521,33 @@ export abstract class HTTPRequest { * {@link Page.setRequestInterception}. If it is not enabled, this method will * throw an exception immediately. */ - abstract abort(errorCode?: ErrorCode, priority?: number): Promise; + async abort( + errorCode: ErrorCode = 'failed', + priority?: number + ): Promise { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + const errorReason = errorReasons[errorCode]; + assert(errorReason, 'Unknown error code: ' + errorCode); + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._abort(errorReason); + } + this.interception.abortReason = errorReason; + if ( + this.interception.resolutionState.priority === undefined || + priority >= this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Abort, + priority, + }; + return; + } + } } /** @@ -517,6 +682,23 @@ export const STATUS_TEXTS: Record = { '511': 'Network Authentication Required', } as const; +const errorReasons: Record = { + aborted: 'Aborted', + accessdenied: 'AccessDenied', + addressunreachable: 'AddressUnreachable', + blockedbyclient: 'BlockedByClient', + blockedbyresponse: 'BlockedByResponse', + connectionaborted: 'ConnectionAborted', + connectionclosed: 'ConnectionClosed', + connectionfailed: 'ConnectionFailed', + connectionrefused: 'ConnectionRefused', + connectionreset: 'ConnectionReset', + internetdisconnected: 'InternetDisconnected', + namenotresolved: 'NameNotResolved', + timedout: 'TimedOut', + failed: 'Failed', +} as const; + /** * @internal */ diff --git a/packages/puppeteer-core/src/bidi/Frame.ts b/packages/puppeteer-core/src/bidi/Frame.ts index 15d2c94b0fc..94dab70619b 100644 --- a/packages/puppeteer-core/src/bidi/Frame.ts +++ b/packages/puppeteer-core/src/bidi/Frame.ts @@ -121,6 +121,7 @@ export class BidiFrame extends Frame { request.once('error', () => { this.page().trustedEmitter.emit(PageEvent.RequestFailed, httpRequest); }); + void httpRequest.finalizeInterceptions(); }); this.browsingContext.on('navigation', ({navigation}) => { diff --git a/packages/puppeteer-core/src/bidi/HTTPRequest.ts b/packages/puppeteer-core/src/bidi/HTTPRequest.ts index 1bede567ed8..73bccbabc75 100644 --- a/packages/puppeteer-core/src/bidi/HTTPRequest.ts +++ b/packages/puppeteer-core/src/bidi/HTTPRequest.ts @@ -48,6 +48,8 @@ export class BidiHTTPRequest extends HTTPRequest { super(); requests.set(request, this); + this.interception.enabled = request.isBlocked; + this.#request = request; this.#frame = frame; this.id = request.id; @@ -60,6 +62,7 @@ export class BidiHTTPRequest extends HTTPRequest { #initialize() { this.#request.on('redirect', request => { this.#redirect = BidiHTTPRequest.from(request, this.#frame); + void this.#redirect.finalizeInterceptions(); }); this.#request.once('success', data => { this.#response = BidiHTTPResponse.from(data, this); @@ -132,33 +135,15 @@ export class BidiHTTPRequest extends HTTPRequest { return redirects; } - override enqueueInterceptAction( - pendingHandler: () => void | PromiseLike - ): void { - // Execute the handler when interception is not supported - void pendingHandler(); - } - override frame(): BidiFrame | null { return this.#frame ?? null; } - override continueRequestOverrides(): never { - throw new UnsupportedOperation(); - } - - override async continue( + override async _continue( overrides: ContinueRequestOverrides = {} ): Promise { - if (!this.#request.isBlocked) { - throw new Error('Request Interception is not enabled!'); - } - // Request interception is not supported for data: urls. - if (this.url().startsWith('data:')) { - return; - } - const headers: Bidi.Network.Header[] = getBidiHeaders(overrides.headers); + this.interception.handled = true; return await this.#request .continueRequest({ @@ -173,53 +158,24 @@ export class BidiHTTPRequest extends HTTPRequest { headers: headers.length > 0 ? headers : undefined, }) .catch(error => { + this.interception.handled = false; return handleError(error); }); } - override responseForRequest(): never { - throw new UnsupportedOperation(); + override async _abort(): Promise { + this.interception.handled = true; + return await this.#request.failRequest().catch(error => { + this.interception.handled = false; + throw error; + }); } - override abortErrorReason(): never { - throw new UnsupportedOperation(); - } - - override interceptResolutionState(): never { - throw new UnsupportedOperation(); - } - - override isInterceptResolutionHandled(): never { - throw new UnsupportedOperation(); - } - - override finalizeInterceptions(): never { - throw new UnsupportedOperation(); - } - - override async abort(): Promise { - if (!this.#request.isBlocked) { - throw new Error('Request Interception is not enabled!'); - } - // Request interception is not supported for data: urls. - if (this.url().startsWith('data:')) { - return; - } - return await this.#request.failRequest(); - } - - override async respond( + override async _respond( response: Partial, _priority?: number ): Promise { - if (!this.#request.isBlocked) { - throw new Error('Request Interception is not enabled!'); - } - // Request interception is not supported for data: urls. - if (this.url().startsWith('data:')) { - return; - } - + this.interception.handled = true; const responseBody: string | undefined = response.body && response.body instanceof Uint8Array ? response.body.toString('base64') @@ -254,17 +210,22 @@ export class BidiHTTPRequest extends HTTPRequest { } const status = response.status || 200; - return await this.#request.provideResponse({ - statusCode: status, - headers: headers.length > 0 ? headers : undefined, - reasonPhrase: STATUS_TEXTS[status], - body: responseBody - ? { - type: 'base64', - value: responseBody, - } - : undefined, - }); + return await this.#request + .provideResponse({ + statusCode: status, + headers: headers.length > 0 ? headers : undefined, + reasonPhrase: STATUS_TEXTS[status], + body: responseBody + ? { + type: 'base64', + value: responseBody, + } + : undefined, + }) + .catch(error => { + this.interception.handled = false; + throw error; + }); } } diff --git a/packages/puppeteer-core/src/cdp/HTTPRequest.ts b/packages/puppeteer-core/src/cdp/HTTPRequest.ts index 35fdcc7fff7..59fc0c940a8 100644 --- a/packages/puppeteer-core/src/cdp/HTTPRequest.ts +++ b/packages/puppeteer-core/src/cdp/HTTPRequest.ts @@ -9,18 +9,14 @@ import type {CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; import { type ContinueRequestOverrides, - type ErrorCode, headersArray, HTTPRequest, - InterceptResolutionAction, - type InterceptResolutionState, type ResourceType, type ResponseForRequest, STATUS_TEXTS, handleError, } from '../api/HTTPRequest.js'; import {debugError, isString} from '../common/util.js'; -import {assert} from '../util/assert.js'; import type {CdpHTTPResponse} from './HTTPResponse.js'; @@ -34,8 +30,7 @@ export class CdpHTTPRequest extends HTTPRequest { #client: CDPSession; #isNavigationRequest: boolean; - #allowInterception: boolean; - #interceptionHandled = false; + #url: string; #resourceType: ResourceType; @@ -44,13 +39,6 @@ export class CdpHTTPRequest extends HTTPRequest { #postData?: string; #headers: Record = {}; #frame: Frame | null; - #continueRequestOverrides: ContinueRequestOverrides; - #responseForRequest: Partial | null = null; - #abortErrorReason: Protocol.Network.ErrorReason | null = null; - #interceptResolutionState: InterceptResolutionState = { - action: InterceptResolutionAction.None, - }; - #interceptHandlers: Array<() => void | PromiseLike>; #initiator?: Protocol.Network.Initiator; override get client(): CDPSession { @@ -96,7 +84,6 @@ export class CdpHTTPRequest extends HTTPRequest { this.#isNavigationRequest = data.requestId === data.loaderId && data.type === 'Document'; this._interceptionId = interceptionId; - this.#allowInterception = allowInterception; this.#url = data.request.url; this.#resourceType = (data.type || 'other').toLowerCase() as ResourceType; this.#method = data.request.method; @@ -104,10 +91,10 @@ export class CdpHTTPRequest extends HTTPRequest { this.#hasPostData = data.request.hasPostData ?? false; this.#frame = frame; this._redirectChain = redirectChain; - this.#continueRequestOverrides = {}; - this.#interceptHandlers = []; this.#initiator = data.initiator; + this.interception.enabled = allowInterception; + for (const [key, value] of Object.entries(data.request.headers)) { this.#headers[key.toLowerCase()] = value; } @@ -117,59 +104,6 @@ export class CdpHTTPRequest extends HTTPRequest { return this.#url; } - override continueRequestOverrides(): ContinueRequestOverrides { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#continueRequestOverrides; - } - - override responseForRequest(): Partial | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#responseForRequest; - } - - override abortErrorReason(): Protocol.Network.ErrorReason | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#abortErrorReason; - } - - override interceptResolutionState(): InterceptResolutionState { - if (!this.#allowInterception) { - return {action: InterceptResolutionAction.Disabled}; - } - if (this.#interceptionHandled) { - return {action: InterceptResolutionAction.AlreadyHandled}; - } - return {...this.#interceptResolutionState}; - } - - override isInterceptResolutionHandled(): boolean { - return this.#interceptionHandled; - } - - enqueueInterceptAction( - pendingHandler: () => void | PromiseLike - ): void { - this.#interceptHandlers.push(pendingHandler); - } - - override async finalizeInterceptions(): Promise { - await this.#interceptHandlers.reduce((promiseChain, interceptAction) => { - return promiseChain.then(interceptAction); - }, Promise.resolve()); - const {action} = this.interceptResolutionState(); - switch (action) { - case 'abort': - return await this.#abort(this.#abortErrorReason); - case 'respond': - if (this.#responseForRequest === null) { - throw new Error('Response is missing for the interception'); - } - return await this.#respond(this.#responseForRequest); - case 'continue': - return await this.#continue(this.#continueRequestOverrides); - } - } - override resourceType(): ResourceType { return this.#resourceType; } @@ -231,46 +165,12 @@ export class CdpHTTPRequest extends HTTPRequest { }; } - override async continue( - overrides: ContinueRequestOverrides = {}, - 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 (priority === undefined) { - return await this.#continue(overrides); - } - this.#continueRequestOverrides = overrides; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Continue, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if ( - this.#interceptResolutionState.action === 'abort' || - this.#interceptResolutionState.action === 'respond' - ) { - return; - } - this.#interceptResolutionState.action = - InterceptResolutionAction.Continue; - } - return; - } - - async #continue(overrides: ContinueRequestOverrides = {}): Promise { + /** + * @internal + */ + async _continue(overrides: ContinueRequestOverrides = {}): Promise { const {url, method, postData, headers} = overrides; - this.#interceptionHandled = true; + this.interception.handled = true; const postDataBinaryBase64 = postData ? Buffer.from(postData).toString('base64') @@ -290,45 +190,13 @@ export class CdpHTTPRequest extends HTTPRequest { headers: headers ? headersArray(headers) : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async respond( - response: Partial, - 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 (priority === undefined) { - return await this.#respond(response); - } - this.#responseForRequest = response; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Respond, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if (this.#interceptResolutionState.action === 'abort') { - return; - } - this.#interceptResolutionState.action = InterceptResolutionAction.Respond; - } - } - - async #respond(response: Partial): Promise { - this.#interceptionHandled = true; + async _respond(response: Partial): Promise { + this.interception.handled = true; const responseBody: Buffer | null = response.body && isString(response.body) @@ -371,43 +239,15 @@ export class CdpHTTPRequest extends HTTPRequest { body: responseBody ? responseBody.toString('base64') : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async abort( - errorCode: ErrorCode = 'failed', - priority?: number - ): Promise { - // Request interception is not supported for data: urls. - 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!'); - if (priority === undefined) { - return await this.#abort(errorReason); - } - this.#abortErrorReason = errorReason; - if ( - this.#interceptResolutionState.priority === undefined || - priority >= this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Abort, - priority, - }; - return; - } - } - - async #abort( + async _abort( errorReason: Protocol.Network.ErrorReason | null ): Promise { - this.#interceptionHandled = true; + this.interception.handled = true; if (this._interceptionId === undefined) { throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.failRequest' @@ -421,20 +261,3 @@ export class CdpHTTPRequest extends HTTPRequest { .catch(handleError); } } - -const errorReasons: Record = { - aborted: 'Aborted', - accessdenied: 'AccessDenied', - addressunreachable: 'AddressUnreachable', - blockedbyclient: 'BlockedByClient', - blockedbyresponse: 'BlockedByResponse', - connectionaborted: 'ConnectionAborted', - connectionclosed: 'ConnectionClosed', - connectionfailed: 'ConnectionFailed', - connectionrefused: 'ConnectionRefused', - connectionreset: 'ConnectionReset', - internetdisconnected: 'InternetDisconnected', - namenotresolved: 'NameNotResolved', - timedout: 'TimedOut', - failed: 'Failed', -} as const; diff --git a/test/TestExpectations.json b/test/TestExpectations.json index e5b1defb14b..00e9bc6e4af 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -174,13 +174,6 @@ "expectations": ["SKIP"], "comment": "Chrome-specific test" }, - { - "testIdPattern": "[requestinterception-experimental.spec] *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[screencast.spec] *", "platforms": ["darwin", "linux", "win32"], @@ -812,12 +805,6 @@ "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[requestinterception-experimental.spec] *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["PASS"] - }, { "testIdPattern": "[requestinterception-experimental.spec] *", "platforms": ["darwin", "linux", "win32"], @@ -825,6 +812,13 @@ "expectations": ["FAIL", "SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec expect the request to not trim the hash" + }, { "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.continue *", "platforms": ["darwin", "linux", "win32"], @@ -839,6 +833,13 @@ "expectations": ["SKIP"], "comment": "TODO: Needs full support for continueResponse in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1853887" }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond should redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Puppeteer request.redirectChain issue" + }, { "testIdPattern": "[requestinterception.spec] *", "platforms": ["darwin", "linux", "win32"], @@ -3403,13 +3404,6 @@ "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to access the error reason", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: Needs Puppeteer support for BidiHTTPRequest.abortErrorReason" - }, { "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests", "platforms": ["darwin", "linux", "win32"], @@ -3439,11 +3433,11 @@ "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" }, { - "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively abort by priority", + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cache if cache enabled", "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: Fixed in the next chromium-bidi release" }, { "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively continue by priority", @@ -3480,13 +3474,6 @@ "expectations": ["SKIP"], "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" }, - { - "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: Needs investigation, not clear why the test expects the hash to be removed" - }, { "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should not cache if cache disabled", "platforms": ["darwin", "linux", "win32"], @@ -3570,6 +3557,13 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond should redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers in provideResponse" + }, { "testIdPattern": "[requestinterception-experimental.spec] request interception \"after each\" hook in \"request interception\"", "platforms": ["win32"], @@ -4163,6 +4157,55 @@ "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`request.postData()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`setExtraHTTPHeaders` not implemented" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`setExtraHTTPHeaders` not implemented" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`setExtraHTTPHeaders` not implemented" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, { "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable", "platforms": ["darwin", "linux", "win32"], diff --git a/test/src/requestinterception-experimental.spec.ts b/test/src/requestinterception-experimental.spec.ts index b192060b3d9..ce3429f0b72 100644 --- a/test/src/requestinterception-experimental.spec.ts +++ b/test/src/requestinterception-experimental.spec.ts @@ -23,8 +23,7 @@ describe('cooperative request interception', function () { describe('Page.setRequestInterception', function () { const expectedActions: ActionResult[] = ['abort', 'continue', 'respond']; - while (expectedActions.length > 0) { - const expectedAction = expectedActions.pop(); + for (const expectedAction of expectedActions) { it(`should cooperatively ${expectedAction} by priority`, async () => { const {page, server} = await getTestState();