chore(webdriver): support options in request.continue (#12155)

This commit is contained in:
Nikolay Vitkov 2024-03-27 11:42:12 +01:00 committed by GitHub
parent b144935789
commit 6edb80b2a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 103 additions and 57 deletions

View File

@ -5,6 +5,9 @@
*/ */
import type {Protocol} from 'devtools-protocol'; import type {Protocol} from 'devtools-protocol';
import type {ProtocolError} from '../common/Errors.js';
import {debugError} from '../common/util.js';
import type {CDPSession} from './CDPSession.js'; import type {CDPSession} from './CDPSession.js';
import type {Frame} from './Frame.js'; import type {Frame} from './Frame.js';
import type {HTTPResponse} from './HTTPResponse.js'; import type {HTTPResponse} from './HTTPResponse.js';
@ -513,3 +516,16 @@ export const STATUS_TEXTS: Record<string, string> = {
'510': 'Not Extended', '510': 'Not Extended',
'511': 'Network Authentication Required', '511': 'Network Authentication Required',
} as const; } as const;
/**
* @internal
*/
export function handleError(error: ProtocolError): void {
if (error.originalMessage.includes('Invalid header')) {
throw error;
}
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
}

View File

@ -14,10 +14,10 @@ import {
HTTPRequest, HTTPRequest,
STATUS_TEXTS, STATUS_TEXTS,
type ResourceType, type ResourceType,
handleError,
} from '../api/HTTPRequest.js'; } from '../api/HTTPRequest.js';
import {PageEvent} from '../api/Page.js'; import {PageEvent} from '../api/Page.js';
import {UnsupportedOperation} from '../common/Errors.js'; import {UnsupportedOperation} from '../common/Errors.js';
import {isString} from '../common/util.js';
import type {Request} from './core/Request.js'; import type {Request} from './core/Request.js';
import type {BidiFrame} from './Frame.js'; import type {BidiFrame} from './Frame.js';
@ -148,9 +148,33 @@ export class BidiHTTPRequest extends HTTPRequest {
} }
override async continue( override async continue(
_overrides: ContinueRequestOverrides = {} overrides: ContinueRequestOverrides = {}
): Promise<void> { ): Promise<void> {
return await this.#request.continueRequest(); 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);
return await this.#request
.continueRequest({
url: overrides.url,
method: overrides.method,
body: overrides.postData
? {
type: 'base64',
value: btoa(overrides.postData),
}
: undefined,
headers: headers.length > 0 ? headers : undefined,
})
.catch(error => {
return handleError(error);
});
} }
override responseForRequest(): never { override responseForRequest(): never {
@ -174,6 +198,13 @@ export class BidiHTTPRequest extends HTTPRequest {
} }
override async abort(): Promise<void> { 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(); return await this.#request.failRequest();
} }
@ -181,25 +212,25 @@ export class BidiHTTPRequest extends HTTPRequest {
response: Partial<ResponseForRequest>, response: Partial<ResponseForRequest>,
_priority?: number _priority?: number
): Promise<void> { ): Promise<void> {
const responseBody: Buffer | null = if (!this.#request.isBlocked) {
response.body && isString(response.body) throw new Error('Request Interception is not enabled!');
? Buffer.from(response.body)
: (response.body as Buffer) || null;
const headers: Bidi.Network.Header[] = [];
let hasContentLength = false;
for (const [name, value] of Object.entries(response.headers ?? [])) {
if (name.toLocaleLowerCase() === 'content-length') {
hasContentLength = true;
}
headers.push({
name: name.toLowerCase(),
value: {
type: 'string',
value: String(value),
},
});
} }
// Request interception is not supported for data: urls.
if (this.url().startsWith('data:')) {
return;
}
const responseBody: string | undefined =
response.body && response.body instanceof Uint8Array
? response.body.toString('base64')
: response.body
? btoa(response.body)
: undefined;
const headers: Bidi.Network.Header[] = getBidiHeaders(response.headers);
const hasContentLength = headers.some(header => {
return header.name === 'content-length';
});
if (response.contentType) { if (response.contentType) {
headers.push({ headers.push({
@ -210,16 +241,17 @@ export class BidiHTTPRequest extends HTTPRequest {
}, },
}); });
} }
if (responseBody && !hasContentLength) { if (responseBody && !hasContentLength) {
const encoder = new TextEncoder();
headers.push({ headers.push({
name: 'content-length', name: 'content-length',
value: { value: {
type: 'string', type: 'string',
value: String(Buffer.byteLength(responseBody)), value: String(encoder.encode(responseBody).byteLength),
}, },
}); });
} }
const status = response.status || 200; const status = response.status || 200;
return await this.#request.provideResponse({ return await this.#request.provideResponse({
@ -229,9 +261,24 @@ export class BidiHTTPRequest extends HTTPRequest {
body: responseBody body: responseBody
? { ? {
type: 'base64', type: 'base64',
value: responseBody.toString('base64'), value: responseBody,
} }
: undefined, : undefined,
}); });
} }
} }
function getBidiHeaders(rawHeaders?: Record<string, unknown>) {
const headers: Bidi.Network.Header[] = [];
for (const [name, value] of Object.entries(rawHeaders ?? [])) {
headers.push({
name: name.toLowerCase(),
value: {
type: 'string',
value: String(value),
},
});
}
return headers;
}

