Skip to content

Commit 7095032

Browse files
and-oliDevtools-frontend LUCI CQ
authored andcommitted
[RPP] Show full stack traces in the details of JS calls (ProfileCalls)
Using the output of the StackTraceForEvent helper and the JSPresentationUtils. Screenshot: https://screenshot.googleplex.com/isqAP3JfX4jSAPM Bug: 381392952 Change-Id: If2cbdf3f5243b347a8bb56cb09613441d73f7274 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6074373 Reviewed-by: Jack Franklin <[email protected]> Commit-Queue: Andres Olivares <[email protected]>
1 parent 786bd42 commit 7095032

File tree

3 files changed

+92
-56
lines changed

3 files changed

+92
-56
lines changed

front_end/models/trace/extras/StackTraceForEvent.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ describeWithEnvironment('StackTraceForTraceEvent', function() {
170170
// Modify the cache, to check it's used when possible
171171
const bottomFrame = stackTraceOfParent.callFrames.at(-1);
172172
assert.exists(bottomFrame);
173+
const originalName = bottomFrame.functionName;
173174
bottomFrame.functionName = 'Overriden name';
174175

175176
// Compute stack trace of foo, ensure the cache calculated with
@@ -204,5 +205,6 @@ describeWithEnvironment('StackTraceForTraceEvent', function() {
204205
description: 'requestAnimationFrame',
205206
},
206207
]);
208+
bottomFrame.functionName = originalName;
207209
});
208210
});

front_end/panels/timeline/TimelineUIUtils.test.ts

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import {
2222
setupPageResourceLoaderForSourceMap,
2323
} from '../../testing/SourceMapHelpers.js';
2424
import {
25+
getBaseTraceParseModelData,
2526
getEventOfType,
2627
getMainThread,
2728
makeCompleteEvent,
29+
makeMockRendererHandlerData,
2830
makeMockSamplesHandlerData,
2931
makeProfileCall,
3032
} from '../../testing/TraceHelpers.js';
@@ -247,22 +249,26 @@ describeWithMockConnection('TimelineUIUtils', function() {
247249
workerIdByThread: new Map(),
248250
workerURLById: new Map(),
249251
};
250-
// This only includes data used in the SourceMapsResolver
251-
const parsedTrace = {
252+
// This only includes data used in the SourceMapsResolver and
253+
// TimelineUIUtils
254+
const parsedTrace = getBaseTraceParseModelData({
252255
Samples: makeMockSamplesHandlerData([profileCall]),
253256
Workers: workersData,
254-
} as Trace.Handlers.Types.ParsedTrace;
257+
Renderer: makeMockRendererHandlerData([profileCall]),
258+
});
255259

256260
const resolver = new Timeline.Utils.SourceMapsResolver.SourceMapsResolver(parsedTrace);
257261
await resolver.install();
258262

259-
const linkifier = new Components.Linkifier.Linkifier();
260-
const node = await Timeline.TimelineUIUtils.TimelineUIUtils.buildDetailsNodeForTraceEvent(
261-
profileCall, target, linkifier, true, parsedTrace);
262-
if (!node) {
263-
throw new Error('Node was unexpectedly null');
264-
}
265-
assert.isTrue(node.textContent?.startsWith('someFunction @'));
263+
const details = await Timeline.TimelineUIUtils.TimelineUIUtils.buildTraceEventDetails(
264+
parsedTrace,
265+
profileCall,
266+
new Components.Linkifier.Linkifier(),
267+
false,
268+
);
269+
const stackTraceData = getStackTraceForDetailsElement(details);
270+
assert.exists(stackTraceData);
271+
assert.isTrue(stackTraceData[0].startsWith('someFunction @'));
266272
});
267273
});
268274
describe('adjusting timestamps for events and navigations', function() {
@@ -839,16 +845,9 @@ describeWithMockConnection('TimelineUIUtils', function() {
839845
new Components.Linkifier.Linkifier(),
840846
false,
841847
);
842-
const rowData = getRowDataForDetailsElement(details);
843-
assert.deepEqual(
844-
rowData,
845-
[
846-
{
847-
title: 'Function',
848-
value: '(anonymous) @ www.google.com:21:17',
849-
},
850-
],
851-
);
848+
const stackTraceData = getStackTraceForDetailsElement(details);
849+
assert.exists(stackTraceData);
850+
assert.strictEqual(stackTraceData[0], '(anonymous) @ www.google.com:21:17');
852851
});
853852
it('renders the stack trace of a ScheduleStyleRecalculation properly', async function() {
854853
Common.Linkifier.registerLinkifier({
@@ -1175,6 +1174,45 @@ describeWithMockConnection('TimelineUIUtils', function() {
11751174
{title: 'Pending for', value: '200.1\xA0ms'},
11761175
]);
11771176
});
1177+
it('renders the stack trace of a profile call event', async function() {
1178+
// uses source maps
1179+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'async-js-calls.json.gz');
1180+
const jsCall = parsedTrace.Renderer.allTraceEntries.find(
1181+
e => Trace.Types.Events.isProfileCall(e) && e.callFrame.functionName === 'baz');
1182+
assert.exists(jsCall);
1183+
const details = await Timeline.TimelineUIUtils.TimelineUIUtils.buildTraceEventDetails(
1184+
parsedTrace,
1185+
jsCall,
1186+
new Components.Linkifier.Linkifier(),
1187+
false,
1188+
);
1189+
const container = document.createElement('div');
1190+
renderElementIntoDOM(container);
1191+
container.appendChild(details);
1192+
const stackTraceContainer = container.querySelector('.stack-preview-container');
1193+
const stackTracesElements =
1194+
stackTraceContainer?.shadowRoot?.querySelector('.stack-preview-container')?.querySelectorAll('tbody');
1195+
assert.exists(stackTracesElements);
1196+
const testData = [...stackTracesElements].map(stackTraceElement => {
1197+
const description = stackTraceElement.querySelector<HTMLTableRowElement>('.stack-preview-async-row');
1198+
const stackFrameElements =
1199+
stackTraceElement.querySelectorAll<HTMLTableRowElement>('tr:not(.stack-preview-async-row)');
1200+
const stackFrames = [...stackFrameElements].map(frame => frame.innerText);
1201+
return {description: description?.innerText || '', stackFrames};
1202+
});
1203+
assert.deepEqual(
1204+
testData,
1205+
[
1206+
{description: '', stackFrames: ['\tbaz\t@\tunknown']},
1207+
{description: '\trequestIdleCallback\t\t', stackFrames: ['\tbar\t@\tunknown']},
1208+
{description: '\tsetTimeout\t\t', stackFrames: ['\tfoo\t@\tunknown']},
1209+
{
1210+
description: '\trequestAnimationFrame\t\t',
1211+
stackFrames: ['\tstartExample\t@\tunknown', '\t(anonymous)\t@\tunknown'],
1212+
},
1213+
],
1214+
);
1215+
});
11781216
});
11791217

