豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 8e2380b

Browse files
authored
fix: console formatter hides frames from ignored scripts (ChromeDevTools#927)
This PR wires up the default DevTools ignore listing to hide frames from third party scripts in stack traces. By default, we ignore: * Scripts with `/node_modules/` in their URL or where the URL starts with `node:/` * Scripts that are marked as ignore listed in the source map * Content scripts from extensions The PR adds both, unit tests and an e2e test. This is because we allow the unit test to pass a mocked ignore checker, where-as the e2e uses the real DevTools one.
1 parent caea23a commit 8e2380b

File tree

5 files changed

+226
-3
lines changed

5 files changed

+226
-3
lines changed

src/formatters/ConsoleFormatter.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
SymbolizedError,
1111
} from '../DevtoolsUtils.js';
1212
import {UncaughtError} from '../PageCollector.js';
13-
import type * as DevTools from '../third_party/index.js';
13+
import * as DevTools from '../third_party/index.js';
1414
import type {ConsoleMessage} from '../third_party/index.js';
1515

1616
export interface ConsoleFormatterOptions {
@@ -20,8 +20,13 @@ export interface ConsoleFormatterOptions {
2020
resolvedArgsForTesting?: unknown[];
2121
resolvedStackTraceForTesting?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
2222
resolvedCauseForTesting?: SymbolizedError;
23+
isIgnoredForTesting?: IgnoreCheck;
2324
}
2425

26+
export type IgnoreCheck = (
27+
frame: DevTools.DevTools.StackTrace.StackTrace.Frame,
28+
) => boolean;
29+
2530
export class ConsoleFormatter {
2631
static readonly #STACK_TRACE_MAX_LINES = 50;
2732

@@ -35,6 +40,8 @@ export class ConsoleFormatter {
3540
readonly #stack?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
3641
readonly #cause?: SymbolizedError;
3742

43+
readonly #isIgnored: IgnoreCheck;
44+
3845
private constructor(params: {
3946
id: number;
4047
type: string;
@@ -43,6 +50,7 @@ export class ConsoleFormatter {
4350
resolvedArgs?: unknown[];
4451
stack?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
4552
cause?: SymbolizedError;
53+
isIgnored: IgnoreCheck;
4654
}) {
4755
this.#id = params.id;
4856
this.#type = params.type;
@@ -51,12 +59,37 @@ export class ConsoleFormatter {
5159
this.#resolvedArgs = params.resolvedArgs ?? [];
5260
this.#stack = params.stack;
5361
this.#cause = params.cause;
62+
this.#isIgnored = params.isIgnored;
5463
}
5564

5665
static async from(
5766
msg: ConsoleMessage | UncaughtError,
5867
options: ConsoleFormatterOptions,
5968
): Promise<ConsoleFormatter> {
69+
const ignoreListManager = options?.devTools?.universe.context.get(
70+
DevTools.DevTools.IgnoreListManager,
71+
);
72+
const isIgnored: IgnoreCheck =
73+
options.isIgnoredForTesting ||
74+
(frame => {
75+
if (!ignoreListManager) {
76+
return false;
77+
}
78+
if (frame.uiSourceCode) {
79+
return ignoreListManager.isUserOrSourceMapIgnoreListedUISourceCode(
80+
frame.uiSourceCode,
81+
);
82+
}
83+
if (frame.url) {
84+
return ignoreListManager.isUserIgnoreListedURL(
85+
frame.url as Parameters<
86+
DevTools.DevTools.IgnoreListManager['isUserIgnoreListedURL']
87+
>[0],
88+
);
89+
}
90+
return false;
91+
});
92+
6093
if (msg instanceof UncaughtError) {
6194
const error = await SymbolizedError.fromDetails({
6295
devTools: options?.devTools,
@@ -72,6 +105,7 @@ export class ConsoleFormatter {
72105
text: error.message,
73106
stack: error.stackTrace,
74107
cause: error.cause,
108+
isIgnored,
75109
});
76110
}
77111

@@ -120,6 +154,7 @@ export class ConsoleFormatter {
120154
argCount: resolvedArgs.length || msg.args().length,
121155
resolvedArgs,
122156
stack,
157+
isIgnored,
123158
});
124159
}
125160

@@ -229,16 +264,22 @@ export class ConsoleFormatter {
229264
#formatFragment(
230265
fragment: DevTools.DevTools.StackTrace.StackTrace.Fragment,
231266
): string[] {
232-
return fragment.frames.map(this.#formatFrame.bind(this));
267+
const frames = fragment.frames.filter(frame => !this.#isIgnored(frame));
268+
return frames.map(this.#formatFrame.bind(this));
233269
}
234270

235271
#formatAsyncFragment(
236272
fragment: DevTools.DevTools.StackTrace.StackTrace.AsyncFragment,
237273
): string[] {
274+
const formattedFrames = this.#formatFragment(fragment);
275+
if (formattedFrames.length === 0) {
276+
return [];
277+
}
278+
238279
const separatorLineLength = 40;
239280
const prefix = `--- ${fragment.description || 'async'} `;
240281
const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
241-
return [separator, ...this.#formatFragment(fragment)];
282+
return [separator, ...formattedFrames];
242283
}
243284

244285
#formatFrame(frame: DevTools.DevTools.StackTrace.StackTrace.Frame): string {

tests/formatters/ConsoleFormatter.test.js.snapshot

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ exports[`ConsoleFormatter > toString > formats an UncaughtError 1`] = `
1414
msgid=4 [error] Uncaught TypeError: Cannot read properties of undefined (0 args)
1515
`;
1616

17+
exports[`ConsoleFormatter > toStringDetailed > does not show call frames with ignore listed scripts 1`] = `
18+
ID: 12
19+
Message: log> Hello stack trace!
20+
### Stack trace
21+
at foo (foo.ts:10:2)
22+
at bar (foo.ts:20:2)
23+
Note: line and column numbers use 1-based indexing
24+
`;
25+
26+
exports[`ConsoleFormatter > toStringDetailed > does not show fragments where all frames are ignore listed 1`] = `
27+
ID: 13
28+
Message: log> Hello stack trace!
29+
### Stack trace
30+
at foo (foo.ts:10:2)
31+
--- await ------------------------------
32+
at bar (foo.ts:20:2)
33+
Note: line and column numbers use 1-based indexing
34+
`;
35+
1736
exports[`ConsoleFormatter > toStringDetailed > formats a console message with a stack trace 1`] = `
1837
ID: 5
1938
Message: log> Hello stack trace!

tests/formatters/ConsoleFormatter.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,113 @@ describe('ConsoleFormatter', () => {
479479
).toStringDetailed();
480480
t.assert.snapshot?.(result);
481481
});
482+
483+
it('does not show call frames with ignore listed scripts', async t => {
484+
const message = createMockMessage({
485+
type: () => 'log',
486+
text: () => 'Hello stack trace!',
487+
});
488+
const stackTrace = {
489+
syncFragment: {
490+
frames: [
491+
{
492+
line: 10,
493+
column: 2,
494+
url: 'foo.ts',
495+
name: 'foo',
496+
},
497+
{
498+
line: 200,
499+
column: 46,
500+
url: './node_modules/some-third-party-package/lib/index.js',
501+
name: 'doThings',
502+
},
503+
{
504+
line: 250,
505+
column: 12,
506+
url: './node_modules/some-third-party-package/lib/index.js',
507+
name: 'doThings2',
508+
},
509+
{
510+
line: 20,
511+
column: 2,
512+
url: 'foo.ts',
513+
name: 'bar',
514+
},
515+
],
516+
},
517+
asyncFragments: [],
518+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
519+
520+
const result = (
521+
await ConsoleFormatter.from(message, {
522+
id: 12,
523+
resolvedStackTraceForTesting: stackTrace,
524+
isIgnoredForTesting: frame =>
525+
Boolean(frame.url?.includes('node_modules')),
526+
})
527+
).toStringDetailed();
528+
t.assert.snapshot?.(result);
529+
});
530+
531+
it('does not show fragments where all frames are ignore listed', async t => {
532+
const message = createMockMessage({
533+
type: () => 'log',
534+
text: () => 'Hello stack trace!',
535+
});
536+
const stackTrace = {
537+
syncFragment: {
538+
frames: [
539+
{
540+
line: 10,
541+
column: 2,
542+
url: 'foo.ts',
543+
name: 'foo',
544+
},
545+
],
546+
},
547+
asyncFragments: [
548+
{
549+
description: 'setTimeout',
550+
frames: [
551+
{
552+
line: 200,
553+
column: 46,
554+
url: './node_modules/some-third-party-package/lib/index.js',
555+
name: 'doThings',
556+
},
557+
{
558+
line: 250,
559+
column: 12,
560+
url: './node_modules/some-third-party-package/lib/index.js',
561+
name: 'doThings2',
562+
},
563+
],
564+
},
565+
{
566+
description: 'await',
567+
frames: [
568+
{
569+
line: 20,
570+
column: 2,
571+
url: 'foo.ts',
572+
name: 'bar',
573+
},
574+
],
575+
},
576+
],
577+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
578+
579+
const result = (
580+
await ConsoleFormatter.from(message, {
581+
id: 13,
582+
resolvedStackTraceForTesting: stackTrace,
583+
isIgnoredForTesting: frame =>
584+
Boolean(frame.url?.includes('node_modules')),
585+
})
586+
).toStringDetailed();
587+
t.assert.snapshot?.(result);
588+
});
482589
});
483590
describe('toJSON', () => {
484591
it('formats a console.log message', async () => {

tests/tools/console.test.js.snapshot

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ at <anonymous> (main.js:14:1)
8383
Note: line and column numbers use 1-based indexing
8484
`;
8585

86+
exports[`console > get_console_message > ignores frames from ignore listed URLs 1`] = `
87+
# test response
88+
ID: 1
89+
Message: log> hello from callback
90+
### Arguments
91+
Arg #0: hello from callback
92+
### Stack trace
93+
at callback ('main.js':3:21)
94+
at callIt ('main.js':7:13)
95+
at ('main.js':8:13)
96+
Note: line and column numbers use 1-based indexing
97+
`;
98+
8699
exports[`console > get_console_message > issues type > gets issue details with node id parsing 1`] = `
87100
# test response
88101
ID: 1

tests/tools/console.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,5 +386,48 @@ describe('console', () => {
386386
t.assert.snapshot?.(rawText);
387387
});
388388
});
389+
390+
it('ignores frames from ignore listed URLs', async t => {
391+
server.addHtmlRoute(
392+
'/index.html',
393+
`<!DOCTYPE html>
394+
<script>
395+
function ignoredFn1(cb) {
396+
ignoredFn2(cb);
397+
}
398+
399+
function ignoredFn2(cb) {
400+
cb();
401+
}
402+
//# sourceURL=./node_modules/foo.js
403+
</script>
404+
<script>
405+
function callback() {
406+
console.log('hello from callback');
407+
}
408+
409+
(function callIt() {
410+
ignoredFn1(callback);
411+
})();
412+
//# sourceURL='main.js'
413+
</script>
414+
`,
415+
);
416+
417+
await withMcpContext(async (response, context) => {
418+
const page = await context.newPage();
419+
await page.goto(server.getRoute('/index.html'));
420+
421+
await getConsoleMessage.handler(
422+
{params: {msgid: 1}},
423+
response,
424+
context,
425+
);
426+
const formattedResponse = await response.handle('test', context);
427+
const rawText = getTextContent(formattedResponse.content[0]);
428+
429+
t.assert.snapshot?.(rawText);
430+
});
431+
});
389432
});
390433
});

0 commit comments

Comments
 (0)