Skip to content

Commit 9803c9c

Browse files
author
Natallia Harshunova
committed
Create console Collector and extract issues subscription logic there
1 parent 40e8a5a commit 9803c9c

File tree

4 files changed

+80
-67
lines changed

4 files changed

+80
-67
lines changed

scripts/post-build.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,16 @@ export const hostConfig = {};
173173
}
174174

175175
function copyDevToolsDescriptionFiles() {
176+
const devtoolsIssuesDescriptionPath = 'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions';
176177
const sourceDir = path.join(
177178
process.cwd(),
178-
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions',
179+
devtoolsIssuesDescriptionPath,
179180
);
180181
const destDir = path.join(
181182
BUILD_DIR,
182-
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions',
183+
devtoolsIssuesDescriptionPath,
183184
);
184185
fs.cpSync(sourceDir, destDir, {recursive: true});
185186
}
186187

187-
main();
188+
main();

src/DevtoolsUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import {
88
type Issue,
9-
type IssuesManager,
9+
type IssuesManagerEventTypes,
1010
Common
1111
} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
1212

@@ -61,7 +61,7 @@ function normalizeUrl(url: string): string {
6161
* A mock implementation of an issues manager that only implements the methods
6262
* that are actually used by the IssuesAggregator
6363
*/
64-
export class FakeIssuesManager extends Common.ObjectWrapper.ObjectWrapper<IssuesManager.EventTypes> {
64+
export class FakeIssuesManager extends Common.ObjectWrapper.ObjectWrapper<IssuesManagerEventTypes> {
6565
issues(): Issue[] {
6666
return [];
6767
}

src/McpContext.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {type AggregatedIssue} from '../node_modules/chrome-devtools-frontend/mcp
1111

1212
import {extractUrlLikeFromDevToolsTitle, urlsEqual} from './DevtoolsUtils.js';
1313
import type {ListenerMap} from './PageCollector.js';
14-
import {NetworkCollector, PageCollector} from './PageCollector.js';
14+
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
1515
import {Locator} from './third_party/index.js';
1616
import type {
1717
Browser,
@@ -96,7 +96,7 @@ export class McpContext implements Context {
9696
// The most recent snapshot.
9797
#textSnapshot: TextSnapshot | null = null;
9898
#networkCollector: NetworkCollector;
99-
#consoleCollector: PageCollector<ConsoleMessage | Error | AggregatedIssue>;
99+
#consoleCollector: ConsoleCollector;
100100

101101
#isRunningTrace = false;
102102
#networkConditionsMap = new WeakMap<Page, string>();
@@ -126,11 +126,14 @@ export class McpContext implements Context {
126126
this.#options.experimentalIncludeAllPages,
127127
);
128128

129-
this.#consoleCollector = new PageCollector(
130-
this.browser,
131-
collect => {
132-
return {
133-
console: event => {
129+
130+
this.#consoleCollector = new ConsoleCollector(this.browser, collect => {
131+
return {
132+
console: event => {
133+
collect(event);
134+
},
135+
pageerror: event => {
136+
if (event instanceof Error) {
134137
collect(event);
135138
},
136139
pageerror: event => {

src/PageCollector.ts

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
import {
88
type AggregatedIssue,
9-
AggregatorEvents,
10-
IssuesManager,
9+
IssueAggregatorEvents,
10+
IssuesManagerEvents,
11+
createIssuesFromProtocolIssue,
1112
IssueAggregator,
1213
} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
1314

1415
import {FakeIssuesManager} from './DevtoolsUtils.js';
16+
import type {ConsoleMessage} from './third_party/index.js';
1517
import {
1618
type Browser,
1719
type Frame,
@@ -50,13 +52,9 @@ export class PageCollector<T> {
5052
collector: (item: T) => void,
5153
) => ListenerMap<PageEvents>;
5254
#listeners = new WeakMap<Page, ListenerMap>();
53-
#seenIssueKeys = new WeakMap<Page, Set<string>>();
5455
#maxNavigationSaved = 3;
5556
#includeAllPages?: boolean;
5657

57-
// Store an aggregator and a mock manager for each page.
58-
#issuesAggregators = new WeakMap<Page, IssueAggregator>();
59-
#mockIssuesManagers = new WeakMap<Page, FakeIssuesManager>();
6058

6159
protected storage = new WeakMap<Page, Array<Array<WithSymbolId<T>>>>();
6260

@@ -88,7 +86,7 @@ export class PageCollector<T> {
8886
if (!page) {
8987
return;
9088
}
91-
this.#cleanupPageDestroyed(page);
89+
this.cleanupPageDestroyed(page);
9290
});
9391
}
9492

@@ -122,7 +120,7 @@ export class PageCollector<T> {
122120
}
123121
};
124122

125-
await this.subscribeForIssues(page);
123+
// await this.subscribeForIssues(page);
126124

127125
const listeners = this.#listenersInitializer(collector);
128126

@@ -141,49 +139,6 @@ export class PageCollector<T> {
141139
this.#listeners.set(page, listeners);
142140
}
143141

144-
protected async subscribeForIssues(page: Page) {
145-
if (this instanceof NetworkCollector) {
146-
return;
147-
}
148-
if (!this.#seenIssueKeys.has(page)) {
149-
this.#seenIssueKeys.set(page, new Set());
150-
}
151-
152-
const mockManager = new FakeIssuesManager();
153-
// @ts-expect-error Aggregator receives partial IssuesManager
154-
const aggregator = new IssueAggregator(mockManager);
155-
this.#mockIssuesManagers.set(page, mockManager);
156-
this.#issuesAggregators.set(page, aggregator);
157-
158-
aggregator.addEventListener(
159-
AggregatorEvents.AGGREGATED_ISSUE_UPDATED,
160-
event => {
161-
page.emit('issue', event.data);
162-
},
163-
);
164-
165-
const session = await page.createCDPSession();
166-
session.on('Audits.issueAdded', data => {
167-
// @ts-expect-error Types of protocol from Puppeteer and CDP are incopatible for Issues but it's the same type
168-
const issue = IssuesManager.createIssuesFromProtocolIssue(null,data.issue,)[0];
169-
if (!issue) {
170-
return;
171-
}
172-
const seenKeys = this.#seenIssueKeys.get(page)!;
173-
const primaryKey = issue.primaryKey();
174-
if (seenKeys.has(primaryKey)) return;
175-
seenKeys.add(primaryKey);
176-
177-
// Trigger the aggregator via our mock manager. Do NOT call collector() here.
178-
const mockManager = this.#mockIssuesManagers.get(page);
179-
if (mockManager) {
180-
// @ts-expect-error we don't care about issies model being null
181-
mockManager.dispatchEventToListeners(IssuesManager.Events.ISSUE_ADDED, {issue, issuesModel: null});
182-
}
183-
});
184-
await session.send('Audits.enable');
185-
}
186-
187142
protected splitAfterNavigation(page: Page) {
188143
const navigations = this.storage.get(page);
189144
if (!navigations) {
@@ -193,17 +148,14 @@ export class PageCollector<T> {
193148
navigations.splice(this.#maxNavigationSaved);
194149
}
195150

196-
#cleanupPageDestroyed(page: Page) {
151+
protected cleanupPageDestroyed(page: Page) {
197152
const listeners = this.#listeners.get(page);
198153
if (listeners) {
199154
for (const [name, listener] of Object.entries(listeners)) {
200155
page.off(name, listener as Handler<unknown>);
201156
}
202157
}
203158
this.storage.delete(page);
204-
this.#seenIssueKeys.delete(page);
205-
this.#issuesAggregators.delete(page);
206-
this.#mockIssuesManagers.delete(page);
207159
}
208160

209161
getData(page: Page, includePreservedData?: boolean): T[] {
@@ -263,6 +215,63 @@ export class PageCollector<T> {
263215
}
264216
}
265217

218+
export class ConsoleCollector extends PageCollector<ConsoleMessage | Error | AggregatedIssue> {
219+
#seenIssueKeys = new WeakMap<Page, Set<string>>();
220+
#issuesAggregators = new WeakMap<Page, IssueAggregator>();
221+
#mockIssuesManagers = new WeakMap<Page, FakeIssuesManager>();
222+
223+
override async addPage(page: Page) {
224+
await super.addPage(page);
225+
await this.subscribeForIssues(page);
226+
}
227+
async subscribeForIssues(page: Page) {
228+
if (!this.#seenIssueKeys.has(page)) {
229+
this.#seenIssueKeys.set(page, new Set());
230+
}
231+
232+
const mockManager = new FakeIssuesManager();
233+
// @ts-expect-error Aggregator receives partial IssuesManager
234+
const aggregator = new IssueAggregator(mockManager);
235+
this.#mockIssuesManagers.set(page, mockManager);
236+
this.#issuesAggregators.set(page, aggregator);
237+
238+
aggregator.addEventListener(
239+
IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED,
240+
event => {
241+
page.emit('issue', event.data);
242+
},
243+
);
244+
245+
const session = await page.createCDPSession();
246+
session.on('Audits.issueAdded', data => {
247+
// @ts-expect-error Types of protocol from Puppeteer and CDP are incopatible for Issues but it's the same type
248+
const issue = createIssuesFromProtocolIssue(null,data.issue,)[0];
249+
if (!issue) {
250+
return;
251+
}
252+
const seenKeys = this.#seenIssueKeys.get(page)!;
253+
const primaryKey = issue.primaryKey();
254+
if (seenKeys.has(primaryKey)) return;
255+
seenKeys.add(primaryKey);
256+
257+
// Trigger the aggregator via our mock manager. Do NOT call collector() here.
258+
const mockManager = this.#mockIssuesManagers.get(page);
259+
if (mockManager) {
260+
// @ts-expect-error We don't care that issues model is null
261+
mockManager.dispatchEventToListeners(IssuesManagerEvents.ISSUE_ADDED, {issue, issuesModel: null});
262+
}
263+
});
264+
await session.send('Audits.enable');
265+
}
266+
267+
override cleanupPageDestroyed(page: Page) {
268+
super.cleanupPageDestroyed(page);
269+
this.#seenIssueKeys.delete(page);
270+
this.#issuesAggregators.delete(page);
271+
this.#mockIssuesManagers.delete(page);
272+
}
273+
}
274+
266275
export class NetworkCollector extends PageCollector<HTTPRequest> {
267276
constructor(
268277
browser: Browser,

0 commit comments

Comments
 (0)