chrome-devtools-mcp 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -4
- package/build/src/HeapSnapshotManager.js +30 -16
- package/build/src/McpContext.js +63 -9
- package/build/src/McpPage.js +9 -9
- package/build/src/McpResponse.js +176 -58
- package/build/src/PageCollector.js +8 -8
- package/build/src/ServiceWorkerCollector.js +171 -0
- package/build/src/TextSnapshot.js +1 -1
- package/build/src/ToolHandler.js +10 -4
- package/build/src/WaitForHelper.js +2 -2
- package/build/src/bin/chrome-devtools-cli-options.js +106 -4
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +64 -5
- package/build/src/bin/chrome-devtools-mcp-main.js +5 -5
- package/build/src/browser.js +8 -4
- package/build/src/daemon/client.js +7 -7
- package/build/src/daemon/daemon.js +12 -12
- package/build/src/daemon/utils.js +2 -2
- package/build/src/{DevToolsConnectionAdapter.js → devtools/DevToolsConnectionAdapter.js} +1 -1
- package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +79 -18
- package/build/src/devtools/McpHostBindingAdapter.js +165 -0
- package/build/src/formatters/ConsoleFormatter.js +1 -1
- package/build/src/formatters/HeapSnapshotFormatter.js +22 -0
- package/build/src/formatters/IssueFormatter.js +4 -4
- package/build/src/index.js +13 -1
- package/build/src/telemetry/ClearcutLogger.js +1 -1
- package/build/src/telemetry/WatchdogClient.js +4 -4
- package/build/src/telemetry/persistence.js +2 -2
- package/build/src/telemetry/watchdog/ClearcutSender.js +10 -10
- package/build/src/telemetry/watchdog/main.js +5 -5
- package/build/src/third_party/THIRD_PARTY_NOTICES +31 -1
- package/build/src/third_party/bundled-packages.json +2 -1
- package/build/src/third_party/devtools-formatter-worker.js +1 -1
- package/build/src/third_party/devtools-heap-snapshot-worker.js +161 -3
- package/build/src/third_party/index.js +7020 -5774
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
- package/build/src/tools/console.js +7 -0
- package/build/src/tools/emulation.js +1 -0
- package/build/src/tools/extensions.js +5 -2
- package/build/src/tools/input.js +11 -3
- package/build/src/tools/lighthouse.js +11 -6
- package/build/src/tools/memory.js +109 -10
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +13 -5
- package/build/src/tools/performance.js +8 -7
- package/build/src/tools/screencast.js +32 -10
- package/build/src/tools/screenshot.js +158 -76
- package/build/src/tools/script.js +1 -1
- package/build/src/tools/slim/tools.js +3 -0
- package/build/src/tools/snapshot.js +3 -2
- package/build/src/tools/thirdPartyDeveloper.js +12 -2
- package/build/src/tools/webmcp.js +2 -0
- package/build/src/trace-processing/parse.js +5 -5
- package/build/src/version.js +1 -1
- package/package.json +4 -3
package/build/src/McpResponse.js
CHANGED
|
@@ -11,7 +11,7 @@ import { NetworkFormatter } from './formatters/NetworkFormatter.js';
|
|
|
11
11
|
import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
|
|
12
12
|
import { UncaughtError } from './PageCollector.js';
|
|
13
13
|
import { TextSnapshot } from './TextSnapshot.js';
|
|
14
|
-
import { DevTools } from './third_party/index.js';
|
|
14
|
+
import { DevTools, toonEncode } from './third_party/index.js';
|
|
15
15
|
import { handleDialog } from './tools/pages.js';
|
|
16
16
|
import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
|
|
17
17
|
import { paginate } from './utils/pagination.js';
|
|
@@ -61,7 +61,7 @@ export function replaceHtmlElementsWithUids(schema) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
async function
|
|
64
|
+
async function getToolGroups(page) {
|
|
65
65
|
// Check if there is a `devtoolstooldiscovery` event listener
|
|
66
66
|
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
|
|
67
67
|
// @ts-expect-error internal API
|
|
@@ -70,43 +70,82 @@ async function getToolGroup(page) {
|
|
|
70
70
|
objectId: windowHandle.remoteObject().objectId,
|
|
71
71
|
});
|
|
72
72
|
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
|
|
73
|
-
return;
|
|
73
|
+
return [];
|
|
74
74
|
}
|
|
75
|
-
const
|
|
75
|
+
const toolGroups = await page.pptrPage.evaluate(() => {
|
|
76
|
+
if (window.__dtmcp) {
|
|
77
|
+
window.__dtmcp.toolGroups = [];
|
|
78
|
+
}
|
|
76
79
|
return new Promise(resolve => {
|
|
77
80
|
const event = new CustomEvent('devtoolstooldiscovery');
|
|
81
|
+
const groups = [];
|
|
78
82
|
// @ts-expect-error Adding custom property
|
|
79
|
-
event.respondWith =
|
|
83
|
+
event.respondWith = toolGroup => {
|
|
80
84
|
if (!window.__dtmcp) {
|
|
81
85
|
window.__dtmcp = {};
|
|
82
86
|
}
|
|
83
|
-
window.__dtmcp.
|
|
87
|
+
if (!window.__dtmcp.toolGroups) {
|
|
88
|
+
window.__dtmcp.toolGroups = [];
|
|
89
|
+
}
|
|
90
|
+
if (typeof toolGroup.name !== 'string' ||
|
|
91
|
+
(toolGroup.description &&
|
|
92
|
+
typeof toolGroup.description !== 'string') ||
|
|
93
|
+
!Array.isArray(toolGroup.tools)) {
|
|
94
|
+
console.error('Invalid toolGroup:', toolGroup);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const tool of toolGroup.tools) {
|
|
98
|
+
if (typeof tool.name !== 'string' ||
|
|
99
|
+
typeof tool.description !== 'string' ||
|
|
100
|
+
typeof tool.inputSchema !== 'object' ||
|
|
101
|
+
typeof tool.execute !== 'function') {
|
|
102
|
+
console.error('Invalid tool:', tool);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
window.__dtmcp.toolGroups.push(toolGroup);
|
|
84
107
|
// When receiving a toolGroup for the first time, expose a simple execution helper
|
|
85
108
|
if (!window.__dtmcp.executeTool) {
|
|
86
109
|
window.__dtmcp.executeTool = async (toolName, args) => {
|
|
87
|
-
if (!window.__dtmcp?.
|
|
110
|
+
if (!window.__dtmcp?.toolGroups ||
|
|
111
|
+
window.__dtmcp.toolGroups.length === 0) {
|
|
88
112
|
throw new Error('No tools found on the page');
|
|
89
113
|
}
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
for (const group of window.__dtmcp.toolGroups) {
|
|
115
|
+
const tool = group.tools?.find(t => t.name === toolName);
|
|
116
|
+
if (tool) {
|
|
117
|
+
return await tool.execute(args);
|
|
118
|
+
}
|
|
93
119
|
}
|
|
94
|
-
|
|
120
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
95
121
|
};
|
|
96
122
|
}
|
|
97
|
-
|
|
123
|
+
groups.push(toolGroup);
|
|
98
124
|
};
|
|
99
125
|
window.dispatchEvent(event);
|
|
100
|
-
// If
|
|
101
|
-
setTimeout
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
// If at least one toolGroup was added synchronously, resolve with the array.
|
|
127
|
+
// Otherwise, use setTimeout to allow for any microtask/asynchronous respondWith calls, or resolve with an empty array.
|
|
128
|
+
if (groups.length > 0) {
|
|
129
|
+
resolve(groups);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
if (groups.length > 0) {
|
|
134
|
+
resolve(groups);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
resolve([]);
|
|
138
|
+
}
|
|
139
|
+
}, 0);
|
|
140
|
+
}
|
|
104
141
|
});
|
|
105
142
|
});
|
|
106
|
-
for (const
|
|
107
|
-
|
|
143
|
+
for (const group of toolGroups) {
|
|
144
|
+
for (const tool of group.tools ?? []) {
|
|
145
|
+
replaceHtmlElementsWithUids(tool.inputSchema);
|
|
146
|
+
}
|
|
108
147
|
}
|
|
109
|
-
return
|
|
148
|
+
return toolGroups;
|
|
110
149
|
}
|
|
111
150
|
export class McpResponse {
|
|
112
151
|
#includePages = false;
|
|
@@ -134,6 +173,9 @@ export class McpResponse {
|
|
|
134
173
|
#redactNetworkHeaders = true;
|
|
135
174
|
#error;
|
|
136
175
|
#attachedWaitForResult;
|
|
176
|
+
get #deviceScope() {
|
|
177
|
+
return this.#page?.viewport?.isMobile ? 'PHONE' : 'DESKTOP';
|
|
178
|
+
}
|
|
137
179
|
constructor(args) {
|
|
138
180
|
this.#args = args;
|
|
139
181
|
}
|
|
@@ -203,6 +245,7 @@ export class McpResponse {
|
|
|
203
245
|
: undefined,
|
|
204
246
|
types: options?.types,
|
|
205
247
|
includePreservedMessages: options?.includePreservedMessages,
|
|
248
|
+
serviceWorkerId: options?.serviceWorkerId,
|
|
206
249
|
};
|
|
207
250
|
}
|
|
208
251
|
setError(error) {
|
|
@@ -291,6 +334,20 @@ export class McpResponse {
|
|
|
291
334
|
pagination: options,
|
|
292
335
|
};
|
|
293
336
|
}
|
|
337
|
+
setHeapSnapshotRetainingPaths(retainingPaths) {
|
|
338
|
+
this.#heapSnapshotOptions = {
|
|
339
|
+
...this.#heapSnapshotOptions,
|
|
340
|
+
include: true,
|
|
341
|
+
retainingPaths,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
setHeapSnapshotDominators(dominators) {
|
|
345
|
+
this.#heapSnapshotOptions = {
|
|
346
|
+
...this.#heapSnapshotOptions,
|
|
347
|
+
include: true,
|
|
348
|
+
dominators,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
294
351
|
attachImage(value) {
|
|
295
352
|
this.#images.push(value);
|
|
296
353
|
}
|
|
@@ -306,7 +363,7 @@ export class McpResponse {
|
|
|
306
363
|
get listWebMcpTools() {
|
|
307
364
|
return this.#listWebMcpTools;
|
|
308
365
|
}
|
|
309
|
-
async handle(toolName, context) {
|
|
366
|
+
async handle(toolName, context, useToon = false) {
|
|
310
367
|
if (this.#includePages) {
|
|
311
368
|
await context.createPagesSnapshot();
|
|
312
369
|
}
|
|
@@ -383,12 +440,11 @@ export class McpResponse {
|
|
|
383
440
|
if (this.#listExtensions) {
|
|
384
441
|
extensions = await context.listExtensions();
|
|
385
442
|
}
|
|
386
|
-
|
|
387
|
-
let thirdPartyDeveloperTools;
|
|
443
|
+
let thirdPartyDeveloperTools = [];
|
|
388
444
|
if (this.#args.categoryExperimentalThirdParty &&
|
|
389
445
|
this.#listThirdPartyDeveloperTools &&
|
|
390
446
|
this.#page) {
|
|
391
|
-
thirdPartyDeveloperTools = await
|
|
447
|
+
thirdPartyDeveloperTools = await getToolGroups(this.#page);
|
|
392
448
|
if (thirdPartyDeveloperTools) {
|
|
393
449
|
this.#page.thirdPartyDeveloperTools = thirdPartyDeveloperTools;
|
|
394
450
|
}
|
|
@@ -401,11 +457,18 @@ export class McpResponse {
|
|
|
401
457
|
}
|
|
402
458
|
let consoleMessages;
|
|
403
459
|
if (this.#consoleDataOptions?.include) {
|
|
404
|
-
|
|
405
|
-
|
|
460
|
+
let messages;
|
|
461
|
+
let page;
|
|
462
|
+
if (this.#consoleDataOptions.serviceWorkerId) {
|
|
463
|
+
messages = context.getServiceWorkerConsoleData(this.#consoleDataOptions.serviceWorkerId);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
page = this.#page;
|
|
467
|
+
if (!page) {
|
|
468
|
+
throw new Error(`Response must have an McpPage`);
|
|
469
|
+
}
|
|
470
|
+
messages = context.getConsoleData(page, this.#consoleDataOptions.includePreservedMessages);
|
|
406
471
|
}
|
|
407
|
-
const page = this.#page;
|
|
408
|
-
let messages = context.getConsoleData(this.#page, this.#consoleDataOptions.includePreservedMessages);
|
|
409
472
|
if (this.#consoleDataOptions.types?.length) {
|
|
410
473
|
const normalizedTypes = new Set(this.#consoleDataOptions.types);
|
|
411
474
|
messages = messages.filter(message => {
|
|
@@ -422,7 +485,9 @@ export class McpResponse {
|
|
|
422
485
|
const consoleMessageStableId = context.getConsoleMessageStableId(item);
|
|
423
486
|
if ('args' in item || item instanceof UncaughtError) {
|
|
424
487
|
const consoleMessage = item;
|
|
425
|
-
const devTools =
|
|
488
|
+
const devTools = page
|
|
489
|
+
? context.getDevToolsUniverse(page)
|
|
490
|
+
: null;
|
|
426
491
|
return await ConsoleFormatter.from(consoleMessage, {
|
|
427
492
|
id: consoleMessageStableId,
|
|
428
493
|
fetchDetailedData: false,
|
|
@@ -479,9 +544,9 @@ export class McpResponse {
|
|
|
479
544
|
thirdPartyDeveloperTools,
|
|
480
545
|
webmcpTools,
|
|
481
546
|
errorMessage: this.#error?.message,
|
|
482
|
-
});
|
|
547
|
+
}, useToon);
|
|
483
548
|
}
|
|
484
|
-
format(toolName, context, data) {
|
|
549
|
+
async format(toolName, context, data, useToon) {
|
|
485
550
|
const structuredContent = {};
|
|
486
551
|
const response = [];
|
|
487
552
|
if (this.#textResponseLines.length) {
|
|
@@ -561,8 +626,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
561
626
|
const contextLabel = isolatedContextName
|
|
562
627
|
? ` isolatedContext=${isolatedContextName}`
|
|
563
628
|
: '';
|
|
564
|
-
|
|
565
|
-
|
|
629
|
+
const title = await fetchPageTitle(page);
|
|
630
|
+
const pageLabel = title
|
|
631
|
+
? `${truncateTitle(title)} (${page.url()})`
|
|
632
|
+
: page.url();
|
|
633
|
+
parts.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
|
|
634
|
+
structuredPages.push(createStructuredPage(page, context, title));
|
|
566
635
|
}
|
|
567
636
|
response.push(...parts);
|
|
568
637
|
structuredContent.pages = structuredPages;
|
|
@@ -576,8 +645,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
576
645
|
const contextLabel = isolatedContextName
|
|
577
646
|
? ` isolatedContext=${isolatedContextName}`
|
|
578
647
|
: '';
|
|
579
|
-
|
|
580
|
-
|
|
648
|
+
const title = await fetchPageTitle(page);
|
|
649
|
+
const pageLabel = title
|
|
650
|
+
? `${truncateTitle(title)} (${page.url()})`
|
|
651
|
+
: page.url();
|
|
652
|
+
response.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
|
|
653
|
+
structuredExtensionPages.push(createStructuredPage(page, context, title));
|
|
581
654
|
}
|
|
582
655
|
structuredContent.extensionPages = structuredExtensionPages;
|
|
583
656
|
}
|
|
@@ -603,7 +676,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
603
676
|
structuredContent.tabId = this.#tabId;
|
|
604
677
|
}
|
|
605
678
|
if (data.traceSummary) {
|
|
606
|
-
const summary = getTraceSummary(data.traceSummary);
|
|
679
|
+
const summary = getTraceSummary(data.traceSummary, this.#deviceScope);
|
|
607
680
|
response.push(summary);
|
|
608
681
|
structuredContent.traceSummary = summary;
|
|
609
682
|
structuredContent.traceInsights = [];
|
|
@@ -617,7 +690,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
617
690
|
}
|
|
618
691
|
}
|
|
619
692
|
if (data.traceInsight) {
|
|
620
|
-
const insightOutput = getInsightOutput(data.traceInsight.trace, data.traceInsight.insightSetId, data.traceInsight.insightName);
|
|
693
|
+
const insightOutput = getInsightOutput(data.traceInsight.trace, data.traceInsight.insightSetId, data.traceInsight.insightName, this.#deviceScope);
|
|
621
694
|
if ('error' in insightOutput) {
|
|
622
695
|
response.push(insightOutput.error);
|
|
623
696
|
}
|
|
@@ -651,9 +724,11 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
651
724
|
structuredContent.snapshotFilePath = data.snapshot;
|
|
652
725
|
}
|
|
653
726
|
else {
|
|
654
|
-
response.push('## Latest page snapshot');
|
|
655
|
-
response.push(data.snapshot.toString());
|
|
656
727
|
structuredContent.snapshot = data.snapshot.toJSON();
|
|
728
|
+
response.push('## Latest page snapshot');
|
|
729
|
+
response.push(useToon
|
|
730
|
+
? toonEncode(structuredContent.snapshot)
|
|
731
|
+
: data.snapshot.toString());
|
|
657
732
|
}
|
|
658
733
|
}
|
|
659
734
|
if (this.#heapSnapshotOptions?.include) {
|
|
@@ -678,8 +753,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
678
753
|
response.push(...paginationData.info);
|
|
679
754
|
const paginatedRecord = Object.fromEntries(paginationData.items);
|
|
680
755
|
const formatter = new HeapSnapshotFormatter(paginatedRecord);
|
|
681
|
-
response.push(formatter.toString());
|
|
682
756
|
structuredContent.heapSnapshotData = formatter.toJSON();
|
|
757
|
+
response.push(useToon
|
|
758
|
+
? toonEncode(structuredContent.heapSnapshotData)
|
|
759
|
+
: formatter.toString());
|
|
683
760
|
}
|
|
684
761
|
const nodes = this.#heapSnapshotOptions.nodes;
|
|
685
762
|
if (nodes) {
|
|
@@ -701,6 +778,36 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
701
778
|
response.push(...paginationData.info);
|
|
702
779
|
structuredContent.heapSnapshotNodes = paginationData.items;
|
|
703
780
|
}
|
|
781
|
+
const retainingPaths = this.#heapSnapshotOptions.retainingPaths;
|
|
782
|
+
if (retainingPaths) {
|
|
783
|
+
response.push('### Retaining Paths');
|
|
784
|
+
const { paths, limitsReached } = retainingPaths;
|
|
785
|
+
if (paths.length === 0) {
|
|
786
|
+
response.push('No retaining paths found.');
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
response.push(HeapSnapshotFormatter.formatRetainingPaths(paths));
|
|
790
|
+
}
|
|
791
|
+
const reached = Object.entries(limitsReached)
|
|
792
|
+
.filter(([, hit]) => hit)
|
|
793
|
+
.map(([limit]) => limit);
|
|
794
|
+
if (reached.length > 0) {
|
|
795
|
+
response.push(`Note: results are truncated, the following limits were reached: ${reached.join(', ')}.`);
|
|
796
|
+
}
|
|
797
|
+
structuredContent.heapSnapshotRetainingPaths =
|
|
798
|
+
retainingPaths;
|
|
799
|
+
}
|
|
800
|
+
const dominators = this.#heapSnapshotOptions.dominators;
|
|
801
|
+
if (dominators) {
|
|
802
|
+
response.push('### Dominator Chain');
|
|
803
|
+
if (dominators.length === 0) {
|
|
804
|
+
response.push('No dominators found.');
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
response.push(HeapSnapshotFormatter.formatDominators(dominators));
|
|
808
|
+
}
|
|
809
|
+
structuredContent.heapSnapshotDominators = dominators;
|
|
810
|
+
}
|
|
704
811
|
}
|
|
705
812
|
if (data.detailedNetworkRequest) {
|
|
706
813
|
response.push(data.detailedNetworkRequest.toStringDetailed());
|
|
@@ -728,18 +835,11 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
728
835
|
response.push(extensionsMessage);
|
|
729
836
|
}
|
|
730
837
|
}
|
|
731
|
-
if (data.thirdPartyDeveloperTools
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
data.thirdPartyDeveloperTools;
|
|
735
|
-
}
|
|
838
|
+
if (data.thirdPartyDeveloperTools.length) {
|
|
839
|
+
structuredContent.thirdPartyDeveloperTools =
|
|
840
|
+
data.thirdPartyDeveloperTools;
|
|
736
841
|
response.push('## Third-party developer tools');
|
|
737
|
-
|
|
738
|
-
!data.thirdPartyDeveloperTools?.tools) {
|
|
739
|
-
response.push('No third-party developer tools available.');
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
const toolGroup = data.thirdPartyDeveloperTools;
|
|
842
|
+
for (const toolGroup of data.thirdPartyDeveloperTools) {
|
|
743
843
|
response.push(`${toolGroup.name}: ${toolGroup.description}`);
|
|
744
844
|
response.push('Available tools:');
|
|
745
845
|
const toolDefinitionsMessage = toolGroup.tools
|
|
@@ -778,11 +878,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
778
878
|
structuredContent.pagination = paginationData.pagination;
|
|
779
879
|
response.push(...paginationData.info);
|
|
780
880
|
if (data.networkRequests) {
|
|
781
|
-
structuredContent.networkRequests =
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
881
|
+
structuredContent.networkRequests = paginationData.items.map(i => i.toJSON());
|
|
882
|
+
response.push(...(useToon
|
|
883
|
+
? [toonEncode(structuredContent.networkRequests)]
|
|
884
|
+
: paginationData.items.map(i => i.toString())));
|
|
786
885
|
}
|
|
787
886
|
}
|
|
788
887
|
else {
|
|
@@ -796,9 +895,14 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
796
895
|
const grouped = ConsoleFormatter.groupConsecutive(messages);
|
|
797
896
|
const paginationData = this.#dataWithPagination(grouped, this.#consoleDataOptions.pagination);
|
|
798
897
|
structuredContent.pagination = paginationData.pagination;
|
|
799
|
-
response.push(...paginationData.info);
|
|
800
|
-
response.push(...paginationData.items.map(item => item.toString()));
|
|
801
898
|
structuredContent.consoleMessages = paginationData.items.map(item => item.toJSON());
|
|
899
|
+
response.push(...paginationData.info);
|
|
900
|
+
if (useToon) {
|
|
901
|
+
response.push(toonEncode(structuredContent.consoleMessages));
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
response.push(...paginationData.items.map(item => item.toString()));
|
|
905
|
+
}
|
|
802
906
|
}
|
|
803
907
|
else {
|
|
804
908
|
response.push('<no console messages found>');
|
|
@@ -857,11 +961,25 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
857
961
|
this.#textResponseLines = [];
|
|
858
962
|
}
|
|
859
963
|
}
|
|
860
|
-
function
|
|
964
|
+
function truncateTitle(title, maxLength = 50) {
|
|
965
|
+
if (title.length <= maxLength) {
|
|
966
|
+
return title;
|
|
967
|
+
}
|
|
968
|
+
return title.slice(0, maxLength - 3) + '...';
|
|
969
|
+
}
|
|
970
|
+
async function fetchPageTitle(page) {
|
|
971
|
+
return Promise.race([
|
|
972
|
+
page.title().catch(() => ''),
|
|
973
|
+
new Promise(resolve => setTimeout(() => resolve(''), 1000)),
|
|
974
|
+
]);
|
|
975
|
+
}
|
|
976
|
+
function createStructuredPage(page, context, rawTitle) {
|
|
861
977
|
const isolatedContextName = context.getIsolatedContextName(page);
|
|
978
|
+
const title = truncateTitle(rawTitle);
|
|
862
979
|
const entry = {
|
|
863
980
|
id: context.getPageId(page),
|
|
864
981
|
url: page.url(),
|
|
982
|
+
title,
|
|
865
983
|
selected: context.isPageSelected(page),
|
|
866
984
|
};
|
|
867
985
|
if (isolatedContextName) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { FakeIssuesManager } from './DevtoolsUtils.js';
|
|
6
|
+
import { FakeIssuesManager } from './devtools/DevtoolsUtils.js';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { DevTools } from './third_party/index.js';
|
|
9
9
|
import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
|
|
@@ -50,7 +50,7 @@ export class PageCollector {
|
|
|
50
50
|
this.addPage(page);
|
|
51
51
|
}
|
|
52
52
|
catch (err) {
|
|
53
|
-
logger('Error getting a page for a target onTargetCreated', err);
|
|
53
|
+
logger?.('Error getting a page for a target onTargetCreated', err);
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
#onTargetDestroyed = async (target) => {
|
|
@@ -62,7 +62,7 @@ export class PageCollector {
|
|
|
62
62
|
this.cleanupPageDestroyed(page);
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
65
|
-
logger('Error getting a page for a target onTargetDestroyed', err);
|
|
65
|
+
logger?.('Error getting a page for a target onTargetDestroyed', err);
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
addPage(page) {
|
|
@@ -136,10 +136,10 @@ export class PageCollector {
|
|
|
136
136
|
throw new Error('No requests found for selected page');
|
|
137
137
|
}
|
|
138
138
|
const item = this.find(page, item => item[stableIdSymbol] === stableId);
|
|
139
|
-
if (item) {
|
|
140
|
-
|
|
139
|
+
if (!item) {
|
|
140
|
+
throw new Error('Request not found for selected page');
|
|
141
141
|
}
|
|
142
|
-
|
|
142
|
+
return item;
|
|
143
143
|
}
|
|
144
144
|
find(page, filter) {
|
|
145
145
|
const navigations = this.storage.get(page);
|
|
@@ -241,7 +241,7 @@ class PageEventSubscriber {
|
|
|
241
241
|
// @ts-expect-error Protocol types diverge.
|
|
242
242
|
inspectorIssue)[0];
|
|
243
243
|
if (!issue) {
|
|
244
|
-
logger('No issue mapping for for the issue: ', inspectorIssue.code);
|
|
244
|
+
logger?.('No issue mapping for for the issue: ', inspectorIssue.code);
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
247
|
const primaryKey = issue.primaryKey();
|
|
@@ -256,7 +256,7 @@ class PageEventSubscriber {
|
|
|
256
256
|
});
|
|
257
257
|
}
|
|
258
258
|
catch (error) {
|
|
259
|
-
logger('Error creating a new issue', error);
|
|
259
|
+
logger?.('Error creating a new issue', error);
|
|
260
260
|
}
|
|
261
261
|
};
|
|
262
262
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { UncaughtError } from './PageCollector.js';
|
|
7
|
+
import { createIdGenerator, stableIdSymbol } from './utils/id.js';
|
|
8
|
+
const CHROME_EXTENSION_PREFIX = 'chrome-extension://';
|
|
9
|
+
export class ServiceWorkerSubscriber {
|
|
10
|
+
#target;
|
|
11
|
+
#callback;
|
|
12
|
+
#session;
|
|
13
|
+
#worker;
|
|
14
|
+
constructor(target, callback) {
|
|
15
|
+
this.#target = target;
|
|
16
|
+
this.#callback = callback;
|
|
17
|
+
}
|
|
18
|
+
async subscribe() {
|
|
19
|
+
this.#session = await this.#target.createCDPSession();
|
|
20
|
+
await this.#session.send('Runtime.enable');
|
|
21
|
+
this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
22
|
+
this.#worker = (await this.#target.worker()) ?? undefined;
|
|
23
|
+
if (this.#worker) {
|
|
24
|
+
this.#worker.on('console', this.#onConsole);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async unsubscribe() {
|
|
28
|
+
if (this.#worker) {
|
|
29
|
+
this.#worker.off('console', this.#onConsole);
|
|
30
|
+
}
|
|
31
|
+
await this.#session?.detach();
|
|
32
|
+
}
|
|
33
|
+
#onConsole = (message) => {
|
|
34
|
+
this.#callback(message);
|
|
35
|
+
};
|
|
36
|
+
#onExceptionThrown = (event) => {
|
|
37
|
+
const url = this.#target.url();
|
|
38
|
+
const extensionId = extractExtensionId(url);
|
|
39
|
+
if (extensionId) {
|
|
40
|
+
this.#callback(new UncaughtError(event.exceptionDetails, extensionId));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export class ServiceWorkerConsoleCollector {
|
|
45
|
+
#storage = new Map();
|
|
46
|
+
#maxLogs;
|
|
47
|
+
#browser;
|
|
48
|
+
#serviceWorkerSubscribers = new Map();
|
|
49
|
+
#idGenerator = createIdGenerator();
|
|
50
|
+
constructor(browser, maxLogs = 1000) {
|
|
51
|
+
this.#browser = browser;
|
|
52
|
+
this.#maxLogs = maxLogs;
|
|
53
|
+
}
|
|
54
|
+
async init(workers) {
|
|
55
|
+
if (!this.#browser) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.#browser.on('targetcreated', this.#onTargetCreated);
|
|
59
|
+
this.#browser.on('targetdestroyed', this.#onTargetDestroyed);
|
|
60
|
+
for (const worker of workers) {
|
|
61
|
+
void this.#onTargetCreated(worker.target);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
dispose() {
|
|
65
|
+
if (!this.#browser) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.#browser.off('targetcreated', this.#onTargetCreated);
|
|
69
|
+
this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
|
|
70
|
+
for (const subscriber of this.#serviceWorkerSubscribers.values()) {
|
|
71
|
+
subscriber.unsubscribe().catch(err => {
|
|
72
|
+
if (err instanceof Error &&
|
|
73
|
+
!err.message.includes('Target closed') &&
|
|
74
|
+
!err.message.includes('Session closed')) {
|
|
75
|
+
// Swallow error as we are tearing down the system
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
this.#serviceWorkerSubscribers.clear();
|
|
80
|
+
}
|
|
81
|
+
#onTargetCreated = async (target) => {
|
|
82
|
+
if (this.#serviceWorkerSubscribers.has(target)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const origin = target.url();
|
|
86
|
+
if (target.type() === 'service_worker' && isExtensionOrigin(origin)) {
|
|
87
|
+
const extensionId = extractExtensionId(origin);
|
|
88
|
+
if (!extensionId) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const subscriber = new ServiceWorkerSubscriber(target, item => {
|
|
92
|
+
this.addLog(extensionId, item);
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
await subscriber.subscribe();
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (err instanceof Error &&
|
|
99
|
+
!err.message.includes('Target closed') &&
|
|
100
|
+
!err.message.includes('Session closed')) {
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this.#serviceWorkerSubscribers.set(target, subscriber);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
#onTargetDestroyed = async (target) => {
|
|
108
|
+
const subscriber = this.#serviceWorkerSubscribers.get(target);
|
|
109
|
+
if (subscriber) {
|
|
110
|
+
try {
|
|
111
|
+
await subscriber.unsubscribe();
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err instanceof Error &&
|
|
115
|
+
!err.message.includes('Target closed') &&
|
|
116
|
+
!err.message.includes('Session closed')) {
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.#serviceWorkerSubscribers.delete(target);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
addLog(extensionId, log) {
|
|
124
|
+
const logs = this.#storage.get(extensionId) ?? [];
|
|
125
|
+
const withId = log;
|
|
126
|
+
withId[stableIdSymbol] = this.#idGenerator();
|
|
127
|
+
logs.push(withId);
|
|
128
|
+
if (logs.length > this.#maxLogs) {
|
|
129
|
+
logs.shift();
|
|
130
|
+
}
|
|
131
|
+
this.#storage.set(extensionId, logs);
|
|
132
|
+
}
|
|
133
|
+
getData(extensionId) {
|
|
134
|
+
return this.#storage.get(extensionId) ?? [];
|
|
135
|
+
}
|
|
136
|
+
getById(extensionId, stableId) {
|
|
137
|
+
const logs = this.#storage.get(extensionId);
|
|
138
|
+
if (!logs) {
|
|
139
|
+
throw new Error('No logs found for selected extension');
|
|
140
|
+
}
|
|
141
|
+
const item = logs.find(item => item[stableIdSymbol] === stableId);
|
|
142
|
+
if (item) {
|
|
143
|
+
return item;
|
|
144
|
+
}
|
|
145
|
+
throw new Error('Log not found for selected extension');
|
|
146
|
+
}
|
|
147
|
+
find(extensionId, filter) {
|
|
148
|
+
const logs = this.#storage.get(extensionId);
|
|
149
|
+
if (!logs) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return logs.find(filter);
|
|
153
|
+
}
|
|
154
|
+
clearLogs(extensionId) {
|
|
155
|
+
this.#storage.delete(extensionId);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function extractExtensionId(origin) {
|
|
159
|
+
if (!origin || !isExtensionOrigin(origin)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const pathPart = origin.substring(CHROME_EXTENSION_PREFIX.length);
|
|
163
|
+
const slashIndex = pathPart.indexOf('/');
|
|
164
|
+
// if there's no / it means that pathPart is now the extensionId, otherwise
|
|
165
|
+
// we take everything until the first /
|
|
166
|
+
return slashIndex === -1 ? pathPart : pathPart.substring(0, slashIndex);
|
|
167
|
+
}
|
|
168
|
+
function isExtensionOrigin(origin) {
|
|
169
|
+
return origin.startsWith(CHROME_EXTENSION_PREFIX);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=ServiceWorkerCollector.js.map
|
|
@@ -186,7 +186,7 @@ export class TextSnapshot {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
catch (e) {
|
|
189
|
-
logger(`Failed to collect descendants for backend node ${backendNodeId}`, e);
|
|
189
|
+
logger?.(`Failed to collect descendants for backend node ${backendNodeId}`, e);
|
|
190
190
|
}
|
|
191
191
|
return descendantIds;
|
|
192
192
|
};
|