View File

@ -141,29 +141,29 @@ export class Request extends EventEmitter<{
get url(): string { get url(): string {
return this.#event.request.url; return this.#event.request.url;
} }
get isBlocked(): boolean {
return this.#event.isBlocked;
}
// keep-sorted end // keep-sorted end
async continueRequest(): Promise<void> { async continueRequest({
if (!this.#event.isBlocked) { url,
throw new Error('Request Interception is not enabled!'); method,
} headers,
// Request interception is not supported for data: urls. cookies,
if (this.url.startsWith('data:')) { body,
return; }: Omit<Bidi.Network.ContinueRequestParameters, 'request'>): Promise<void> {
}
await this.#session.send('network.continueRequest', { await this.#session.send('network.continueRequest', {
request: this.id, request: this.id,
url,
method,
headers,
body,
cookies,
}); });
} }
async failRequest(): Promise<void> { async failRequest(): Promise<void> {
if (!this.#event.isBlocked) {
throw new Error('Request Interception is not enabled!');
}
// Request interception is not supported for data: urls.
if (this.url.startsWith('data:')) {
return;
}
await this.#session.send('network.failRequest', { await this.#session.send('network.failRequest', {
request: this.id, request: this.id,
}); });
@ -175,13 +175,6 @@ export class Request extends EventEmitter<{
headers, headers,
body, body,
}: Omit<Bidi.Network.ProvideResponseParameters, 'request'>): Promise<void> { }: Omit<Bidi.Network.ProvideResponseParameters, 'request'>): Promise<void> {
if (!this.#event.isBlocked) {
throw new Error('Request Interception is not enabled!');
}
// Request interception is not supported for data: urls.
if (this.url.startsWith('data:')) {
return;
}
await this.#session.send('network.provideResponse', { await this.#session.send('network.provideResponse', {
request: this.id, request: this.id,
statusCode, statusCode,

View File

@ -17,8 +17,8 @@ import {
type ResourceType, type ResourceType,
type ResponseForRequest, type ResponseForRequest,
STATUS_TEXTS, STATUS_TEXTS,
handleError,
} from '../api/HTTPRequest.js'; } from '../api/HTTPRequest.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';
@ -438,13 +438,3 @@ const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = {
timedout: 'TimedOut', timedout: 'TimedOut',
failed: 'Failed', failed: 'Failed',
} as const; } as const;
async function handleError(error: ProtocolError) {
if (['Invalid header'].includes(error.originalMessage)) {
throw error;
}
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
}