Skip to content

Commit 18f0036

Browse files
authored
Merge branch 'main' into main
2 parents eded1ae + c3784d1 commit 18f0036

File tree

15 files changed

+565
-75
lines changed

15 files changed

+565
-75
lines changed

package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"@types/yargs": "^17.0.33",
5353
"@typescript-eslint/eslint-plugin": "^8.43.0",
5454
"@typescript-eslint/parser": "^8.43.0",
55-
"chrome-devtools-frontend": "1.0.1543472",
55+
"chrome-devtools-frontend": "1.0.1544076",
5656
"core-js": "3.46.0",
5757
"debug": "4.4.3",
5858
"eslint": "^9.35.0",

scripts/post-build.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const i18n = {
6363
return str;
6464
},
6565
lockedLazyString: () => {},
66-
getLazilyComputedLocalizedString: () => {},
66+
getLazilyComputedLocalizedString: () => ()=>{},
6767
};
6868
6969
// TODO(jacktfranklin): once the DocumentLatency insight does not depend on
@@ -169,6 +169,15 @@ export const hostConfig = {};
169169
fs.copyFileSync(devtoolsLicenseFileSource, devtoolsLicenseFileDestination);
170170

171171
copyThirdPartyLicenseFiles();
172+
copyDevToolsDescriptionFiles();
173+
}
174+
175+
function copyDevToolsDescriptionFiles() {
176+
const devtoolsIssuesDescriptionPath =
177+
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions';
178+
const sourceDir = path.join(process.cwd(), devtoolsIssuesDescriptionPath);
179+
const destDir = path.join(BUILD_DIR, devtoolsIssuesDescriptionPath);
180+
fs.cpSync(sourceDir, destDir, {recursive: true});
172181
}
173182

174183
main();

