Skip to content

Commit 2636063

Browse files
Eric LeeseDevtools-frontend LUCI CQ
authored andcommitted
Display anonymous frames as ignored if they call ignored code
Anonymous frames for JS builtins are displayed in error stack traces in the console tab. Because they don't link to any code we were not treated them as ignored, as they had no ignore listed link to base this judgement on. Now we treat them as ignored if they call ignored code. Bug: 382422292 Change-Id: Ide669e69e22060cb0779d06a71dff8dce9962f67 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6129131 Reviewed-by: Benedikt Meurer <[email protected]> Commit-Queue: Eric Leese <[email protected]>
1 parent 1c13aa8 commit 2636063

File tree

5 files changed

+119
-12
lines changed

5 files changed

+119
-12
lines changed

front_end/panels/console/ConsoleViewMessage.test.ts

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,23 @@ describeWithMockConnection('ConsoleViewMessage', () => {
204204
assert.isTrue(showLess.checkVisibility());
205205
}
206206

207-
function errorMessageForStack(stack: Protocol.Runtime.StackTrace) {
208-
return [
207+
function errorMessageForStack(stack: Protocol.Runtime.StackTrace, withBuiltinFrames?: boolean) {
208+
const lines = [
209209
'Error:',
210-
...(stack.callFrames.map(
211-
frame => ` at ${frame.functionName} (${frame.url}:${frame.lineNumber}:${frame.columnNumber})`)),
212-
].join('\n');
210+
...(stack.callFrames.flatMap(frame => {
211+
const line = ` at ${frame.functionName} (${frame.url}:${frame.lineNumber}:${frame.columnNumber})`;
212+
if (withBuiltinFrames) {
213+
return [line, ' at JSON.parse (<anonymous>)'];
214+
}
215+
return [line];
216+
})),
217+
];
218+
return lines.join('\n');
213219
}
214220

215221
function getCallFrames(element: HTMLElement): string[] {
216222
const results = [];
217-
for (const line of element.querySelectorAll('.formatted-stack-frame')) {
223+
for (const line of element.querySelectorAll('.formatted-stack-frame,.formatted-builtin-stack-frame')) {
218224
if (line.checkVisibility()) {
219225
results.push(line.textContent ?? 'Error: line was null or undefined');
220226
}
@@ -246,15 +252,16 @@ describeWithMockConnection('ConsoleViewMessage', () => {
246252
(showLess.querySelector('.link') as HTMLElement).click();
247253
}
248254

249-
async function createConsoleMessageWithIgnoreListing(ignoreListFn: (url: string) => Boolean): Promise<HTMLElement> {
255+
async function createConsoleMessageWithIgnoreListing(
256+
ignoreListFn: (url: string) => Boolean, withBuiltinFrames?: boolean): Promise<HTMLElement> {
250257
const target = createTarget();
251258
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
252259
const stackTrace = createStackTrace([
253260
'USER_ID::userNestedFunction::http://example.com/script.js::40::15',
254261
'USER_ID::userFunction::http://example.com/script.js::10::2',
255262
'APP_ID::entry::http://example.com/app.js::25::10',
256263
]);
257-
const stackTraceMessage = errorMessageForStack(stackTrace);
264+
const stackTraceMessage = errorMessageForStack(stackTrace, withBuiltinFrames);
258265
const messageDetails = {
259266
type: Protocol.Runtime.ConsoleAPICalledEventType.Error,
260267
stackTrace,
@@ -305,6 +312,20 @@ describeWithMockConnection('ConsoleViewMessage', () => {
305312
' at userNestedFunction (/script.js:40:15)\n',
306313
' at userFunction (/script.js:10:2)\n',
307314
];
315+
const EXPANDED_UNSTRUCTURED_WITH_BUILTIN = [
316+
' at userNestedFunction (/script.js:40:15)\n',
317+
' at JSON.parse (<anonymous>)\n',
318+
' at userFunction (/script.js:10:2)\n',
319+
' at JSON.parse (<anonymous>)\n',
320+
' at entry (/app.js:25:10)\n',
321+
' at JSON.parse (<anonymous>)',
322+
];
323+
const COLLAPSED_UNSTRUCTURED_WITH_BUILTIN = [
324+
' at userNestedFunction (/script.js:40:15)\n',
325+
' at JSON.parse (<anonymous>)\n',
326+
' at userFunction (/script.js:10:2)\n',
327+
' at JSON.parse (<anonymous>)\n',
328+
];
308329
const EXPANDED_STRUCTURED = [
309330
'\nuserNestedFunction @ example.com/script.js:41',
310331
'\nuserFunction @ example.com/script.js:11',
@@ -360,5 +381,51 @@ describeWithMockConnection('ConsoleViewMessage', () => {
360381
assert.deepEqual(getStructuredCallFrames(element), COLLAPSED_STRUCTURED);
361382
assert.deepEqual(getCallFrames(element), COLLAPSED_UNSTRUCTURED);
362383
});
384+
385+
it('shows everything with no links when nothing is ignore listed, including builtin frames', async () => {
386+
const element = await createConsoleMessageWithIgnoreListing(_ => false, true);
387+
assertNoLinks(element);
388+
assert.deepEqual(getCallFrames(element), EXPANDED_UNSTRUCTURED_WITH_BUILTIN);
389+
assert.deepEqual(getStructuredCallFrames(element), []);
390+
expandStructuredTrace(element);
391+
assertNoLinks(element);
392+
assert.deepEqual(getStructuredCallFrames(element), EXPANDED_STRUCTURED);
393+
});
394+
395+
it('shows everything with no links when everything is ignore listed, including builtin frames', async () => {
396+
const element = await createConsoleMessageWithIgnoreListing(_ => true, true);
397+
assertNoLinks(element);
398+
assert.deepEqual(getCallFrames(element), EXPANDED_UNSTRUCTURED_WITH_BUILTIN);
399+
assert.deepEqual(getStructuredCallFrames(element), []);
400+
expandStructuredTrace(element);
401+
assertNoLinks(element);
402+
assert.deepEqual(getStructuredCallFrames(element), EXPANDED_STRUCTURED);
403+
});
404+
405+
it('shows expandable list when something is ignore listed, collapsing builtin frames', async () => {
406+
const element = await createConsoleMessageWithIgnoreListing(url => url.includes('/app.js'), true);
407+
assertShowAllLink(element);
408+
assert.deepEqual(getStructuredCallFrames(element), []);
409+
assert.deepEqual(getCallFrames(element), COLLAPSED_UNSTRUCTURED_WITH_BUILTIN);
410+
expandIgnored(element);
411+
assertShowLessLink(element);
412+
assert.deepEqual(getCallFrames(element), EXPANDED_UNSTRUCTURED_WITH_BUILTIN);
413+
collapseIgnored(element);
414+
assertShowAllLink(element);
415+
416+
expandStructuredTrace(element);
417+
418+
assertShowAllLink(element);
419+
assert.deepEqual(getStructuredCallFrames(element), COLLAPSED_STRUCTURED);
420+
assert.deepEqual(getCallFrames(element), COLLAPSED_UNSTRUCTURED_WITH_BUILTIN);
421+
expandIgnored(element);
422+
assertShowLessLink(element);
423+
assert.deepEqual(getCallFrames(element), EXPANDED_UNSTRUCTURED_WITH_BUILTIN);
424+
assert.deepEqual(getStructuredCallFrames(element), EXPANDED_STRUCTURED);
425+
collapseIgnored(element);
426+
assertShowAllLink(element);
427+
assert.deepEqual(getStructuredCallFrames(element), COLLAPSED_STRUCTURED);
428+
assert.deepEqual(getCallFrames(element), COLLAPSED_UNSTRUCTURED_WITH_BUILTIN);
429+
});
363430
});
364431
});

front_end/panels/console/ConsoleViewMessage.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,7 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
17391739

17401740
for (let i = 0; i < linkInfos.length; ++i) {
17411741
const newline = i < linkInfos.length - 1 ? '\n' : '';
1742-
const {line, link} = linkInfos[i];
1742+
const {line, link, isCallFrame} = linkInfos[i];
17431743
// Syntax errors don't have a stack frame that points to the source position
17441744
// where the error occurred. We use the source location from the
17451745
// exceptionDetails and append it to the end of the message instead.
@@ -1754,11 +1754,17 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
17541754
formattedResult.append(newline);
17551755
continue;
17561756
}
1757-
if (!link) {
1757+
if (!isCallFrame) {
17581758
formattedResult.appendChild(this.linkifyStringAsFragment(`${line}${newline}`));
17591759
continue;
17601760
}
17611761
const formattedLine = document.createElement('span');
1762+
if (!link) {
1763+
formattedLine.appendChild(this.linkifyStringAsFragment(`${line}${newline}`));
1764+
formattedLine.classList.add('formatted-builtin-stack-frame');
1765+
formattedResult.appendChild(formattedLine);
1766+
continue;
1767+
}
17621768
const suffix = `${link.suffix}${newline}`;
17631769
formattedLine.appendChild(this.linkifyStringAsFragment(link.prefix));
17641770
const scriptLocationLink = this.linkifier.linkifyScriptLocation(

front_end/panels/console/ErrorStackParser.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,5 +320,25 @@ describe('ErrorStackParser', () => {
320320
assert.isUndefined(parsedFrames[2].link);
321321
assert.strictEqual(parsedFrames[3].link?.scriptId, sid('30'));
322322
});
323+
324+
it('combines builtin frames', () => {
325+
const parsedFrames = parseErrorStack(`Error: some error
326+
at foo (http://example.com/a.js:6:3)
327+
at Array.forEach (<anonymous>)
328+
at JSON.parse (<anonymous>)
329+
at bar (http://example.com/b.js:43:14)`);
330+
assert.exists(parsedFrames);
331+
332+
assert.isUndefined(parsedFrames[0].link);
333+
assert.isUndefined(parsedFrames[0].isCallFrame);
334+
assert.strictEqual(parsedFrames[1].link?.url, 'http://example.com/a.js' as Platform.DevToolsPath.UrlString);
335+
assert.isTrue(parsedFrames[1].isCallFrame);
336+
assert.isUndefined(parsedFrames[2].link);
337+
assert.isTrue(parsedFrames[2].isCallFrame);
338+
assert.strictEqual(
339+
parsedFrames[2].line, ' at Array.forEach (<anonymous>)\n at JSON.parse (<anonymous>)');
340+
assert.strictEqual(parsedFrames[3].link?.url, 'http://example.com/b.js' as Platform.DevToolsPath.UrlString);
341+
assert.isTrue(parsedFrames[3].isCallFrame);
342+
});
323343
});
324344
});

front_end/panels/console/ErrorStackParser.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type * as Protocol from '../../generated/protocol.js';
99

1010
export interface ParsedErrorFrame {
1111
line: string;
12+
isCallFrame?: boolean;
1213
link?: {
1314
url: Platform.DevToolsPath.UrlString,
1415
prefix: string,
@@ -41,13 +42,14 @@ export function parseSourcePositionsFromErrorStack(
4142
for (const line of lines) {
4243
const match = /^\s*at\s(async\s)?/.exec(line);
4344
if (!match) {
44-
if (linkInfos.length && linkInfos[linkInfos.length - 1].link) {
45+
if (linkInfos.length && linkInfos[linkInfos.length - 1].isCallFrame) {
4546
return null;
4647
}
4748
linkInfos.push({line});
4849
continue;
4950
}
5051

52+
const isCallFrame = true;
5153
let left = match[0].length;
5254
let right = line.length;
5355
let enclosedInBraces = false;
@@ -74,7 +76,12 @@ export function parseSourcePositionsFromErrorStack(
7476
const linkCandidate = line.substring(left, right);
7577
const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(linkCandidate);
7678
if (splitResult.url === '<anonymous>') {
77-
linkInfos.push({line});
79+
if (linkInfos.length && linkInfos[linkInfos.length - 1].isCallFrame && !linkInfos[linkInfos.length - 1].link) {
80+
// Combine builtin frames.
81+
linkInfos[linkInfos.length - 1].line += `\n${line}`;
82+
} else {
83+
linkInfos.push({line, isCallFrame});
84+
}
7885
continue;
7986
}
8087
let url = parseOrScriptMatch(debuggerModel, splitResult.url);
@@ -87,6 +94,7 @@ export function parseSourcePositionsFromErrorStack(
8794

8895
linkInfos.push({
8996
line,
97+
isCallFrame,
9098
link: {
9199
url,
92100
prefix: line.substring(0, left),

front_end/panels/console/consoleView.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,12 @@
389389
.formatted-stack-frame:has(.ignore-list-link) {
390390
display: var(--display-ignored-formatted-stack-frame);
391391
opacity: 60%;
392+
393+
/* Subsequent builtin stack frames are also treated as ignored */
394+
& + .formatted-builtin-stack-frame {
395+
display: var(--display-ignored-formatted-stack-frame);
396+
opacity: 60%;
397+
}
392398
}
393399
}
394400

0 commit comments

Comments
 (0)