From d4fe03884d9c53f205dcbc873a92f9967628fe6c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 8 May 2023 16:24:12 -0700 Subject: [PATCH 1/6] core(global-listeners): iterate all execution contexts --- core/gather/driver/target-manager.js | 50 ++++++++++++++++ core/gather/gatherers/global-listeners.js | 57 ++++++++++++------- core/legacy/gather/driver.js | 10 ++++ core/test/gather/driver-test.js | 3 + .../gather/driver/network-monitor-test.js | 1 + .../test/gather/driver/target-manager-test.js | 7 ++- types/gatherer.d.ts | 1 + 7 files changed, 107 insertions(+), 22 deletions(-) diff --git a/core/gather/driver/target-manager.js b/core/gather/driver/target-manager.js index a4bab53e1ebd..189a77fa5a59 100644 --- a/core/gather/driver/target-manager.js +++ b/core/gather/driver/target-manager.js @@ -47,9 +47,14 @@ class TargetManager extends ProtocolEventEmitter { * @type {Map} */ this._targetIdToTargets = new Map(); + /** @type {Array} */ + this._executionContextDescriptions = []; this._onSessionAttached = this._onSessionAttached.bind(this); this._onFrameNavigated = this._onFrameNavigated.bind(this); + this._onExecutionContextCreated = this._onExecutionContextCreated.bind(this); + this._onExecutionContextDestroyed = this._onExecutionContextDestroyed.bind(this); + this._onExecutionContextsCleared = this._onExecutionContextsCleared.bind(this); } /** @@ -97,6 +102,10 @@ class TargetManager extends ProtocolEventEmitter { return this._findSession(rootSessionId); } + executionContexts() { + return [...this._executionContextDescriptions]; + } + /** * @param {LH.Puppeteer.CDPSession} cdpSession */ @@ -153,6 +162,35 @@ class TargetManager extends ProtocolEventEmitter { } } + /** + * @param {LH.Crdp.Runtime.ExecutionContextCreatedEvent} event + */ + _onExecutionContextCreated(event) { + if (event.context.name === '__puppeteer_utility_world__') return; + if (event.context.name === 'lighthouse_isolated_context') return; + + const index = this._executionContextDescriptions.findIndex(d => + d.uniqueId === event.context.uniqueId); + if (index === -1) { + this._executionContextDescriptions.push(event.context); + } + } + + /** + * @param {LH.Crdp.Runtime.ExecutionContextDestroyedEvent} event + */ + _onExecutionContextDestroyed(event) { + const index = this._executionContextDescriptions.findIndex(d => + d.uniqueId === event.executionContextUniqueId); + if (index !== -1) { + this._executionContextDescriptions.splice(index, 1); + } + } + + _onExecutionContextsCleared() { + this._executionContextDescriptions = []; + } + /** * Returns a listener for all protocol events from session, and augments the * event with the sessionId. @@ -185,8 +223,12 @@ class TargetManager extends ProtocolEventEmitter { this._targetIdToTargets = new Map(); this._rootCdpSession.on('Page.frameNavigated', this._onFrameNavigated); + this._rootCdpSession.on('Runtime.executionContextCreated', this._onExecutionContextCreated); + this._rootCdpSession.on('Runtime.executionContextDestroyed', this._onExecutionContextDestroyed); + this._rootCdpSession.on('Runtime.executionContextsCleared', this._onExecutionContextsCleared); await this._rootCdpSession.send('Page.enable'); + await this._rootCdpSession.send('Runtime.enable'); // Start with the already attached root session. await this._onSessionAttached(this._rootCdpSession); @@ -197,14 +239,22 @@ class TargetManager extends ProtocolEventEmitter { */ async disable() { this._rootCdpSession.off('Page.frameNavigated', this._onFrameNavigated); + this._rootCdpSession.off('Runtime.executionContextCreated', this._onExecutionContextCreated); + this._rootCdpSession.off('Runtime.executionContextDestroyed', + this._onExecutionContextDestroyed); + this._rootCdpSession.off('Runtime.executionContextsCleared', this._onExecutionContextsCleared); for (const {cdpSession, protocolListener} of this._targetIdToTargets.values()) { cdpSession.off('*', protocolListener); cdpSession.off('sessionattached', this._onSessionAttached); } + await this._rootCdpSession.send('Page.disable'); + await this._rootCdpSession.send('Runtime.disable'); + this._enabled = false; this._targetIdToTargets = new Map(); + this._executionContextDescriptions = []; } } diff --git a/core/gather/gatherers/global-listeners.js b/core/gather/gatherers/global-listeners.js index 38f5eb4ca45a..27f0ab24a177 100644 --- a/core/gather/gatherers/global-listeners.js +++ b/core/gather/gatherers/global-listeners.js @@ -61,30 +61,45 @@ class GlobalListeners extends FRGatherer { async getArtifact(passContext) { const session = passContext.driver.defaultSession; - // Get a RemoteObject handle to `window`. - const {result: {objectId}} = await session.sendCommand('Runtime.evaluate', { - expression: 'window', - returnByValue: false, - }); - if (!objectId) { - throw new Error('Error fetching information about the global object'); - } + /** @type {Array} */ + const listeners = []; - // And get all its listeners of interest. - const {listeners} = await session.sendCommand('DOMDebugger.getEventListeners', {objectId}); - const filteredListeners = listeners.filter(GlobalListeners._filterForAllowlistedTypes) - .map(listener => { - const {type, scriptId, lineNumber, columnNumber} = listener; - return { - type, - scriptId, - lineNumber, - columnNumber, - }; - }); + for (const executionContext of passContext.driver.targetManager.executionContexts()) { + // Get a RemoteObject handle to `window`. + let objectId; + try { + const {result} = await session.sendCommand('Runtime.evaluate', { + expression: 'window', + returnByValue: false, + uniqueContextId: executionContext.uniqueId, + }); + if (!result.objectId) { + throw new Error('Error fetching information about the global object'); + } + objectId = result.objectId; + } catch (err) { + // Execution context is no longer valid, but don't let that fail the gatherer. + console.error(err); + continue; + } + + // And get all its listeners of interest. + const response = await session.sendCommand('DOMDebugger.getEventListeners', {objectId}); + for (const listener of response.listeners) { + if (GlobalListeners._filterForAllowlistedTypes(listener)) { + const {type, scriptId, lineNumber, columnNumber} = listener; + listeners.push({ + type, + scriptId, + lineNumber, + columnNumber, + }); + } + } + } // Dedupe listeners with same underlying data. - return this.dedupeListeners(filteredListeners); + return this.dedupeListeners(listeners); } } diff --git a/core/legacy/gather/driver.js b/core/legacy/gather/driver.js index 397380f797dd..f7ecc6dfef4c 100644 --- a/core/legacy/gather/driver.js +++ b/core/legacy/gather/driver.js @@ -101,6 +101,16 @@ class Driver { rootSession: () => { return this.defaultSession; }, + // For legacy driver, only bother supporting access to the default execution context. + executionContexts: () => { + // @ts-expect-error - undefined ids are OK for purposes of calling protocol commands like Runtime.evaluate. + return [/** @type {LH.Crdp.Runtime.ExecutionContextDescription} */({ + id: undefined, + uniqueId: undefined, + origin: '', + name: '', + })]; + }, /** * Bind to *any* protocol event. * @param {'protocolevent'} event diff --git a/core/test/gather/driver-test.js b/core/test/gather/driver-test.js index c12fafc404f3..62622d0e0bbd 100644 --- a/core/test/gather/driver-test.js +++ b/core/test/gather/driver-test.js @@ -27,6 +27,9 @@ beforeEach(() => { const puppeteerSession = createMockCdpSession(); puppeteerSession.send .mockResponse('Page.enable') + .mockResponse('Runtime.enable') + .mockResponse('Page.disable') + .mockResponse('Runtime.disable') .mockResponse('Target.getTargetInfo', {targetInfo: {type: 'page', targetId: 'page'}}) .mockResponse('Network.enable') .mockResponse('Target.setAutoAttach') diff --git a/core/test/gather/driver/network-monitor-test.js b/core/test/gather/driver/network-monitor-test.js index 7f5ee560dbc9..079bfa07b199 100644 --- a/core/test/gather/driver/network-monitor-test.js +++ b/core/test/gather/driver/network-monitor-test.js @@ -38,6 +38,7 @@ describe('NetworkMonitor', () => { const cdpSessionMock = createMockCdpSession(id); cdpSessionMock.send .mockResponse('Page.enable') + .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo: {type: targetType, targetId: id}}) .mockResponse('Network.enable') .mockResponse('Target.setAutoAttach') diff --git a/core/test/gather/driver/target-manager-test.js b/core/test/gather/driver/target-manager-test.js index 5a32714b2e0b..b5ca4a446b88 100644 --- a/core/test/gather/driver/target-manager-test.js +++ b/core/test/gather/driver/target-manager-test.js @@ -40,6 +40,9 @@ describe('TargetManager', () => { sendMock = sessionMock.send; sendMock .mockResponse('Page.enable') + .mockResponse('Runtime.enable') + .mockResponse('Page.disable') + .mockResponse('Runtime.disable') .mockResponse('Runtime.runIfWaitingForDebugger'); targetManager = new TargetManager(sessionMock.asCdpSession()); targetInfo = createTargetInfo(); @@ -78,7 +81,7 @@ describe('TargetManager', () => { await targetManager.enable(); expect(sessionMock.on).toHaveBeenCalled(); - const sessionListener = sessionMock.on.mock.calls[3][1]; + const sessionListener = sessionMock.on.mock.calls.find(c => c[0] === 'sessionattached')[1]; // Original, attach. expect(sendMock.findAllInvocations('Target.getTargetInfo')).toHaveLength(1); @@ -258,6 +261,7 @@ describe('TargetManager', () => { // Still mock command responses at session level. rootSession.send = createMockSendCommandFn({useSessionId: false}) .mockResponse('Page.enable') + .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo: rootTargetInfo}) .mockResponse('Network.enable') .mockResponse('Target.setAutoAttach') @@ -327,6 +331,7 @@ describe('TargetManager', () => { // Still mock command responses at session level. rootSession.send = createMockSendCommandFn({useSessionId: false}) .mockResponse('Page.enable') + .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo}) .mockResponse('Network.enable') .mockResponse('Target.setAutoAttach') diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts index 56fe31cc742d..1e1f4e838226 100644 --- a/types/gatherer.d.ts +++ b/types/gatherer.d.ts @@ -47,6 +47,7 @@ declare module Gatherer { url: () => Promise; targetManager: { rootSession(): FRProtocolSession; + executionContexts(): Array; on(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void }; From 84afe302c1a661a37fadb644cc2d542176ed510f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 8 May 2023 16:42:11 -0700 Subject: [PATCH 2/6] types --- types/gatherer.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts index 1e1f4e838226..399723b944fd 100644 --- a/types/gatherer.d.ts +++ b/types/gatherer.d.ts @@ -47,7 +47,7 @@ declare module Gatherer { url: () => Promise; targetManager: { rootSession(): FRProtocolSession; - executionContexts(): Array; + executionContexts(): Array; on(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void }; From 9b4c35d5c6aaa6ac0d18898f40dfbea8e7db28ce Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 9 May 2023 09:36:00 -0700 Subject: [PATCH 3/6] filter based on main frame --- core/gather/driver/target-manager.js | 12 ++++++++++++ core/gather/gatherers/global-listeners.js | 2 +- core/legacy/gather/driver.js | 4 ++++ types/gatherer.d.ts | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core/gather/driver/target-manager.js b/core/gather/driver/target-manager.js index 189a77fa5a59..ef80d29d48bc 100644 --- a/core/gather/driver/target-manager.js +++ b/core/gather/driver/target-manager.js @@ -40,6 +40,7 @@ class TargetManager extends ProtocolEventEmitter { this._enabled = false; this._rootCdpSession = cdpSession; + this._mainFrameId = ''; /** * A map of target id to target/session information. Used to ensure unique @@ -106,10 +107,17 @@ class TargetManager extends ProtocolEventEmitter { return [...this._executionContextDescriptions]; } + mainFrameExecutionContexts() { + return this._executionContextDescriptions.filter(executionContext => { + return executionContext.auxData.frameId === this._mainFrameId; + }); + } + /** * @param {LH.Puppeteer.CDPSession} cdpSession */ async _onSessionAttached(cdpSession) { + const isRootSession = this._rootCdpSession === cdpSession; const newSession = new ProtocolSession(cdpSession); try { @@ -124,6 +132,7 @@ class TargetManager extends ProtocolEventEmitter { const targetId = target.targetInfo.targetId; if (this._targetIdToTargets.has(targetId)) return; + if (isRootSession) this._mainFrameId = targetId; newSession.setTargetInfo(target.targetInfo); const targetName = target.targetInfo.url || target.targetInfo.targetId; log.verbose('target-manager', `target ${targetName} attached`); @@ -166,6 +175,9 @@ class TargetManager extends ProtocolEventEmitter { * @param {LH.Crdp.Runtime.ExecutionContextCreatedEvent} event */ _onExecutionContextCreated(event) { + // This execution context was made via the protocol (e.g. by us or Puppeteer). + if (event.context.origin === '://' || event.context.origin === '') return; + // Just in case the above changes somehow. if (event.context.name === '__puppeteer_utility_world__') return; if (event.context.name === 'lighthouse_isolated_context') return; diff --git a/core/gather/gatherers/global-listeners.js b/core/gather/gatherers/global-listeners.js index 27f0ab24a177..16c4dca7e7cf 100644 --- a/core/gather/gatherers/global-listeners.js +++ b/core/gather/gatherers/global-listeners.js @@ -64,7 +64,7 @@ class GlobalListeners extends FRGatherer { /** @type {Array} */ const listeners = []; - for (const executionContext of passContext.driver.targetManager.executionContexts()) { + for (const executionContext of passContext.driver.targetManager.mainFrameExecutionContexts()) { // Get a RemoteObject handle to `window`. let objectId; try { diff --git a/core/legacy/gather/driver.js b/core/legacy/gather/driver.js index f7ecc6dfef4c..88199b40f7f4 100644 --- a/core/legacy/gather/driver.js +++ b/core/legacy/gather/driver.js @@ -109,8 +109,12 @@ class Driver { uniqueId: undefined, origin: '', name: '', + auxData: {isDefault: true, type: 'default', frameId: ''}, })]; }, + mainFrameExecutionContexts: () => { + return this.targetManager.executionContexts(); + }, /** * Bind to *any* protocol event. * @param {'protocolevent'} event diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts index 399723b944fd..b170705b8e10 100644 --- a/types/gatherer.d.ts +++ b/types/gatherer.d.ts @@ -48,6 +48,7 @@ declare module Gatherer { targetManager: { rootSession(): FRProtocolSession; executionContexts(): Array; + mainFrameExecutionContexts(): Array; on(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void }; From 3a9be8958dfcd7f2894ba3ca1ef22ed14c00f712 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 9 May 2023 11:40:10 -0700 Subject: [PATCH 4/6] add test for timespan --- core/test/scenarios/api-test-pptr.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/test/scenarios/api-test-pptr.js b/core/test/scenarios/api-test-pptr.js index acf9a8c2ec79..dd7c990ed340 100644 --- a/core/test/scenarios/api-test-pptr.js +++ b/core/test/scenarios/api-test-pptr.js @@ -9,6 +9,7 @@ import jestMock from 'jest-mock'; import * as api from '../../index.js'; import {createTestState, getAuditsBreakdown} from './pptr-test-utils.js'; import {LH_ROOT} from '../../../root.js'; +import {TargetManager} from '../../gather/driver/target-manager.js'; describe('Fraggle Rock API', function() { // eslint-disable-next-line no-invalid-this @@ -147,6 +148,7 @@ describe('Fraggle Rock API', function() { // eslint-disable-next-line max-len it('should know target type of network requests from frames created before timespan', async () => { + const spy = jestMock.spyOn(TargetManager.prototype, '_onExecutionContextCreated'); state.server.baseDir = `${LH_ROOT}/cli/test/fixtures`; const {page, serverBaseUrl} = state; @@ -192,6 +194,18 @@ Array [ }, ] `); + + // Check that TargetManager is getting execution context created events even if connecting + // to the page after they already exist. + // There are two execution contexts, one for the main frame and one for the iframe of + // the same origin. + const contextCreatedMainFrameCalls = + spy.mock.calls.filter(call => call[0].context.origin === 'http://localhost:10200'); + // For some reason, puppeteer gives us two created events for every uniqueId, + // so using Set here to ignore that detail. + expect(new Set(contextCreatedMainFrameCalls.map(call => call[0].context.uniqueId)).size) + .toEqual(2); + spy.mockRestore(); }); }); From 078123a63733da58a1302fd45c50aa5608a87554 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 9 May 2023 12:08:01 -0700 Subject: [PATCH 5/6] map time map time --- core/gather/driver/target-manager.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/core/gather/driver/target-manager.js b/core/gather/driver/target-manager.js index ef80d29d48bc..f521fd4f6b7d 100644 --- a/core/gather/driver/target-manager.js +++ b/core/gather/driver/target-manager.js @@ -48,8 +48,8 @@ class TargetManager extends ProtocolEventEmitter { * @type {Map} */ this._targetIdToTargets = new Map(); - /** @type {Array} */ - this._executionContextDescriptions = []; + /** @type {Map} */ + this._executionContextIdToDescriptions = new Map(); this._onSessionAttached = this._onSessionAttached.bind(this); this._onFrameNavigated = this._onFrameNavigated.bind(this); @@ -104,11 +104,11 @@ class TargetManager extends ProtocolEventEmitter { } executionContexts() { - return [...this._executionContextDescriptions]; + return [...this._executionContextIdToDescriptions.values()]; } mainFrameExecutionContexts() { - return this._executionContextDescriptions.filter(executionContext => { + return this.executionContexts().filter(executionContext => { return executionContext.auxData.frameId === this._mainFrameId; }); } @@ -181,26 +181,18 @@ class TargetManager extends ProtocolEventEmitter { if (event.context.name === '__puppeteer_utility_world__') return; if (event.context.name === 'lighthouse_isolated_context') return; - const index = this._executionContextDescriptions.findIndex(d => - d.uniqueId === event.context.uniqueId); - if (index === -1) { - this._executionContextDescriptions.push(event.context); - } + this._executionContextIdToDescriptions.set(event.context.uniqueId, event.context); } /** * @param {LH.Crdp.Runtime.ExecutionContextDestroyedEvent} event */ _onExecutionContextDestroyed(event) { - const index = this._executionContextDescriptions.findIndex(d => - d.uniqueId === event.executionContextUniqueId); - if (index !== -1) { - this._executionContextDescriptions.splice(index, 1); - } + this._executionContextIdToDescriptions.delete(event.executionContextUniqueId); } _onExecutionContextsCleared() { - this._executionContextDescriptions = []; + this._executionContextIdToDescriptions.clear(); } /** @@ -233,6 +225,7 @@ class TargetManager extends ProtocolEventEmitter { this._enabled = true; this._targetIdToTargets = new Map(); + this._executionContextIdToDescriptions = new Map(); this._rootCdpSession.on('Page.frameNavigated', this._onFrameNavigated); this._rootCdpSession.on('Runtime.executionContextCreated', this._onExecutionContextCreated); @@ -266,7 +259,7 @@ class TargetManager extends ProtocolEventEmitter { this._enabled = false; this._targetIdToTargets = new Map(); - this._executionContextDescriptions = []; + this._executionContextIdToDescriptions = new Map(); } } From ed67e34da3b2244853828316a30fb8ba3d869a26 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 9 May 2023 13:14:46 -0700 Subject: [PATCH 6/6] pr --- core/gather/driver/target-manager.js | 16 +++++----------- core/gather/gatherers/global-listeners.js | 4 +++- core/legacy/gather/driver.js | 5 +---- core/test/gather/driver-test.js | 1 + core/test/gather/driver/network-monitor-test.js | 1 + core/test/gather/driver/target-manager-test.js | 4 ++++ types/gatherer.d.ts | 1 - 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/core/gather/driver/target-manager.js b/core/gather/driver/target-manager.js index f521fd4f6b7d..0619b9eee938 100644 --- a/core/gather/driver/target-manager.js +++ b/core/gather/driver/target-manager.js @@ -48,7 +48,7 @@ class TargetManager extends ProtocolEventEmitter { * @type {Map} */ this._targetIdToTargets = new Map(); - /** @type {Map} */ + /** @type {Map} */ this._executionContextIdToDescriptions = new Map(); this._onSessionAttached = this._onSessionAttached.bind(this); @@ -103,12 +103,8 @@ class TargetManager extends ProtocolEventEmitter { return this._findSession(rootSessionId); } - executionContexts() { - return [...this._executionContextIdToDescriptions.values()]; - } - mainFrameExecutionContexts() { - return this.executionContexts().filter(executionContext => { + return [...this._executionContextIdToDescriptions.values()].filter(executionContext => { return executionContext.auxData.frameId === this._mainFrameId; }); } @@ -117,7 +113,6 @@ class TargetManager extends ProtocolEventEmitter { * @param {LH.Puppeteer.CDPSession} cdpSession */ async _onSessionAttached(cdpSession) { - const isRootSession = this._rootCdpSession === cdpSession; const newSession = new ProtocolSession(cdpSession); try { @@ -132,7 +127,6 @@ class TargetManager extends ProtocolEventEmitter { const targetId = target.targetInfo.targetId; if (this._targetIdToTargets.has(targetId)) return; - if (isRootSession) this._mainFrameId = targetId; newSession.setTargetInfo(target.targetInfo); const targetName = target.targetInfo.url || target.targetInfo.targetId; log.verbose('target-manager', `target ${targetName} attached`); @@ -175,9 +169,6 @@ class TargetManager extends ProtocolEventEmitter { * @param {LH.Crdp.Runtime.ExecutionContextCreatedEvent} event */ _onExecutionContextCreated(event) { - // This execution context was made via the protocol (e.g. by us or Puppeteer). - if (event.context.origin === '://' || event.context.origin === '') return; - // Just in case the above changes somehow. if (event.context.name === '__puppeteer_utility_world__') return; if (event.context.name === 'lighthouse_isolated_context') return; @@ -235,6 +226,8 @@ class TargetManager extends ProtocolEventEmitter { await this._rootCdpSession.send('Page.enable'); await this._rootCdpSession.send('Runtime.enable'); + this._mainFrameId = (await this._rootCdpSession.send('Page.getFrameTree')).frameTree.frame.id; + // Start with the already attached root session. await this._onSessionAttached(this._rootCdpSession); } @@ -260,6 +253,7 @@ class TargetManager extends ProtocolEventEmitter { this._enabled = false; this._targetIdToTargets = new Map(); this._executionContextIdToDescriptions = new Map(); + this._mainFrameId = ''; } } diff --git a/core/gather/gatherers/global-listeners.js b/core/gather/gatherers/global-listeners.js index 16c4dca7e7cf..96a8982ee38a 100644 --- a/core/gather/gatherers/global-listeners.js +++ b/core/gather/gatherers/global-listeners.js @@ -11,6 +11,8 @@ * around page unload, but this can be expanded in the future. */ +import log from 'lighthouse-logger'; + import FRGatherer from '../base-gatherer.js'; class GlobalListeners extends FRGatherer { @@ -79,7 +81,7 @@ class GlobalListeners extends FRGatherer { objectId = result.objectId; } catch (err) { // Execution context is no longer valid, but don't let that fail the gatherer. - console.error(err); + log.warn('Execution context is no longer valid', executionContext, err); continue; } diff --git a/core/legacy/gather/driver.js b/core/legacy/gather/driver.js index 88199b40f7f4..27c055a15d82 100644 --- a/core/legacy/gather/driver.js +++ b/core/legacy/gather/driver.js @@ -102,7 +102,7 @@ class Driver { return this.defaultSession; }, // For legacy driver, only bother supporting access to the default execution context. - executionContexts: () => { + mainFrameExecutionContexts: () => { // @ts-expect-error - undefined ids are OK for purposes of calling protocol commands like Runtime.evaluate. return [/** @type {LH.Crdp.Runtime.ExecutionContextDescription} */({ id: undefined, @@ -112,9 +112,6 @@ class Driver { auxData: {isDefault: true, type: 'default', frameId: ''}, })]; }, - mainFrameExecutionContexts: () => { - return this.targetManager.executionContexts(); - }, /** * Bind to *any* protocol event. * @param {'protocolevent'} event diff --git a/core/test/gather/driver-test.js b/core/test/gather/driver-test.js index 62622d0e0bbd..d82e57a20a4f 100644 --- a/core/test/gather/driver-test.js +++ b/core/test/gather/driver-test.js @@ -27,6 +27,7 @@ beforeEach(() => { const puppeteerSession = createMockCdpSession(); puppeteerSession.send .mockResponse('Page.enable') + .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: 'mainFrameId'}}}) .mockResponse('Runtime.enable') .mockResponse('Page.disable') .mockResponse('Runtime.disable') diff --git a/core/test/gather/driver/network-monitor-test.js b/core/test/gather/driver/network-monitor-test.js index 079bfa07b199..179a645a97bb 100644 --- a/core/test/gather/driver/network-monitor-test.js +++ b/core/test/gather/driver/network-monitor-test.js @@ -38,6 +38,7 @@ describe('NetworkMonitor', () => { const cdpSessionMock = createMockCdpSession(id); cdpSessionMock.send .mockResponse('Page.enable') + .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: 'mainFrameId'}}}) .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo: {type: targetType, targetId: id}}) .mockResponse('Network.enable') diff --git a/core/test/gather/driver/target-manager-test.js b/core/test/gather/driver/target-manager-test.js index b5ca4a446b88..c01df1515c2c 100644 --- a/core/test/gather/driver/target-manager-test.js +++ b/core/test/gather/driver/target-manager-test.js @@ -40,6 +40,7 @@ describe('TargetManager', () => { sendMock = sessionMock.send; sendMock .mockResponse('Page.enable') + .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: 'mainFrameId'}}}) .mockResponse('Runtime.enable') .mockResponse('Page.disable') .mockResponse('Runtime.disable') @@ -58,6 +59,7 @@ describe('TargetManager', () => { expect(sendMock.findAllInvocations('Target.setAutoAttach')).toHaveLength(1); expect(sendMock.findAllInvocations('Runtime.runIfWaitingForDebugger')).toHaveLength(1); + expect(targetManager._mainFrameId).toEqual('mainFrameId'); }); it('should autoattach to further unique sessions', async () => { @@ -261,6 +263,7 @@ describe('TargetManager', () => { // Still mock command responses at session level. rootSession.send = createMockSendCommandFn({useSessionId: false}) .mockResponse('Page.enable') + .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: ''}}}) .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo: rootTargetInfo}) .mockResponse('Network.enable') @@ -331,6 +334,7 @@ describe('TargetManager', () => { // Still mock command responses at session level. rootSession.send = createMockSendCommandFn({useSessionId: false}) .mockResponse('Page.enable') + .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: ''}}}) .mockResponse('Runtime.enable') .mockResponse('Target.getTargetInfo', {targetInfo}) .mockResponse('Network.enable') diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts index b170705b8e10..5b252bec611e 100644 --- a/types/gatherer.d.ts +++ b/types/gatherer.d.ts @@ -47,7 +47,6 @@ declare module Gatherer { url: () => Promise; targetManager: { rootSession(): FRProtocolSession; - executionContexts(): Array; mainFrameExecutionContexts(): Array; on(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void