Skip to content

Commit

Permalink
core(global-listeners): iterate all execution contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed May 8, 2023
1 parent 3ba11a8 commit d4fe038
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 22 deletions.
50 changes: 50 additions & 0 deletions core/gather/driver/target-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ class TargetManager extends ProtocolEventEmitter {
* @type {Map<string, TargetWithSession>}
*/
this._targetIdToTargets = new Map();
/** @type {Array<LH.Crdp.Runtime.ExecutionContextDescription>} */
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);
}

/**
Expand Down Expand Up @@ -97,6 +102,10 @@ class TargetManager extends ProtocolEventEmitter {
return this._findSession(rootSessionId);
}

executionContexts() {
return [...this._executionContextDescriptions];
}

/**
* @param {LH.Puppeteer.CDPSession} cdpSession
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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 = [];
}
}

Expand Down
57 changes: 36 additions & 21 deletions core/gather/gatherers/global-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<LH.Artifacts.GlobalListener>} */
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);
}
}

Expand Down
10 changes: 10 additions & 0 deletions core/legacy/gather/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions core/test/gather/driver-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions core/test/gather/driver/network-monitor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
7 changes: 6 additions & 1 deletion core/test/gather/driver/target-manager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions types/gatherer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ declare module Gatherer {
url: () => Promise<string>;
targetManager: {
rootSession(): FRProtocolSession;
executionContexts(): Array<LH.Crdp.Runtime.ExecutionContextDescription>;
on(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void
off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void
};
Expand Down

0 comments on commit d4fe038

Please sign in to comment.