diff --git a/src/bidiMapper/modules/cdp/CdpTarget.ts b/src/bidiMapper/modules/cdp/CdpTarget.ts index 8824fc30cd..0e6839d585 100644 --- a/src/bidiMapper/modules/cdp/CdpTarget.ts +++ b/src/bidiMapper/modules/cdp/CdpTarget.ts @@ -33,6 +33,11 @@ import type {PreloadScriptStorage} from '../script/PreloadScriptStorage.js'; import type {RealmStorage} from '../script/RealmStorage.js'; import type {EventManager} from '../session/EventManager.js'; +interface FetchStages { + request: boolean; + response: boolean; + auth: boolean; +} export class CdpTarget { readonly #id: Protocol.Target.TargetID; readonly #cdpClient: CdpClient; @@ -53,7 +58,7 @@ export class CdpTarget { #deviceAccessEnabled = false; #cacheDisableState = false; #networkDomainEnabled = false; - #fetchDomainStages = { + #fetchDomainStages: FetchStages = { request: false, response: false, auth: false, @@ -290,7 +295,24 @@ export class CdpTarget { handleAuthRequests: stages.auth, }); } else { - await this.#cdpClient.sendCommand('Fetch.disable'); + const blockedRequest = this.#networkStorage + .getRequestsByTarget(this) + .filter((request) => request.interceptPhase); + void Promise.allSettled( + blockedRequest.map((request) => request.waitNextPhase), + ) + .then(async () => { + const blockedRequest = this.#networkStorage + .getRequestsByTarget(this) + .filter((request) => request.interceptPhase); + if (blockedRequest.length) { + return await this.toggleFetchIfNeeded(); + } + return await this.#cdpClient.sendCommand('Fetch.disable'); + }) + .catch((error) => { + this.#logger?.(LogType.bidi, 'Disable failed', error); + }); } } @@ -400,6 +422,98 @@ export class CdpTarget { }); } + async #toggleNetwork(enable: boolean): Promise { + this.#networkDomainEnabled = enable; + try { + await this.#cdpClient.sendCommand( + enable ? 'Network.enable' : 'Network.disable', + ); + } catch { + this.#networkDomainEnabled = !enable; + } + } + + async #enableFetch(stages: FetchStages) { + const patterns: Protocol.Fetch.EnableRequest['patterns'] = []; + + if (stages.request || stages.auth) { + // CDP quirk we need request interception when we intercept auth + patterns.push({ + urlPattern: '*', + requestStage: 'Request', + }); + } + if (stages.response) { + patterns.push({ + urlPattern: '*', + requestStage: 'Response', + }); + } + if ( + // Only enable interception when Network is enabled + this.#networkDomainEnabled && + patterns.length + ) { + const oldStages = this.#fetchDomainStages; + this.#fetchDomainStages = stages; + try { + await this.#cdpClient.sendCommand('Fetch.enable', { + patterns, + handleAuthRequests: stages.auth, + }); + } catch { + this.#fetchDomainStages = oldStages; + } + } + } + + async #disableFetch() { + const blockedRequest = this.#networkStorage + .getRequestsByTarget(this) + .filter((request) => request.interceptPhase); + + if (blockedRequest.length === 0) { + this.#fetchDomainStages = { + request: false, + response: false, + auth: false, + }; + await this.#cdpClient.sendCommand('Fetch.disable'); + } + } + + async toggleNetwork() { + const stages = this.#networkStorage.getInterceptionStages(this.topLevelId); + const fetchEnable = Object.values(stages).some((value) => value); + const fetchChanged = + this.#fetchDomainStages.request !== stages.request || + this.#fetchDomainStages.response !== stages.response || + this.#fetchDomainStages.auth !== stages.auth; + const networkEnable = this.isSubscribedTo(BiDiModule.Network); + const networkChanged = this.#networkDomainEnabled !== networkEnable; + + this.#logger?.( + LogType.debugInfo, + 'Toggle Network', + `Fetch (${fetchEnable}) ${fetchChanged}`, + `Network (${networkEnable}) ${networkChanged}`, + ); + + if (networkEnable && networkChanged) { + await this.#toggleNetwork(true); + } + if (fetchEnable && fetchChanged) { + await this.#enableFetch(stages); + } + if (!fetchEnable && fetchChanged) { + await this.#disableFetch(); + } + + if (!networkEnable && networkChanged && !fetchEnable && !fetchChanged) { + await this.#toggleNetwork(false); + } + } + /** * All the ProxyChannels from all the preload scripts of the given * BrowsingContext. diff --git a/src/bidiMapper/modules/network/NetworkProcessor.ts b/src/bidiMapper/modules/network/NetworkProcessor.ts index d63801dd35..b70b5c8645 100644 --- a/src/bidiMapper/modules/network/NetworkProcessor.ts +++ b/src/bidiMapper/modules/network/NetworkProcessor.ts @@ -57,7 +57,7 @@ export class NetworkProcessor { await Promise.all( this.#browsingContextStorage.getAllContexts().map((context) => { - return context.cdpTarget.toggleFetchIfNeeded(); + return context.cdpTarget.toggleNetwork(); }), ); @@ -181,7 +181,7 @@ export class NetworkProcessor { await Promise.all( this.#browsingContextStorage.getAllContexts().map((context) => { - return context.cdpTarget.toggleFetchIfNeeded(); + return context.cdpTarget.toggleNetwork(); }), ); diff --git a/src/bidiMapper/modules/network/NetworkRequest.ts b/src/bidiMapper/modules/network/NetworkRequest.ts index 6848d064bc..f3ff3ac043 100644 --- a/src/bidiMapper/modules/network/NetworkRequest.ts +++ b/src/bidiMapper/modules/network/NetworkRequest.ts @@ -732,6 +732,10 @@ export class NetworkRequest { this.#interceptPhase = undefined; } + dispose() { + this.waitNextPhase.reject(new Error('waitNextPhase disposed')); + } + async #continueWithAuth( authChallengeResponse: Protocol.Fetch.ContinueWithAuthRequest['authChallengeResponse'], ) { diff --git a/src/bidiMapper/modules/network/NetworkStorage.ts b/src/bidiMapper/modules/network/NetworkStorage.ts index 54df9cd394..63eecbb4ab 100644 --- a/src/bidiMapper/modules/network/NetworkStorage.ts +++ b/src/bidiMapper/modules/network/NetworkStorage.ts @@ -268,6 +268,7 @@ export class NetworkStorage { for (const request of this.#requests.values()) { if (request.cdpClient.sessionId === sessionId) { this.#requests.delete(request.id); + request.dispose(); } } } @@ -298,6 +299,16 @@ export class NetworkStorage { this.#intercepts.delete(intercept); } + getRequestsByTarget(target: CdpTarget): NetworkRequest[] { + const requests: NetworkRequest[] = []; + for (const request of this.#requests.values()) { + if (request.cdpTarget === target) { + requests.push(request); + } + } + return requests; + } + getRequestById(id: Network.Request): NetworkRequest | undefined { return this.#requests.get(id); } diff --git a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini deleted file mode 100644 index 8882ef40ab..0000000000 --- a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini +++ /dev/null @@ -1,5 +0,0 @@ -[remove_intercept.py] - [test_remove_intercept[responseStarted\]] - expected: [FAIL, PASS] - [test_remove_intercept[beforeRequestSent\]] - expected: [FAIL, PASS] diff --git a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini deleted file mode 100644 index 8882ef40ab..0000000000 --- a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini +++ /dev/null @@ -1,5 +0,0 @@ -[remove_intercept.py] - [test_remove_intercept[responseStarted\]] - expected: [FAIL, PASS] - [test_remove_intercept[beforeRequestSent\]] - expected: [FAIL, PASS] diff --git a/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini b/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini deleted file mode 100644 index 8882ef40ab..0000000000 --- a/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/remove_intercept/remove_intercept.py.ini +++ /dev/null @@ -1,5 +0,0 @@ -[remove_intercept.py] - [test_remove_intercept[responseStarted\]] - expected: [FAIL, PASS] - [test_remove_intercept[beforeRequestSent\]] - expected: [FAIL, PASS]