11801218
it('can generate details for a frame', async function() {

front_end/panels/timeline/TimelineUIUtils.ts

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -969,38 +969,15 @@ export class TimelineUIUtils {
969969
}
970970
break;
971971
}
972-
case Trace.Types.Events.Name.PROFILE_CALL: {
973-
details = document.createElement('span');
974-
// This check is only added for convenience with the type checker.
975-
if (!Trace.Types.Events.isProfileCall(event)) {
976-
break;
977-
}
978-
const maybeResolvedData = Utils.SourceMapsResolver.SourceMapsResolver.resolvedCodeLocationForEntry(event);
979-
const functionName = maybeResolvedData?.name || TimelineUIUtils.frameDisplayName(event.callFrame);
980-
UI.UIUtils.createTextChild(details, functionName);
981-
const location = this.linkifyLocation({
982-
scriptId: event.callFrame['scriptId'],
983-
url: event.callFrame['url'],
984-
lineNumber: event.callFrame['lineNumber'],
985-
columnNumber: event.callFrame['columnNumber'],
986-
target,
987-
isFreshRecording,
988-
linkifier,
989-
});
990-
if (location) {
991-
UI.UIUtils.createTextChild(details, ' @ ');
992-
details.appendChild(location);
993-
}
994-
break;
995-
}
996972

997973
default: {
998974
/**
999975
* Some events have a stack trace which is extracted by default at @see TimelineUIUtils.generateCauses
1000976
* thus, we prevent extracting the stack trace again here.
1001977
*/
1002978
if (Trace.Helpers.Trace.eventHasCategory(event, Trace.Types.Events.Categories.Console) ||
1003-
Trace.Types.Events.isUserTiming(event) || Trace.Types.Extensions.isSyntheticExtensionEntry(event)) {
979+
Trace.Types.Events.isUserTiming(event) || Trace.Types.Extensions.isSyntheticExtensionEntry(event) ||
980+
Trace.Types.Events.isProfileCall(event)) {
1004981
detailsText = null;
1005982
} else {
1006983
details = this.linkifyTopCallFrame(event, target, linkifier, isFreshRecording) ?? null;
@@ -1684,7 +1661,8 @@ export class TimelineUIUtils {
16841661
}
16851662

16861663
const stackTrace = Trace.Helpers.Trace.getZeroIndexedStackTraceForEvent(event);
1687-
if (initiator || initiatorFor || stackTrace || parsedTrace?.Invalidations.invalidationsForEvent.get(event)) {
1664+
if (Trace.Types.Events.isProfileCall(event) || initiator || initiatorFor || stackTrace ||
1665+
parsedTrace?.Invalidations.invalidationsForEvent.get(event)) {
16881666
await TimelineUIUtils.generateCauses(event, contentHelper, parsedTrace);
16891667
}
16901668

@@ -1872,13 +1850,26 @@ export class TimelineUIUtils {
18721850
return {callFrames} as Protocol.Runtime.StackTrace;
18731851
}
18741852

1875-
private static async generateCauses(
1853+
static async generateCauses(
18761854
event: Trace.Types.Events.Event, contentHelper: TimelineDetailsContentHelper,
18771855
parsedTrace: Trace.Handlers.Types.ParsedTrace): Promise<void> {
18781856
const {startTime} = Trace.Helpers.Timing.eventTimingsMilliSeconds(event);
18791857
let initiatorStackLabel = i18nString(UIStrings.initiatorStackTrace);
18801858
let stackLabel = i18nString(UIStrings.stackTrace);
1881-
1859+
const stackTraceForEvent = Trace.Extras.StackTraceForEvent.get(
1860+
event, parsedTrace, {isIgnoreListedCallback: Utils.IgnoreList.isIgnoreListedEntry});
1861+
if (Trace.Types.Events.isProfileCall(event) && stackTraceForEvent) {
1862+
contentHelper.addSection(i18nString(UIStrings.stackTrace));
1863+
contentHelper.createChildStackTraceElement(stackTraceForEvent);
1864+
// TODO(andoli): also build stack trace component for other events
1865+
// that have a stack trace using the StackTraceForEvent helper.
1866+
} else {
1867+
const stackTrace = Trace.Helpers.Trace.getZeroIndexedStackTraceForEvent(event);
1868+
if (stackTrace && stackTrace.length) {
1869+
contentHelper.addSection(stackLabel);
1870+
contentHelper.createChildStackTraceElement(TimelineUIUtils.stackTraceFromCallFrames(stackTrace));
1871+
}
1872+
}
18821873
switch (event.name) {
18831874
case Trace.Types.Events.Name.TIMER_FIRE:
18841875
initiatorStackLabel = i18nString(UIStrings.timerInstalled);
@@ -1899,12 +1890,6 @@ export class TimelineUIUtils {
18991890
break;
19001891
}
19011892

1902-
const stackTrace = Trace.Helpers.Trace.getZeroIndexedStackTraceForEvent(event);
1903-
if (stackTrace && stackTrace.length) {
1904-
contentHelper.addSection(stackLabel);
1905-
contentHelper.createChildStackTraceElement(TimelineUIUtils.stackTraceFromCallFrames(stackTrace));
1906-
}
1907-
19081893
const initiator = parsedTrace.Initiators.eventToInitiator.get(event);
19091894
const initiatorFor = parsedTrace.Initiators.initiatorToEvents.get(event);
19101895
const invalidations = parsedTrace.Invalidations.invalidationsForEvent.get(event);
@@ -2589,11 +2574,22 @@ export class TimelineDetailsContentHelper {
25892574
if (!this.linkifierInternal) {
25902575
return;
25912576
}
2592-
2577+
const resolvedStackTrace: Protocol.Runtime.StackTrace = structuredClone(stackTrace);
2578+
let currentResolvedStackTrace: Protocol.Runtime.StackTrace|undefined = resolvedStackTrace;
2579+
while (currentResolvedStackTrace) {
2580+
currentResolvedStackTrace.callFrames = currentResolvedStackTrace.callFrames.map(
2581+
callFrame => ({
2582+
...callFrame,
2583+
functionName:
2584+
Utils.SourceMapsResolver.SourceMapsResolver.resolvedCodeLocationForCallFrame(callFrame)?.name ||
2585+
callFrame.functionName,
2586+
}));
2587+
currentResolvedStackTrace = currentResolvedStackTrace.parent;
2588+
}
25932589
const stackTraceElement =
25942590
this.tableElement.createChild('div', 'timeline-details-view-row timeline-details-stack-values');
25952591
const callFrameContents = LegacyComponents.JSPresentationUtils.buildStackTracePreviewContents(
2596-
this.target, this.linkifierInternal, {stackTrace, tabStops: true, showColumnNumber: true});
2592+
this.target, this.linkifierInternal, {stackTrace: resolvedStackTrace, tabStops: true, showColumnNumber: true});
25972593
stackTraceElement.appendChild(callFrameContents.element);
25982594
}
25992595
}

0 commit comments

Comments
 (0)