diff --git a/src/bidiMapper/domains/context/CdpTarget.ts b/src/bidiMapper/domains/context/CdpTarget.ts index 2f69122354..e8e59003a5 100644 --- a/src/bidiMapper/domains/context/CdpTarget.ts +++ b/src/bidiMapper/domains/context/CdpTarget.ts @@ -36,11 +36,16 @@ export class CdpTarget { readonly #eventManager: EventManager; readonly #preloadScriptStorage: PreloadScriptStorage; + readonly #networkStorage: NetworkStorage; readonly #unblocked = new Deferred>(); readonly #acceptInsecureCerts: boolean; #networkDomainEnabled = false; - #fetchDomainEnabled = false; + #fetchDomainStages = { + request: false, + response: false, + auth: false, + }; static create( targetId: Protocol.Target.TargetID, @@ -59,6 +64,7 @@ export class CdpTarget { browserCdpClient, eventManager, preloadScriptStorage, + networkStorage, acceptInsecureCerts ); @@ -79,13 +85,15 @@ export class CdpTarget { browserCdpClient: CdpClient, eventManager: EventManager, preloadScriptStorage: PreloadScriptStorage, + networkStorage: NetworkStorage, acceptInsecureCerts: boolean ) { this.#id = targetId; this.#cdpClient = cdpClient; + this.#browserCdpClient = browserCdpClient; this.#eventManager = eventManager; this.#preloadScriptStorage = preloadScriptStorage; - this.#browserCdpClient = browserCdpClient; + this.#networkStorage = networkStorage; this.#acceptInsecureCerts = acceptInsecureCerts; } @@ -122,6 +130,7 @@ export class CdpTarget { BiDiModule.Network, this.#id ); + this.#networkDomainEnabled = enabledNetwork; try { await Promise.all([ @@ -138,6 +147,7 @@ export class CdpTarget { enabledNetwork ? this.#cdpClient.sendCommand('Network.enable') : undefined, + this.toggleFetchIfNeeded(), this.#cdpClient.sendCommand('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, @@ -163,29 +173,41 @@ export class CdpTarget { }); } - async enableFetchIfNeeded(params: Protocol.Fetch.EnableRequest) { - if (!this.#networkDomainEnabled || this.#fetchDomainEnabled) { + async toggleFetchIfNeeded() { + const stages = this.#networkStorage.getInterceptionStages(this.id); + + if ( + // Only toggle interception when Network is enabled + !this.#networkDomainEnabled || + (this.#fetchDomainStages.request === stages.request && + this.#fetchDomainStages.response === stages.response && + this.#fetchDomainStages.auth === stages.auth) + ) { return; } + const patterns: Protocol.Fetch.EnableRequest['patterns'] = []; - this.#fetchDomainEnabled = true; - try { - await this.#cdpClient.sendCommand('Fetch.enable', params); - } catch (err) { - this.#fetchDomainEnabled = false; + this.#fetchDomainStages = stages; + if (stages.request || stages.auth) { + // CDP quirk we need request interception when we intercept auth + patterns.push({ + urlPattern: '*', + requestStage: 'Request', + }); } - } - - async disableFetchIfNeeded() { - if (!this.#fetchDomainEnabled) { - return; + if (stages.response) { + patterns.push({ + urlPattern: '*', + requestStage: 'Response', + }); } - - this.#fetchDomainEnabled = false; - try { + if (patterns.length) { + await this.#cdpClient.sendCommand('Fetch.enable', { + patterns, + handleAuthRequests: stages.auth, + }); + } else { await this.#cdpClient.sendCommand('Fetch.disable'); - } catch (err) { - this.#fetchDomainEnabled = true; } } @@ -196,9 +218,12 @@ export class CdpTarget { this.#networkDomainEnabled = enabled; try { - await this.#cdpClient.sendCommand( - this.#networkDomainEnabled ? 'Network.enable' : 'Network.disable' - ); + await Promise.all([ + this.#cdpClient.sendCommand( + enabled ? 'Network.enable' : 'Network.disable' + ), + this.toggleFetchIfNeeded(), + ]); } catch (err) { this.#networkDomainEnabled = !enabled; } diff --git a/src/bidiMapper/domains/network/NetworkProcessor.ts b/src/bidiMapper/domains/network/NetworkProcessor.ts index 37d2e888a8..2ffb8fa4a6 100644 --- a/src/bidiMapper/domains/network/NetworkProcessor.ts +++ b/src/bidiMapper/domains/network/NetworkProcessor.ts @@ -49,18 +49,24 @@ export class NetworkProcessor { async addIntercept( params: Network.AddInterceptParameters ): Promise { - // TODO: Use in intercepts this.#browsingContextStorage.verifyContextsList(params.contexts); const urlPatterns: Network.UrlPattern[] = params.urlPatterns ?? []; const parsedUrlPatterns: Network.UrlPattern[] = NetworkProcessor.parseUrlPatterns(urlPatterns); - const intercept: Network.Intercept = - await this.#networkStorage.addIntercept({ - urlPatterns: parsedUrlPatterns, - phases: params.phases, - }); + const intercept: Network.Intercept = this.#networkStorage.addIntercept({ + urlPatterns: parsedUrlPatterns, + phases: params.phases, + contexts: params.contexts, + }); + + await Promise.all( + // TODO: We should to this for other CDP targets as well + this.#browsingContextStorage.getTopLevelContexts().map((context) => { + return context.cdpTarget.toggleFetchIfNeeded(); + }) + ); return { intercept, @@ -213,7 +219,14 @@ export class NetworkProcessor { async removeIntercept( params: Network.RemoveInterceptParameters ): Promise { - await this.#networkStorage.removeIntercept(params.intercept); + this.#networkStorage.removeIntercept(params.intercept); + + await Promise.all( + // TODO: We should to this for other CDP targets as well + this.#browsingContextStorage.getTopLevelContexts().map((context) => { + return context.cdpTarget.toggleFetchIfNeeded(); + }) + ); return {}; } diff --git a/src/bidiMapper/domains/network/NetworkRequest.ts b/src/bidiMapper/domains/network/NetworkRequest.ts index e83da7a67e..0c6c0058a7 100644 --- a/src/bidiMapper/domains/network/NetworkRequest.ts +++ b/src/bidiMapper/domains/network/NetworkRequest.ts @@ -150,6 +150,10 @@ export class NetworkRequest { return this.#redirectCount; } + get cdpTarget() { + return this.#cdpTarget; + } + get cdpClient() { return this.#cdpTarget.cdpClient; } diff --git a/src/bidiMapper/domains/network/NetworkStorage.spec.ts b/src/bidiMapper/domains/network/NetworkStorage.spec.ts index 903e33d951..40f5376dd6 100644 --- a/src/bidiMapper/domains/network/NetworkStorage.spec.ts +++ b/src/bidiMapper/domains/network/NetworkStorage.spec.ts @@ -125,7 +125,7 @@ describe('NetworkStorage', () => { it('should work interception', async () => { const request = new MockCdpNetworkEvents(cdpClient); - const interception = await networkStorage.addIntercept({ + const interception = networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: request.url}], phases: [Network.InterceptPhase.BeforeRequestSent], }); @@ -144,7 +144,7 @@ describe('NetworkStorage', () => { it('should work interception pause first', async () => { const request = new MockCdpNetworkEvents(cdpClient); - const interception = await networkStorage.addIntercept({ + const interception = networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: request.url}], phases: [Network.InterceptPhase.BeforeRequestSent], }); @@ -162,7 +162,7 @@ describe('NetworkStorage', () => { }); it('should work non blocking interception', async () => { - await networkStorage.addIntercept({ + networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: 'http://not.correct.com'}], phases: [Network.InterceptPhase.BeforeRequestSent], }); @@ -182,7 +182,7 @@ describe('NetworkStorage', () => { it('should work with non blocking interception and fail response', async () => { const request = new MockCdpNetworkEvents(cdpClient); - await networkStorage.addIntercept({ + networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: 'http://not.correct.com'}], phases: [Network.InterceptPhase.BeforeRequestSent], }); @@ -238,7 +238,7 @@ describe('NetworkStorage', () => { it('should work interception', async () => { const request = new MockCdpNetworkEvents(cdpClient); - const interception = await networkStorage.addIntercept({ + const interception = networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: request.url}], phases: [Network.InterceptPhase.ResponseStarted], }); @@ -259,7 +259,7 @@ describe('NetworkStorage', () => { }); it('should work non blocking interception', async () => { - await networkStorage.addIntercept({ + networkStorage.addIntercept({ urlPatterns: [{type: 'string', pattern: 'http://not.correct.com'}], phases: [Network.InterceptPhase.ResponseStarted], }); diff --git a/src/bidiMapper/domains/network/NetworkStorage.ts b/src/bidiMapper/domains/network/NetworkStorage.ts index bdbe06aa03..87e21c0599 100644 --- a/src/bidiMapper/domains/network/NetworkStorage.ts +++ b/src/bidiMapper/domains/network/NetworkStorage.ts @@ -16,7 +16,11 @@ */ import type {Protocol} from 'devtools-protocol'; -import {Network, NoSuchInterceptException} from '../../../protocol/protocol.js'; +import { + type BrowsingContext, + Network, + NoSuchInterceptException, +} from '../../../protocol/protocol.js'; import type {LoggerFn} from '../../../utils/log.js'; import {uuidv4} from '../../../utils/uuid.js'; import type {CdpClient} from '../../BidiMapper.js'; @@ -26,17 +30,18 @@ import type {EventManager} from '../session/EventManager.js'; import {NetworkRequest} from './NetworkRequest.js'; import {matchUrlPattern} from './NetworkUtils.js'; -interface NetworkInterception { +type NetworkInterception = Omit< + Network.AddInterceptParameters, + 'urlPatterns' +> & { urlPatterns: Network.UrlPattern[]; - phases: Network.AddInterceptParameters['phases']; -} +}; /** Stores network and intercept maps. */ export class NetworkStorage { #eventManager: EventManager; #logger?: LoggerFn; - readonly #targets = new Set(); /** * A map from network request ID to Network Request objects. * Needed as long as information about requests comes from different events. @@ -46,12 +51,6 @@ export class NetworkStorage { /** A map from intercept ID to track active network intercepts. */ readonly #intercepts = new Map(); - #interceptionStages = { - request: false, - response: false, - auth: false, - }; - constructor( eventManager: EventManager, browserClient: CdpClient, @@ -98,8 +97,6 @@ export class NetworkStorage { } onCdpTargetCreated(cdpTarget: CdpTarget) { - this.#targets.add(cdpTarget); - const cdpClient = cdpTarget.cdpClient; // TODO: Wrap into object @@ -200,74 +197,32 @@ export class NetworkStorage { } } - async toggleInterception() { - if (this.#intercepts.size) { - const stages = { - request: false, - response: false, - auth: false, - }; - for (const intercept of this.#intercepts.values()) { - stages.request ||= intercept.phases.includes( - Network.InterceptPhase.BeforeRequestSent - ); - stages.response ||= intercept.phases.includes( - Network.InterceptPhase.ResponseStarted - ); - stages.auth ||= intercept.phases.includes( - Network.InterceptPhase.AuthRequired - ); - } - const patterns: Protocol.Fetch.EnableRequest['patterns'] = []; - + getInterceptionStages(browsingContextId: BrowsingContext.BrowsingContext) { + const stages = { + request: false, + response: false, + auth: false, + }; + for (const intercept of this.#intercepts.values()) { if ( - this.#interceptionStages.request === stages.request && - this.#interceptionStages.response === stages.response && - this.#interceptionStages.auth === stages.auth + intercept.contexts && + !intercept.contexts.includes(browsingContextId) ) { - return; - } - - this.#interceptionStages = stages; - // CDP quirk we need request interception when we intercept auth - if (stages.request || stages.auth) { - patterns.push({ - urlPattern: '*', - requestStage: 'Request', - }); - } - if (stages.response) { - patterns.push({ - urlPattern: '*', - requestStage: 'Response', - }); + continue; } - // TODO: Don't enable on start as we will have - // no network interceptions at this time. - // Needed to enable fetch events. - - await Promise.all( - [...this.#targets.values()].map(async (cdpTarget) => { - return await cdpTarget.enableFetchIfNeeded({ - patterns, - handleAuthRequests: stages.auth, - }); - }) + stages.request ||= intercept.phases.includes( + Network.InterceptPhase.BeforeRequestSent + ); + stages.response ||= intercept.phases.includes( + Network.InterceptPhase.ResponseStarted ); - } else { - this.#interceptionStages = { - request: false, - response: false, - auth: false, - }; - - await Promise.all( - [...this.#targets.values()].map((target) => { - return target.disableFetchIfNeeded(); - }) + stages.auth ||= intercept.phases.includes( + Network.InterceptPhase.AuthRequired ); } + + return stages; } getInterceptsForPhase( @@ -280,9 +235,14 @@ export class NetworkStorage { const intercepts = new Set(); for (const [interceptId, intercept] of this.#intercepts.entries()) { - if (!intercept.phases.includes(phase)) { + if ( + !intercept.phases.includes(phase) || + (intercept.contexts && + !intercept.contexts.includes(request.cdpTarget.id)) + ) { continue; } + if (intercept.urlPatterns.length === 0) { intercepts.add(interceptId); continue; @@ -313,12 +273,10 @@ export class NetworkStorage { * * @return The intercept ID. */ - async addIntercept(value: NetworkInterception): Promise { + addIntercept(value: NetworkInterception): Network.Intercept { const interceptId: Network.Intercept = uuidv4(); this.#intercepts.set(interceptId, value); - await this.toggleInterception(); - return interceptId; } @@ -326,15 +284,13 @@ export class NetworkStorage { * Removes the given intercept from the intercept map. * Throws NoSuchInterceptException if the intercept does not exist. */ - async removeIntercept(intercept: Network.Intercept) { + removeIntercept(intercept: Network.Intercept) { if (!this.#intercepts.has(intercept)) { throw new NoSuchInterceptException( `Intercept '${intercept}' does not exist.` ); } this.#intercepts.delete(intercept); - - await this.toggleInterception(); } getRequestById(id: Network.Request): NetworkRequest | undefined { diff --git a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/add_intercept/contexts.py.ini b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/add_intercept/contexts.py.ini deleted file mode 100644 index bbf7cdc65e..0000000000 --- a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/network/add_intercept/contexts.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[contexts.py] - [test_other_context_with_event_subscription] - expected: FAIL - - [test_two_contexts_global_intercept] - expected: FAIL diff --git a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini deleted file mode 100644 index bbf7cdc65e..0000000000 --- a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[contexts.py] - [test_other_context_with_event_subscription] - expected: FAIL - - [test_two_contexts_global_intercept] - expected: FAIL diff --git a/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini b/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini deleted file mode 100644 index bbf7cdc65e..0000000000 --- a/wpt-metadata/mapper/headless/webdriver/tests/bidi/network/add_intercept/contexts.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[contexts.py] - [test_other_context_with_event_subscription] - expected: FAIL - - [test_two_contexts_global_intercept] - expected: FAIL