chrome-devtools-mcp 0.15.1 → 0.17.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 +13 -5
- package/build/src/DevtoolsUtils.js +120 -5
- package/build/src/McpContext.js +5 -9
- package/build/src/McpResponse.js +4 -10
- package/build/src/PageCollector.js +18 -2
- package/build/src/cli.js +9 -0
- package/build/src/formatters/ConsoleFormatter.js +155 -68
- package/build/src/main.js +5 -1
- package/build/src/third_party/THIRD_PARTY_NOTICES +4 -4
- package/build/src/third_party/bundled-packages.json +3 -3
- package/build/src/third_party/devtools-formatter-worker.js +0 -2
- package/build/src/third_party/index.js +512 -295
- package/build/src/tools/input.js +0 -1
- package/build/src/tools/performance.js +31 -1
- package/build/src/tools/script.js +2 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Chrome DevTools for reliable automation, in-depth debugging, and performance ana
|
|
|
15
15
|
DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
|
|
16
16
|
traces and extract actionable performance insights.
|
|
17
17
|
- **Advanced browser debugging**: Analyze network requests, take screenshots and
|
|
18
|
-
check
|
|
18
|
+
check browser console messages (with source-mapped stack traces).
|
|
19
19
|
- **Reliable automation**. Uses
|
|
20
20
|
[puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
|
|
21
21
|
Chrome and automatically wait for action results.
|
|
@@ -27,6 +27,12 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
|
|
|
27
27
|
Avoid sharing sensitive or personal information that you don't want to share with
|
|
28
28
|
MCP clients.
|
|
29
29
|
|
|
30
|
+
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
|
|
31
|
+
experience data. This helps provide a holistic performance picture by
|
|
32
|
+
presenting field data alongside lab data. This data is collected by the [Chrome
|
|
33
|
+
User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
|
|
34
|
+
this, run with the `--no-performance-crux` flag.
|
|
35
|
+
|
|
30
36
|
## **Usage statistics**
|
|
31
37
|
|
|
32
38
|
Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
|
|
@@ -466,6 +472,11 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
466
472
|
- **Type:** boolean
|
|
467
473
|
- **Default:** `true`
|
|
468
474
|
|
|
475
|
+
- **`--performanceCrux`/ `--performance-crux`**
|
|
476
|
+
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
|
|
477
|
+
- **Type:** boolean
|
|
478
|
+
- **Default:** `true`
|
|
479
|
+
|
|
469
480
|
- **`--usageStatistics`/ `--usage-statistics`**
|
|
470
481
|
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
471
482
|
- **Type:** boolean
|
|
@@ -563,15 +574,12 @@ The following code snippet is an example configuration for gemini-cli:
|
|
|
563
574
|
"mcpServers": {
|
|
564
575
|
"chrome-devtools": {
|
|
565
576
|
"command": "npx",
|
|
566
|
-
"args": ["chrome-devtools-mcp@latest", "--autoConnect"
|
|
577
|
+
"args": ["chrome-devtools-mcp@latest", "--autoConnect"]
|
|
567
578
|
}
|
|
568
579
|
}
|
|
569
580
|
}
|
|
570
581
|
```
|
|
571
582
|
|
|
572
|
-
Note: you have to specify `--channel=beta` until Chrome M144 has reached the
|
|
573
|
-
stable channel.
|
|
574
|
-
|
|
575
583
|
**Step 3:** Test your setup
|
|
576
584
|
|
|
577
585
|
Make sure your browser is running. Open gemini-cli and run the following prompt:
|
|
@@ -169,16 +169,131 @@ const SKIP_ALL_PAUSES = {
|
|
|
169
169
|
// Do nothing.
|
|
170
170
|
},
|
|
171
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Constructed from Runtime.ExceptionDetails of an uncaught error.
|
|
174
|
+
*
|
|
175
|
+
* TODO: Also construct from a RemoteObject of subtype 'error'.
|
|
176
|
+
*
|
|
177
|
+
* Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
|
|
178
|
+
*/
|
|
179
|
+
export class SymbolizedError {
|
|
180
|
+
message;
|
|
181
|
+
stackTrace;
|
|
182
|
+
cause;
|
|
183
|
+
constructor(message, stackTrace, cause) {
|
|
184
|
+
this.message = message;
|
|
185
|
+
this.stackTrace = stackTrace;
|
|
186
|
+
this.cause = cause;
|
|
187
|
+
}
|
|
188
|
+
static async fromDetails(opts) {
|
|
189
|
+
const message = SymbolizedError.#getMessage(opts.details);
|
|
190
|
+
if (!opts.includeStackAndCause || !opts.devTools) {
|
|
191
|
+
return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
|
|
192
|
+
}
|
|
193
|
+
let stackTrace;
|
|
194
|
+
if (opts.resolvedStackTraceForTesting) {
|
|
195
|
+
stackTrace = opts.resolvedStackTraceForTesting;
|
|
196
|
+
}
|
|
197
|
+
else if (opts.details.stackTrace) {
|
|
198
|
+
try {
|
|
199
|
+
stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
|
|
206
|
+
// If its an Error, recursively create a SymbolizedError.
|
|
207
|
+
let cause;
|
|
208
|
+
if (opts.resolvedCauseForTesting) {
|
|
209
|
+
cause = opts.resolvedCauseForTesting;
|
|
210
|
+
}
|
|
211
|
+
else if (opts.details.exception) {
|
|
212
|
+
try {
|
|
213
|
+
const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
|
|
214
|
+
if (causeRemoteObj) {
|
|
215
|
+
cause = await SymbolizedError.fromError({
|
|
216
|
+
devTools: opts.devTools,
|
|
217
|
+
error: causeRemoteObj,
|
|
218
|
+
targetId: opts.targetId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Ignore
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
227
|
+
}
|
|
228
|
+
static async fromError(opts) {
|
|
229
|
+
const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
|
|
230
|
+
if (details) {
|
|
231
|
+
return SymbolizedError.fromDetails({
|
|
232
|
+
details,
|
|
233
|
+
devTools: opts.devTools,
|
|
234
|
+
targetId: opts.targetId,
|
|
235
|
+
includeStackAndCause: true,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
|
|
239
|
+
}
|
|
240
|
+
static #getMessage(details) {
|
|
241
|
+
// For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
|
|
242
|
+
// we have to manually parse out the error text from the exception description.
|
|
243
|
+
// In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
|
|
244
|
+
if (details.text === 'Uncaught' && details.exception) {
|
|
245
|
+
return ('Uncaught ' +
|
|
246
|
+
SymbolizedError.#getMessageFromException(details.exception));
|
|
247
|
+
}
|
|
248
|
+
return details.text;
|
|
249
|
+
}
|
|
250
|
+
static #getMessageFromException(error) {
|
|
251
|
+
const messageWithRest = error.description?.split('\n at ', 2) ?? [];
|
|
252
|
+
return messageWithRest[0] ?? '';
|
|
253
|
+
}
|
|
254
|
+
static async #getExceptionDetails(devTools, error, targetId) {
|
|
255
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
259
|
+
const target = targetId
|
|
260
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
261
|
+
: devTools.target;
|
|
262
|
+
const model = target.model(DevTools.RuntimeModel);
|
|
263
|
+
return ((await model.getExceptionDetails(error.objectId)) ?? null);
|
|
264
|
+
}
|
|
265
|
+
static async #lookupCause(devTools, error, targetId) {
|
|
266
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
270
|
+
const target = targetId
|
|
271
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
272
|
+
: devTools.target;
|
|
273
|
+
const properties = await target.runtimeAgent().invoke_getProperties({
|
|
274
|
+
objectId: error.objectId,
|
|
275
|
+
});
|
|
276
|
+
if (properties.getError()) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
|
|
280
|
+
}
|
|
281
|
+
static createForTesting(message, stackTrace, cause) {
|
|
282
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
172
285
|
export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
|
|
173
286
|
const message = consoleMessage;
|
|
174
287
|
const rawStackTrace = message._rawStackTrace();
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
288
|
+
if (rawStackTrace) {
|
|
289
|
+
return createStackTrace(devTools, rawStackTrace, message._targetId());
|
|
177
290
|
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
export async function createStackTrace(devTools, rawStackTrace, targetId) {
|
|
178
294
|
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
? targetManager.targetById(messageTargetId) || devTools.target
|
|
295
|
+
const target = targetId
|
|
296
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
182
297
|
: devTools.target;
|
|
183
298
|
const model = target.model(DevTools.DebuggerModel);
|
|
184
299
|
// DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
|
package/build/src/McpContext.js
CHANGED
|
@@ -80,15 +80,8 @@ export class McpContext {
|
|
|
80
80
|
console: event => {
|
|
81
81
|
collect(event);
|
|
82
82
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
collect(event);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
const error = new Error(`${event}`);
|
|
89
|
-
error.stack = undefined;
|
|
90
|
-
collect(error);
|
|
91
|
-
}
|
|
83
|
+
uncaughtError: event => {
|
|
84
|
+
collect(event);
|
|
92
85
|
},
|
|
93
86
|
issue: event => {
|
|
94
87
|
collect(event);
|
|
@@ -269,6 +262,9 @@ export class McpContext {
|
|
|
269
262
|
isRunningPerformanceTrace() {
|
|
270
263
|
return this.#isRunningTrace;
|
|
271
264
|
}
|
|
265
|
+
isCruxEnabled() {
|
|
266
|
+
return this.#options.performanceCrux;
|
|
267
|
+
}
|
|
272
268
|
getDialog() {
|
|
273
269
|
return this.#dialog;
|
|
274
270
|
}
|
package/build/src/McpResponse.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ConsoleFormatter } from './formatters/ConsoleFormatter.js';
|
|
|
7
7
|
import { IssueFormatter } from './formatters/IssueFormatter.js';
|
|
8
8
|
import { NetworkFormatter } from './formatters/NetworkFormatter.js';
|
|
9
9
|
import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
|
|
10
|
+
import { UncaughtError } from './PageCollector.js';
|
|
10
11
|
import { DevTools } from './third_party/index.js';
|
|
11
12
|
import { handleDialog } from './tools/pages.js';
|
|
12
13
|
import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
|
|
@@ -173,7 +174,7 @@ export class McpResponse {
|
|
|
173
174
|
if (this.#attachedConsoleMessageId) {
|
|
174
175
|
const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
|
|
175
176
|
const consoleMessageStableId = this.#attachedConsoleMessageId;
|
|
176
|
-
if ('args' in message) {
|
|
177
|
+
if ('args' in message || message instanceof UncaughtError) {
|
|
177
178
|
const consoleMessage = message;
|
|
178
179
|
const devTools = context.getDevToolsUniverse();
|
|
179
180
|
detailedConsoleMessage = await ConsoleFormatter.from(consoleMessage, {
|
|
@@ -193,11 +194,6 @@ export class McpResponse {
|
|
|
193
194
|
}
|
|
194
195
|
detailedConsoleMessage = formatter;
|
|
195
196
|
}
|
|
196
|
-
else {
|
|
197
|
-
detailedConsoleMessage = await ConsoleFormatter.from(message, {
|
|
198
|
-
id: consoleMessageStableId,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
197
|
}
|
|
202
198
|
let extensions;
|
|
203
199
|
if (this.#listExtensions) {
|
|
@@ -220,7 +216,7 @@ export class McpResponse {
|
|
|
220
216
|
}
|
|
221
217
|
consoleMessages = (await Promise.all(messages.map(async (item) => {
|
|
222
218
|
const consoleMessageStableId = context.getConsoleMessageStableId(item);
|
|
223
|
-
if ('args' in item) {
|
|
219
|
+
if ('args' in item || item instanceof UncaughtError) {
|
|
224
220
|
const consoleMessage = item;
|
|
225
221
|
const devTools = context.getDevToolsUniverse();
|
|
226
222
|
return await ConsoleFormatter.from(consoleMessage, {
|
|
@@ -238,9 +234,7 @@ export class McpResponse {
|
|
|
238
234
|
}
|
|
239
235
|
return formatter;
|
|
240
236
|
}
|
|
241
|
-
return
|
|
242
|
-
id: consoleMessageStableId,
|
|
243
|
-
});
|
|
237
|
+
return null;
|
|
244
238
|
}))).filter(item => item !== null);
|
|
245
239
|
}
|
|
246
240
|
let networkRequests;
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
import { FakeIssuesManager } from './DevtoolsUtils.js';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { DevTools } from './third_party/index.js';
|
|
9
|
+
export class UncaughtError {
|
|
10
|
+
details;
|
|
11
|
+
targetId;
|
|
12
|
+
constructor(details, targetId) {
|
|
13
|
+
this.details = details;
|
|
14
|
+
this.targetId = targetId;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
9
17
|
function createIdGenerator() {
|
|
10
18
|
let i = 1;
|
|
11
19
|
return () => {
|
|
@@ -161,7 +169,7 @@ export class ConsoleCollector extends PageCollector {
|
|
|
161
169
|
addPage(page) {
|
|
162
170
|
super.addPage(page);
|
|
163
171
|
if (!this.#subscribedPages.has(page)) {
|
|
164
|
-
const subscriber = new
|
|
172
|
+
const subscriber = new PageEventSubscriber(page);
|
|
165
173
|
this.#subscribedPages.set(page, subscriber);
|
|
166
174
|
void subscriber.subscribe();
|
|
167
175
|
}
|
|
@@ -172,17 +180,20 @@ export class ConsoleCollector extends PageCollector {
|
|
|
172
180
|
this.#subscribedPages.delete(page);
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
|
-
class
|
|
183
|
+
class PageEventSubscriber {
|
|
176
184
|
#issueManager = new FakeIssuesManager();
|
|
177
185
|
#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
|
|
178
186
|
#seenKeys = new Set();
|
|
179
187
|
#seenIssues = new Set();
|
|
180
188
|
#page;
|
|
181
189
|
#session;
|
|
190
|
+
#targetId;
|
|
182
191
|
constructor(page) {
|
|
183
192
|
this.#page = page;
|
|
184
193
|
// @ts-expect-error use existing CDP client (internal Puppeteer API).
|
|
185
194
|
this.#session = this.#page._client();
|
|
195
|
+
// @ts-expect-error use internal Puppeteer API to get target ID
|
|
196
|
+
this.#targetId = this.#session.target()._targetId;
|
|
186
197
|
}
|
|
187
198
|
#resetIssueAggregator() {
|
|
188
199
|
this.#issueManager = new FakeIssuesManager();
|
|
@@ -196,6 +207,7 @@ class PageIssueSubscriber {
|
|
|
196
207
|
this.#resetIssueAggregator();
|
|
197
208
|
this.#page.on('framenavigated', this.#onFrameNavigated);
|
|
198
209
|
this.#session.on('Audits.issueAdded', this.#onIssueAdded);
|
|
210
|
+
this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
199
211
|
try {
|
|
200
212
|
await this.#session.send('Audits.enable');
|
|
201
213
|
}
|
|
@@ -208,6 +220,7 @@ class PageIssueSubscriber {
|
|
|
208
220
|
this.#seenIssues.clear();
|
|
209
221
|
this.#page.off('framenavigated', this.#onFrameNavigated);
|
|
210
222
|
this.#session.off('Audits.issueAdded', this.#onIssueAdded);
|
|
223
|
+
this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
211
224
|
if (this.#issueAggregator) {
|
|
212
225
|
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
|
|
213
226
|
}
|
|
@@ -222,6 +235,9 @@ class PageIssueSubscriber {
|
|
|
222
235
|
this.#seenIssues.add(event.data);
|
|
223
236
|
this.#page.emit('issue', event.data);
|
|
224
237
|
};
|
|
238
|
+
#onExceptionThrown = (event) => {
|
|
239
|
+
this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
|
|
240
|
+
};
|
|
225
241
|
// On navigation, we reset issue aggregation.
|
|
226
242
|
#onFrameNavigated = (frame) => {
|
|
227
243
|
// Only split the storage on main frame navigation
|
package/build/src/cli.js
CHANGED
|
@@ -188,6 +188,11 @@ export const cliOptions = {
|
|
|
188
188
|
hidden: true,
|
|
189
189
|
describe: 'Set to false to exclude tools related to extensions.',
|
|
190
190
|
},
|
|
191
|
+
performanceCrux: {
|
|
192
|
+
type: 'boolean',
|
|
193
|
+
default: true,
|
|
194
|
+
describe: 'Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.',
|
|
195
|
+
},
|
|
191
196
|
usageStatistics: {
|
|
192
197
|
type: 'boolean',
|
|
193
198
|
default: true,
|
|
@@ -277,6 +282,10 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
277
282
|
'$0 --no-usage-statistics',
|
|
278
283
|
'Do not send usage statistics https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics.',
|
|
279
284
|
],
|
|
285
|
+
[
|
|
286
|
+
'$0 --no-performance-crux',
|
|
287
|
+
'Disable CrUX (field data) integration in performance tools.',
|
|
288
|
+
],
|
|
280
289
|
]);
|
|
281
290
|
return yargsInstance
|
|
282
291
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
|
@@ -3,85 +3,130 @@
|
|
|
3
3
|
* Copyright 2026 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
var _a;
|
|
7
|
+
import { createStackTraceForConsoleMessage, SymbolizedError, } from '../DevtoolsUtils.js';
|
|
8
|
+
import { UncaughtError } from '../PageCollector.js';
|
|
9
|
+
import * as DevTools from '../third_party/index.js';
|
|
7
10
|
export class ConsoleFormatter {
|
|
8
|
-
#
|
|
9
|
-
#resolvedArgs = [];
|
|
10
|
-
#resolvedStackTrace;
|
|
11
|
+
static #STACK_TRACE_MAX_LINES = 50;
|
|
11
12
|
#id;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
#type;
|
|
14
|
+
#text;
|
|
15
|
+
#argCount;
|
|
16
|
+
#resolvedArgs;
|
|
17
|
+
#stack;
|
|
18
|
+
#cause;
|
|
19
|
+
#isIgnored;
|
|
20
|
+
constructor(params) {
|
|
21
|
+
this.#id = params.id;
|
|
22
|
+
this.#type = params.type;
|
|
23
|
+
this.#text = params.text;
|
|
24
|
+
this.#argCount = params.argCount ?? 0;
|
|
25
|
+
this.#resolvedArgs = params.resolvedArgs ?? [];
|
|
26
|
+
this.#stack = params.stack;
|
|
27
|
+
this.#cause = params.cause;
|
|
28
|
+
this.#isIgnored = params.isIgnored;
|
|
16
29
|
}
|
|
17
30
|
static async from(msg, options) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
const ignoreListManager = options?.devTools?.universe.context.get(DevTools.DevTools.IgnoreListManager);
|
|
32
|
+
const isIgnored = options.isIgnoredForTesting ||
|
|
33
|
+
(frame => {
|
|
34
|
+
if (!ignoreListManager) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (frame.uiSourceCode) {
|
|
38
|
+
return ignoreListManager.isUserOrSourceMapIgnoreListedUISourceCode(frame.uiSourceCode);
|
|
39
|
+
}
|
|
40
|
+
if (frame.url) {
|
|
41
|
+
return ignoreListManager.isUserIgnoreListedURL(frame.url);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
if (msg instanceof UncaughtError) {
|
|
46
|
+
const error = await SymbolizedError.fromDetails({
|
|
47
|
+
devTools: options?.devTools,
|
|
48
|
+
details: msg.details,
|
|
49
|
+
targetId: msg.targetId,
|
|
50
|
+
includeStackAndCause: options?.fetchDetailedData,
|
|
51
|
+
resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
|
|
52
|
+
resolvedCauseForTesting: options?.resolvedCauseForTesting,
|
|
53
|
+
});
|
|
54
|
+
return new _a({
|
|
55
|
+
id: options.id,
|
|
56
|
+
type: 'error',
|
|
57
|
+
text: error.message,
|
|
58
|
+
stack: error.stackTrace,
|
|
59
|
+
cause: error.cause,
|
|
60
|
+
isIgnored,
|
|
61
|
+
});
|
|
21
62
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (this.#msg instanceof Error) {
|
|
26
|
-
return;
|
|
63
|
+
let resolvedArgs = [];
|
|
64
|
+
if (options.resolvedArgsForTesting) {
|
|
65
|
+
resolvedArgs = options.resolvedArgsForTesting;
|
|
27
66
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
67
|
+
else if (options.fetchDetailedData) {
|
|
68
|
+
resolvedArgs = await Promise.all(msg.args().map(async (arg, i) => {
|
|
69
|
+
try {
|
|
70
|
+
const remoteObject = arg.remoteObject();
|
|
71
|
+
if (remoteObject.type === 'object' &&
|
|
72
|
+
remoteObject.subtype === 'error') {
|
|
73
|
+
return await SymbolizedError.fromError({
|
|
74
|
+
devTools: options.devTools,
|
|
75
|
+
error: remoteObject,
|
|
76
|
+
// @ts-expect-error Internal ConsoleMessage API
|
|
77
|
+
targetId: msg._targetId(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return await arg.jsonValue();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return `<error: Argument ${i} is no longer available>`;
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
let stack;
|
|
88
|
+
if (options.resolvedStackTraceForTesting) {
|
|
89
|
+
stack = options.resolvedStackTraceForTesting;
|
|
90
|
+
}
|
|
91
|
+
else if (options.fetchDetailedData && options.devTools) {
|
|
37
92
|
try {
|
|
38
|
-
|
|
93
|
+
stack = await createStackTraceForConsoleMessage(options.devTools, msg);
|
|
39
94
|
}
|
|
40
95
|
catch {
|
|
41
96
|
// ignore
|
|
42
97
|
}
|
|
43
98
|
}
|
|
99
|
+
return new _a({
|
|
100
|
+
id: options.id,
|
|
101
|
+
type: msg.type(),
|
|
102
|
+
text: msg.text(),
|
|
103
|
+
argCount: resolvedArgs.length || msg.args().length,
|
|
104
|
+
resolvedArgs,
|
|
105
|
+
stack,
|
|
106
|
+
isIgnored,
|
|
107
|
+
});
|
|
44
108
|
}
|
|
45
109
|
// The short format for a console message.
|
|
46
110
|
toString() {
|
|
47
|
-
|
|
48
|
-
const text = this.#getText();
|
|
49
|
-
const argsCount = this.#msg instanceof Error
|
|
50
|
-
? 0
|
|
51
|
-
: this.#resolvedArgs.length || this.#msg.args().length;
|
|
52
|
-
const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
|
|
53
|
-
return `${idPart}[${type}] ${text} (${argsCount} args)`;
|
|
111
|
+
return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`;
|
|
54
112
|
}
|
|
55
113
|
// The verbose format for a console message, including all details.
|
|
56
114
|
toStringDetailed() {
|
|
57
115
|
const result = [
|
|
58
|
-
|
|
59
|
-
`Message: ${this.#
|
|
116
|
+
`ID: ${this.#id}`,
|
|
117
|
+
`Message: ${this.#type}> ${this.#text}`,
|
|
60
118
|
this.#formatArgs(),
|
|
61
|
-
this.#formatStackTrace(this.#
|
|
119
|
+
this.#formatStackTrace(this.#stack, this.#cause, {
|
|
120
|
+
includeHeading: true,
|
|
121
|
+
}),
|
|
62
122
|
].filter(line => !!line);
|
|
63
123
|
return result.join('\n');
|
|
64
124
|
}
|
|
65
|
-
#getType() {
|
|
66
|
-
if (this.#msg instanceof Error) {
|
|
67
|
-
return 'error';
|
|
68
|
-
}
|
|
69
|
-
return this.#msg.type();
|
|
70
|
-
}
|
|
71
|
-
#getText() {
|
|
72
|
-
if (this.#msg instanceof Error) {
|
|
73
|
-
return this.#msg.message;
|
|
74
|
-
}
|
|
75
|
-
return this.#msg.text();
|
|
76
|
-
}
|
|
77
125
|
#getArgs() {
|
|
78
|
-
if (this.#msg instanceof Error) {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
126
|
if (this.#resolvedArgs.length > 0) {
|
|
82
127
|
const args = [...this.#resolvedArgs];
|
|
83
128
|
// If there is no text, the first argument serves as text (see formatMessage).
|
|
84
|
-
if (!this.#
|
|
129
|
+
if (!this.#text) {
|
|
85
130
|
args.shift();
|
|
86
131
|
}
|
|
87
132
|
return args;
|
|
@@ -89,6 +134,16 @@ export class ConsoleFormatter {
|
|
|
89
134
|
return [];
|
|
90
135
|
}
|
|
91
136
|
#formatArg(arg) {
|
|
137
|
+
if (arg instanceof SymbolizedError) {
|
|
138
|
+
return [
|
|
139
|
+
arg.message,
|
|
140
|
+
this.#formatStackTrace(arg.stackTrace, arg.cause, {
|
|
141
|
+
includeHeading: false,
|
|
142
|
+
}),
|
|
143
|
+
]
|
|
144
|
+
.filter(line => !!line)
|
|
145
|
+
.join('\n');
|
|
146
|
+
}
|
|
92
147
|
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
|
|
93
148
|
}
|
|
94
149
|
#formatArgs() {
|
|
@@ -102,52 +157,84 @@ export class ConsoleFormatter {
|
|
|
102
157
|
}
|
|
103
158
|
return result.join('\n');
|
|
104
159
|
}
|
|
105
|
-
#formatStackTrace(stackTrace) {
|
|
160
|
+
#formatStackTrace(stackTrace, cause, opts) {
|
|
106
161
|
if (!stackTrace) {
|
|
107
162
|
return '';
|
|
108
163
|
}
|
|
164
|
+
const lines = this.#formatStackTraceInner(stackTrace, cause);
|
|
165
|
+
const includedLines = lines.slice(0, _a.#STACK_TRACE_MAX_LINES);
|
|
166
|
+
const reminderCount = lines.length - includedLines.length;
|
|
167
|
+
return [
|
|
168
|
+
opts.includeHeading ? '### Stack trace' : '',
|
|
169
|
+
...includedLines,
|
|
170
|
+
reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
|
|
171
|
+
'Note: line and column numbers use 1-based indexing',
|
|
172
|
+
]
|
|
173
|
+
.filter(line => !!line)
|
|
174
|
+
.join('\n');
|
|
175
|
+
}
|
|
176
|
+
#formatStackTraceInner(stackTrace, cause) {
|
|
177
|
+
if (!stackTrace) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
109
180
|
return [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
181
|
+
...this.#formatFragment(stackTrace.syncFragment),
|
|
182
|
+
...stackTrace.asyncFragments
|
|
183
|
+
.map(this.#formatAsyncFragment.bind(this))
|
|
184
|
+
.flat(),
|
|
185
|
+
...this.#formatCause(cause),
|
|
186
|
+
];
|
|
114
187
|
}
|
|
115
188
|
#formatFragment(fragment) {
|
|
116
|
-
|
|
189
|
+
const frames = fragment.frames.filter(frame => !this.#isIgnored(frame));
|
|
190
|
+
return frames.map(this.#formatFrame.bind(this));
|
|
117
191
|
}
|
|
118
192
|
#formatAsyncFragment(fragment) {
|
|
193
|
+
const formattedFrames = this.#formatFragment(fragment);
|
|
194
|
+
if (formattedFrames.length === 0) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
119
197
|
const separatorLineLength = 40;
|
|
120
198
|
const prefix = `--- ${fragment.description || 'async'} `;
|
|
121
199
|
const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
|
|
122
|
-
return separator
|
|
200
|
+
return [separator, ...formattedFrames];
|
|
123
201
|
}
|
|
124
202
|
#formatFrame(frame) {
|
|
125
203
|
let result = `at ${frame.name ?? '<anonymous>'}`;
|
|
126
204
|
if (frame.uiSourceCode) {
|
|
127
|
-
|
|
205
|
+
const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
|
|
206
|
+
result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`;
|
|
128
207
|
}
|
|
129
208
|
else if (frame.url) {
|
|
130
209
|
result += ` (${frame.url}:${frame.line}:${frame.column})`;
|
|
131
210
|
}
|
|
132
211
|
return result;
|
|
133
212
|
}
|
|
213
|
+
#formatCause(cause) {
|
|
214
|
+
if (!cause) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
return [
|
|
218
|
+
`Caused by: ${cause.message}`,
|
|
219
|
+
...this.#formatStackTraceInner(cause.stackTrace, cause.cause),
|
|
220
|
+
];
|
|
221
|
+
}
|
|
134
222
|
toJSON() {
|
|
135
223
|
return {
|
|
136
|
-
type: this.#
|
|
137
|
-
text: this.#
|
|
138
|
-
argsCount: this.#
|
|
139
|
-
? 0
|
|
140
|
-
: this.#resolvedArgs.length || this.#msg.args().length,
|
|
224
|
+
type: this.#type,
|
|
225
|
+
text: this.#text,
|
|
226
|
+
argsCount: this.#argCount,
|
|
141
227
|
id: this.#id,
|
|
142
228
|
};
|
|
143
229
|
}
|
|
144
230
|
toJSONDetailed() {
|
|
145
231
|
return {
|
|
146
232
|
id: this.#id,
|
|
147
|
-
type: this.#
|
|
148
|
-
text: this.#
|
|
233
|
+
type: this.#type,
|
|
234
|
+
text: this.#text,
|
|
149
235
|
args: this.#getArgs().map(arg => typeof arg === 'object' ? arg : String(arg)),
|
|
150
|
-
stackTrace: this.#
|
|
236
|
+
stackTrace: this.#stack,
|
|
151
237
|
};
|
|
152
238
|
}
|
|
153
239
|
}
|
|
240
|
+
_a = ConsoleFormatter;
|