diff --git a/src/bidiMapper/modules/cdp/CdpTarget.ts b/src/bidiMapper/modules/cdp/CdpTarget.ts index 5551ae5085..b526f0d1b8 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; @@ -50,7 +55,7 @@ export class CdpTarget { #cacheDisableState = false; #networkDomainEnabled = false; - #fetchDomainStages = { + #fetchDomainStages: FetchStages = { request: false, response: false, auth: false, @@ -264,7 +269,18 @@ 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 () => { + return await this.#cdpClient.sendCommand('Fetch.disable'); + }) + .catch((error) => { + this.#logger?.(LogType.bidi, 'Disable failed', error); + }); } } @@ -338,6 +354,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 6953152cc5..b15375d87f 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 8972f4afa2..e09fc34f88 100644 --- a/src/bidiMapper/modules/network/NetworkRequest.ts +++ b/src/bidiMapper/modules/network/NetworkRequest.ts @@ -732,6 +732,19 @@ export class NetworkRequest { this.#interceptPhase = undefined; } + dispose() { + this.waitNextPhase.reject(new Error()); + } + + /** Returns the HTTP status code associated with this request if any. */ + get statusCode(): number | undefined { + return ( + this.#response.paused?.responseStatusCode ?? + this.#response.extraInfo?.statusCode ?? + this.#response.info?.status + ); + } + async #continueWithAuth( authChallengeResponse: Protocol.Fetch.ContinueWithAuthRequest['authChallengeResponse'] ) { diff --git a/src/bidiMapper/modules/network/NetworkStorage.ts b/src/bidiMapper/modules/network/NetworkStorage.ts index 7137dc616f..eb5d8db2b4 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]