mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore(webdriver): support priority for request interception (#12191)
This commit is contained in:
parent
59bffce972
commit
08bc8542ea
@ -10,7 +10,7 @@ Aborts a request.
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract abort(errorCode?: ErrorCode, priority?: number): Promise<void>;
|
||||
abort(errorCode?: ErrorCode, priority?: number): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -10,7 +10,7 @@ Continues request with optional request overrides.
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract continue(
|
||||
continue(
|
||||
overrides?: ContinueRequestOverrides,
|
||||
priority?: number
|
||||
): Promise<void>;
|
||||
|
@ -10,7 +10,7 @@ The `ContinueRequestOverrides` that will be used if the interception is allowed
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract continueRequestOverrides(): ContinueRequestOverrides;
|
||||
continueRequestOverrides(): ContinueRequestOverrides;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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<unknown>
|
||||
): void;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ Awaits pending interception handlers and then decides how to fulfill the request
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract finalizeInterceptions(): Promise<void>;
|
||||
finalizeInterceptions(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -14,7 +14,7 @@ InterceptResolutionAction is one of: `abort`, `respond`, `continue`, `disabled`,
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract interceptResolutionState(): InterceptResolutionState;
|
||||
interceptResolutionState(): InterceptResolutionState;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -10,7 +10,7 @@ Is `true` if the intercept resolution has already been handled, `false` otherwis
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract isInterceptResolutionHandled(): boolean;
|
||||
isInterceptResolutionHandled(): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -10,7 +10,7 @@ Fulfills a request with the given response.
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract respond(
|
||||
respond(
|
||||
response: Partial<ResponseForRequest>,
|
||||
priority?: number
|
||||
): Promise<void>;
|
||||
|
@ -10,7 +10,7 @@ The `ResponseForRequest` that gets used if the interception is allowed to respon
|
||||
|
||||
```typescript
|
||||
class HTTPRequest {
|
||||
abstract responseForRequest(): Partial<ResponseForRequest> | null;
|
||||
responseForRequest(): Partial<ResponseForRequest> | null;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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<any>>;
|
||||
resolutionState: InterceptResolutionState;
|
||||
requestOverrides: ContinueRequestOverrides;
|
||||
response: Partial<ResponseForRequest> | 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<ResponseForRequest> | null;
|
||||
responseForRequest(): Partial<ResponseForRequest> | 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<unknown>
|
||||
): void;
|
||||
): void {
|
||||
this.interception.handlers.push(pendingHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract _abort(
|
||||
errorReason: Protocol.Network.ErrorReason | null
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract _respond(response: Partial<ResponseForRequest>): Promise<void>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract _continue(overrides: ContinueRequestOverrides): Promise<void>;
|
||||
|
||||
/**
|
||||
* Awaits pending interception handlers and then decides how to fulfill
|
||||
* the request interception.
|
||||
*/
|
||||
abstract finalizeInterceptions(): Promise<void>;
|
||||
async finalizeInterceptions(): Promise<void> {
|
||||
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<void>;
|
||||
): Promise<void> {
|
||||
// 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<ResponseForRequest>,
|
||||
priority?: number
|
||||
): Promise<void>;
|
||||
): Promise<void> {
|
||||
// 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<void>;
|
||||
async abort(
|
||||
errorCode: ErrorCode = 'failed',
|
||||
priority?: number
|
||||
): Promise<void> {
|
||||
// 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<string, string> = {
|
||||
'511': 'Network Authentication Required',
|
||||
} as const;
|
||||
|
||||
const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = {
|
||||
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
|
||||
*/
|
||||
|
@ -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}) => {
|
||||
|
@ -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<unknown>
|
||||
): 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<ResponseForRequest>,
|
||||
_priority?: number
|
||||
): Promise<void> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<string, string> = {};
|
||||
#frame: Frame | null;
|
||||
#continueRequestOverrides: ContinueRequestOverrides;
|
||||
#responseForRequest: Partial<ResponseForRequest> | null = null;
|
||||
#abortErrorReason: Protocol.Network.ErrorReason | null = null;
|
||||
#interceptResolutionState: InterceptResolutionState = {
|
||||
action: InterceptResolutionAction.None,
|
||||
};
|
||||
#interceptHandlers: Array<() => void | PromiseLike<any>>;
|
||||
#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<ResponseForRequest> | 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<unknown>
|
||||
): void {
|
||||
this.#interceptHandlers.push(pendingHandler);
|
||||
}
|
||||
|
||||
override async finalizeInterceptions(): Promise<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> {
|
||||
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<ResponseForRequest>,
|
||||
priority?: number
|
||||
): Promise<void> {
|
||||
// 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<ResponseForRequest>): Promise<void> {
|
||||
this.#interceptionHandled = true;
|
||||
async _respond(response: Partial<ResponseForRequest>): Promise<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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<ErrorCode, Protocol.Network.ErrorReason> = {
|
||||
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;
|
||||
|
@ -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"],
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user