summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts226
1 files changed, 212 insertions, 14 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts
index d72f088686..674abc61f2 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts
@@ -5,6 +5,10 @@
*/
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';
import type {HTTPResponse} from './HTTPResponse.js';
@@ -118,6 +122,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.
*
* @experimental
@@ -139,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
@@ -163,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.
@@ -177,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
@@ -323,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.
@@ -360,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.
@@ -379,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;
+ }
+ }
}
/**
@@ -513,3 +681,33 @@ export const STATUS_TEXTS: Record<string, string> = {
'510': 'Not Extended',
'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
+ */
+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);
+}