src/DevtoolsUtils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
* Copyright 2025 Google LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6+
7+
import {
8+
type Issue,
9+
type IssuesManagerEventTypes,
10+
Common,
11+
} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
12+
613
export function extractUrlLikeFromDevToolsTitle(
714
title: string,
815
): string | undefined {
@@ -49,3 +56,14 @@ function normalizeUrl(url: string): string {
4956

5057
return result;
5158
}
59+
60+
/**
61+
* A mock implementation of an issues manager that only implements the methods
62+
* that are actually used by the IssuesAggregator
63+
*/
64+
export class FakeIssuesManager extends Common.ObjectWrapper
65+
.ObjectWrapper<IssuesManagerEventTypes> {
66+
issues(): Issue[] {
67+
return [];
68+
}
69+
}

src/McpContext.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import fs from 'node:fs/promises';
77
import os from 'node:os';
88
import path from 'node:path';
99

10+
import {type AggregatedIssue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
11+
1012
import {extractUrlLikeFromDevToolsTitle, urlsEqual} from './DevtoolsUtils.js';
1113
import type {ListenerMap} from './PageCollector.js';
12-
import {NetworkCollector, PageCollector} from './PageCollector.js';
14+
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
1315
import {Locator} from './third_party/index.js';
1416
import type {
1517
Browser,
@@ -92,7 +94,7 @@ export class McpContext implements Context {
9294
// The most recent snapshot.
9395
#textSnapshot: TextSnapshot | null = null;
9496
#networkCollector: NetworkCollector;
95-
#consoleCollector: PageCollector<ConsoleMessage | Error>;
97+
#consoleCollector: ConsoleCollector;
9698

9799
#isRunningTrace = false;
98100
#networkConditionsMap = new WeakMap<Page, string>();
@@ -122,7 +124,7 @@ export class McpContext implements Context {
122124
this.#options.experimentalIncludeAllPages,
123125
);
124126

125-
this.#consoleCollector = new PageCollector(
127+
this.#consoleCollector = new ConsoleCollector(
126128
this.browser,
127129
collect => {
128130
return {
@@ -138,6 +140,9 @@ export class McpContext implements Context {
138140
collect(error);
139141
}
140142
},
143+
issue: event => {
144+
collect(event);
145+
},
141146
} as ListenerMap;
142147
},
143148
this.#options.experimentalIncludeAllPages,
@@ -150,6 +155,11 @@ export class McpContext implements Context {
150155
await this.#consoleCollector.init();
151156
}
152157

158+
dispose() {
159+
this.#networkCollector.dispose();
160+
this.#consoleCollector.dispose();
161+
}
162+
153163
static async from(
154164
browser: Browser,
155165
logger: Debugger,
@@ -205,16 +215,18 @@ export class McpContext implements Context {
205215

206216
getConsoleData(
207217
includePreservedMessages?: boolean,
208-
): Array<ConsoleMessage | Error> {
218+
): Array<ConsoleMessage | Error | AggregatedIssue> {
209219
const page = this.getSelectedPage();
210220
return this.#consoleCollector.getData(page, includePreservedMessages);
211221
}
212222

213-
getConsoleMessageStableId(message: ConsoleMessage | Error): number {
223+
getConsoleMessageStableId(
224+
message: ConsoleMessage | Error | AggregatedIssue,
225+
): number {
214226
return this.#consoleCollector.getIdForResource(message);
215227
}
216228

217-
getConsoleMessageById(id: number): ConsoleMessage | Error {
229+
getConsoleMessageById(id: number): ConsoleMessage | Error | AggregatedIssue {
218230
return this.#consoleCollector.getById(this.getSelectedPage(), id);
219231
}
220232

src/McpResponse.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
* Copyright 2025 Google LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6+
import {
7+
AggregatedIssue,
8+
Marked,
9+
findTitleFromMarkdownAst,
10+
} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
11+
612
import type {ConsoleMessageData} from './formatters/consoleFormatter.js';
713
import {
814
formatConsoleEventShort,
@@ -16,6 +22,8 @@ import {
1622
getStatusFromRequest,
1723
} from './formatters/networkFormatter.js';
1824
import {formatSnapshotNode} from './formatters/snapshotFormatter.js';
25+
import {getIssueDescription} from './issue-descriptions.js';
26+
import {logger} from './logger.js';
1927
import type {McpContext} from './McpContext.js';
2028
import type {
2129
ConsoleMessage,
@@ -269,40 +277,70 @@ export class McpResponse implements Response {
269277
if ('type' in message) {
270278
return normalizedTypes.has(message.type());
271279
}
280+
if (message instanceof AggregatedIssue) {
281+
return normalizedTypes.has('issue');
282+
}
272283
return normalizedTypes.has('error');
273284
});
274285
}
275286

276-
consoleListData = await Promise.all(
277-
messages.map(async (item): Promise<ConsoleMessageData> => {
278-
const consoleMessageStableId =
279-
context.getConsoleMessageStableId(item);
280-
if ('args' in item) {
281-
const consoleMessage = item as ConsoleMessage;
287+
consoleListData = (
288+
await Promise.all(
289+
messages.map(async (item): Promise<ConsoleMessageData | null> => {
290+
const consoleMessageStableId =
291+
context.getConsoleMessageStableId(item);
292+
if ('args' in item) {
293+
const consoleMessage = item as ConsoleMessage;
294+
return {
295+
consoleMessageStableId,
296+
type: consoleMessage.type(),
297+
message: consoleMessage.text(),
298+
args: await Promise.all(
299+
consoleMessage.args().map(async arg => {
300+
const stringArg = await arg.jsonValue().catch(() => {
301+
// Ignore errors.
302+
});
303+
return typeof stringArg === 'object'
304+
? JSON.stringify(stringArg)
305+
: String(stringArg);
306+
}),
307+
),
308+
};
309+
}
310+
if (item instanceof AggregatedIssue) {
311+
const count = item.getAggregatedIssuesCount();
312+
const filename = item.getDescription()?.file;
313+
const rawMarkdown = filename
314+
? getIssueDescription(filename)
315+
: null;
316+
if (!rawMarkdown) {
317+
logger(`no markdown ${filename} found for issue:` + item.code);
318+
return null;
319+
}
320+
const markdownAst = Marked.Marked.lexer(rawMarkdown);
321+
const title = findTitleFromMarkdownAst(markdownAst);
322+
if (!title) {
323+
logger('cannot read issue title from ' + filename);
324+
return null;
325+
}
326+
return {
327+
consoleMessageStableId,
328+
type: 'issue',
329+
item,
330+
message: title,
331+
count,
332+
args: [],
333+
};
334+
}
282335
return {
283336
consoleMessageStableId,
284-
type: consoleMessage.type(),
285-
message: consoleMessage.text(),
286-
args: await Promise.all(
287-
consoleMessage.args().map(async arg => {
288-
const stringArg = await arg.jsonValue().catch(() => {
289-
// Ignore errors.
290-
});
291-
return typeof stringArg === 'object'
292-
? JSON.stringify(stringArg)
293-
: String(stringArg);
294-
}),
295-
),
337+
type: 'error',
338+
message: (item as Error).message,
339+
args: [],
296340
};
297-
}
298-
return {
299-
consoleMessageStableId,
300-
type: 'error',
301-
message: (item as Error).message,
302-
args: [],
303-
};
304-
}),
305-
);
341+
}),
342+
)
343+
).filter(item => item !== null);
306344
}
307345

308346
return this.format(toolName, context, {

0 commit comments

Comments
 (0)