From 388af91e46b8f6e44eeda2d7c6a0c8feabdac946 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:55:07 +0100 Subject: [PATCH] chore(webdriver): support for Network interception (#12106) --- .../puppeteer-core/src/bidi/BrowserContext.ts | 5 ++ .../puppeteer-core/src/bidi/HTTPRequest.ts | 14 ++-- packages/puppeteer-core/src/bidi/Page.ts | 22 +++++- .../puppeteer-core/src/bidi/core/Browser.ts | 10 +++ .../src/bidi/core/BrowsingContext.ts | 33 +++++++- .../src/bidi/core/Connection.ts | 17 ++++ .../puppeteer-core/src/bidi/core/Request.ts | 26 +++++++ .../puppeteer-core/src/bidi/core/Session.ts | 14 ++++ test/TestExpectations.json | 78 +++++-------------- 9 files changed, 153 insertions(+), 66 deletions(-) diff --git a/packages/puppeteer-core/src/bidi/BrowserContext.ts b/packages/puppeteer-core/src/bidi/BrowserContext.ts index 9976e4cc6a5..1dc7468e279 100644 --- a/packages/puppeteer-core/src/bidi/BrowserContext.ts +++ b/packages/puppeteer-core/src/bidi/BrowserContext.ts @@ -185,6 +185,11 @@ export class BidiBrowserContext extends BrowserContext { } try { + for (const page of await this.pages()) { + // Workaround for Firefox + // TODO: Remove once https://bugzilla.mozilla.org/show_bug.cgi?id=1882260 is fixed + await page.setRequestInterception(false); + } await this.userContext.remove(); } catch (error) { debugError(error); diff --git a/packages/puppeteer-core/src/bidi/HTTPRequest.ts b/packages/puppeteer-core/src/bidi/HTTPRequest.ts index 972d4fd82eb..9a6f9c1e0e1 100644 --- a/packages/puppeteer-core/src/bidi/HTTPRequest.ts +++ b/packages/puppeteer-core/src/bidi/HTTPRequest.ts @@ -142,8 +142,10 @@ export class BidiHTTPRequest extends HTTPRequest { throw new UnsupportedOperation(); } - override continue(_overrides: ContinueRequestOverrides = {}): never { - throw new UnsupportedOperation(); + override async continue( + _overrides: ContinueRequestOverrides = {} + ): Promise { + return await this.#request.continueRequest(); } override responseForRequest(): never { @@ -166,14 +168,14 @@ export class BidiHTTPRequest extends HTTPRequest { throw new UnsupportedOperation(); } - override abort(): never { - throw new UnsupportedOperation(); + override async abort(): Promise { + return await this.#request.failRequest(); } - override respond( + override async respond( _response: Partial, _priority?: number - ): never { + ): Promise { throw new UnsupportedOperation(); } } diff --git a/packages/puppeteer-core/src/bidi/Page.ts b/packages/puppeteer-core/src/bidi/Page.ts index cd2921f8fea..c0b26202b2c 100644 --- a/packages/puppeteer-core/src/bidi/Page.ts +++ b/packages/puppeteer-core/src/bidi/Page.ts @@ -202,6 +202,11 @@ export class BidiPage extends Page { override async close(options?: {runBeforeUnload?: boolean}): Promise { try { + if (this.#interception) { + // Workaround for Firefox + // TODO: Remove once https://bugzilla.mozilla.org/show_bug.cgi?id=1882260 is fixed + await this.setRequestInterception(false); + } await this.#frame.browsingContext.close(options?.runBeforeUnload); } catch { return; @@ -499,8 +504,21 @@ export class BidiPage extends Page { return [...this.#workers]; } - override setRequestInterception(): never { - throw new UnsupportedOperation(); + #interception?: string; + override async setRequestInterception(enable: boolean): Promise { + if (enable && !this.#interception) { + this.#interception = await this.#frame.browsingContext.addIntercept({ + phases: [ + Bidi.Network.InterceptPhase.BeforeRequestSent, + Bidi.Network.InterceptPhase.AuthRequired, + ], + }); + } else if (!enable && this.#interception) { + await this.#frame.browsingContext.userContext.browser.removeIntercept( + this.#interception + ); + this.#interception = undefined; + } } override setDragInterception(): never { diff --git a/packages/puppeteer-core/src/bidi/core/Browser.ts b/packages/puppeteer-core/src/bidi/core/Browser.ts index efeabc3a590..5300f5819fe 100644 --- a/packages/puppeteer-core/src/bidi/core/Browser.ts +++ b/packages/puppeteer-core/src/bidi/core/Browser.ts @@ -199,6 +199,16 @@ export class Browser extends EventEmitter<{ return script; } + @throwIfDisposed(browser => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return browser.#reason!; + }) + async removeIntercept(intercept: Bidi.Network.Intercept): Promise { + await this.session.send('network.removeIntercept', { + intercept, + }); + } + @throwIfDisposed(browser => { // SAFETY: By definition of `disposed`, `#reason` is defined. return browser.#reason!; diff --git a/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts b/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts index 56ca71c1ebe..f4491c76ab6 100644 --- a/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts +++ b/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts @@ -18,6 +18,14 @@ import {Request} from './Request.js'; import type {UserContext} from './UserContext.js'; import {UserPrompt} from './UserPrompt.js'; +/** + * @internal + */ +export type AddInterceptOptions = Omit< + Bidi.Network.AddInterceptParameters, + 'contexts' +>; + /** * @internal */ @@ -478,11 +486,26 @@ export class BrowsingContext extends EventEmitter<{ functionDeclaration, { ...options, - contexts: [this, ...(options.contexts ?? [])], + contexts: [this], } ); } + @throwIfDisposed(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async addIntercept(options: AddInterceptOptions): Promise { + const { + result: {intercept}, + } = await this.userContext.browser.session.send('network.addIntercept', { + ...options, + contexts: [this.id], + }); + + return intercept; + } + @throwIfDisposed(context => { // SAFETY: Disposal implies this exists. return context.#reason!; @@ -547,6 +570,14 @@ export class BrowsingContext extends EventEmitter<{ await this.#session.subscribe(events, [this.id]); } + @throwIfDisposed(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async addInterception(events: [string, ...string[]]): Promise { + await this.#session.subscribe(events, [this.id]); + } + [disposeSymbol](): void { this.#reason ??= 'Browsing context already closed, probably because the user context closed.'; diff --git a/packages/puppeteer-core/src/bidi/core/Connection.ts b/packages/puppeteer-core/src/bidi/core/Connection.ts index 9c26a035038..cbfb788005a 100644 --- a/packages/puppeteer-core/src/bidi/core/Connection.ts +++ b/packages/puppeteer-core/src/bidi/core/Connection.ts @@ -149,6 +149,23 @@ export interface Commands { params: Bidi.Storage.SetCookieParameters; returnType: Bidi.Storage.SetCookieParameters; }; + + 'network.addIntercept': { + params: Bidi.Network.AddInterceptParameters; + returnType: Bidi.Network.AddInterceptResult; + }; + 'network.removeIntercept': { + params: Bidi.Network.RemoveInterceptParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueRequest': { + params: Bidi.Network.ContinueRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.failRequest': { + params: Bidi.Network.FailRequestParameters; + returnType: Bidi.EmptyResult; + }; } /** diff --git a/packages/puppeteer-core/src/bidi/core/Request.ts b/packages/puppeteer-core/src/bidi/core/Request.ts index fd616b668d9..a880a509eaf 100644 --- a/packages/puppeteer-core/src/bidi/core/Request.ts +++ b/packages/puppeteer-core/src/bidi/core/Request.ts @@ -143,6 +143,32 @@ export class Request extends EventEmitter<{ } // keep-sorted end + async continueRequest(): Promise { + 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.continueRequest', { + request: this.id, + }); + } + + async failRequest(): Promise { + 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', { + request: this.id, + }); + } + @inertIfDisposed private dispose(): void { this[disposeSymbol](); diff --git a/packages/puppeteer-core/src/bidi/core/Session.ts b/packages/puppeteer-core/src/bidi/core/Session.ts index 9636a52d070..9ebcbea5071 100644 --- a/packages/puppeteer-core/src/bidi/core/Session.ts +++ b/packages/puppeteer-core/src/bidi/core/Session.ts @@ -174,6 +174,20 @@ export class Session }); } + @throwIfDisposed(session => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return session.#reason!; + }) + async addIntercepts( + events: [string, ...string[]], + contexts?: [string, ...string[]] + ): Promise { + await this.send('session.subscribe', { + events, + contexts, + }); + } + @throwIfDisposed(session => { // SAFETY: By definition of `disposed`, `#reason` is defined. return session.#reason!; diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 7ad5e925ccf..f130bbb651a 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -589,6 +589,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[network.spec] network Request.isNavigationRequest should work with request interception", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[network.spec] network Request.postData should be |undefined| when there is no post data", "platforms": ["darwin", "linux", "win32"], @@ -631,6 +637,13 @@ "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, + { + "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL", "TIMEOUT"], + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + }, { "testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page", "platforms": ["darwin", "linux", "win32"], @@ -722,13 +735,6 @@ "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[page.spec] Page Page.setOfflineMode should emulate navigator.onLine", "platforms": ["darwin", "linux", "win32"], @@ -1098,13 +1104,6 @@ "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[chromiumonly.spec] Chromium-Specific Page Tests Page.setRequestInterception should work with intervention headers", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle", "platforms": ["darwin", "linux", "win32"], @@ -1770,13 +1769,6 @@ "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", "platforms": ["darwin", "linux", "win32"], @@ -1784,13 +1776,6 @@ "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, - { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[input.spec] input tests FileChooser.accept should accept single file", "platforms": ["darwin", "linux", "win32"], @@ -2910,39 +2895,11 @@ "expectations": ["SKIP"], "comment": "Failed previously and currently times out" }, - { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, { "testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["FAIL", "TIMEOUT"], "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=187816" }, { @@ -3246,6 +3203,13 @@ "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, + { + "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + }, { "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", "platforms": ["darwin", "linux", "win32"],