Skip to content

Commit 35b73f9

Browse files
committed
Extract the first called function name from ignore listed frames
The name of the function that is actually called into from first party code is not actually on the stack so we extract that separately.
1 parent 090acfa commit 35b73f9

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

packages/react-server/src/ReactFlightServer.js

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,50 @@ function defaultFilterStackFrame(
165165
);
166166
}
167167

168+
function devirtualizeURL(url: string): string {
169+
if (url.startsWith('rsc://React/')) {
170+
// This callsite is a virtual fake callsite that came from another Flight client.
171+
// We need to reverse it back into the original location by stripping its prefix
172+
// and suffix. We don't need the environment name because it's available on the
173+
// parent object that will contain the stack.
174+
const envIdx = url.indexOf('/', 12);
175+
const suffixIdx = url.lastIndexOf('?');
176+
if (envIdx > -1 && suffixIdx > -1) {
177+
return url.slice(envIdx + 1, suffixIdx);
178+
}
179+
}
180+
return url;
181+
}
182+
183+
function findCalledFunctionNameFromStackTrace(
184+
request: Request,
185+
stack: ReactStackTrace,
186+
): string {
187+
// Gets the name of the first function called from first party code.
188+
let bestMatch = '';
189+
const filterStackFrame = request.filterStackFrame;
190+
for (let i = 0; i < stack.length; i++) {
191+
const callsite = stack[i];
192+
const functionName = callsite[0];
193+
const url = devirtualizeURL(callsite[1]);
194+
if (filterStackFrame(url, functionName)) {
195+
if (bestMatch === '') {
196+
// If we had no good stack frames for internal calls, just use the last
197+
// first party function name.
198+
return functionName;
199+
}
200+
return bestMatch;
201+
} else if (functionName === 'new Promise') {
202+
// Ignore Promise constructors.
203+
} else if (url === 'node:internal/async_hooks') {
204+
// Ignore the stack frames from the async hooks themselves.
205+
} else {
206+
bestMatch = functionName;
207+
}
208+
}
209+
return '';
210+
}
211+
168212
function filterStackTrace(
169213
request: Request,
170214
stack: ReactStackTrace,
@@ -178,18 +222,7 @@ function filterStackTrace(
178222
for (let i = 0; i < stack.length; i++) {
179223
const callsite = stack[i];
180224
const functionName = callsite[0];
181-
let url = callsite[1];
182-
if (url.startsWith('rsc://React/')) {
183-
// This callsite is a virtual fake callsite that came from another Flight client.
184-
// We need to reverse it back into the original location by stripping its prefix
185-
// and suffix. We don't need the environment name because it's available on the
186-
// parent object that will contain the stack.
187-
const envIdx = url.indexOf('/', 12);
188-
const suffixIdx = url.lastIndexOf('?');
189-
if (envIdx > -1 && suffixIdx > -1) {
190-
url = url.slice(envIdx + 1, suffixIdx);
191-
}
192-
}
225+
const url = devirtualizeURL(callsite[1]);
193226
if (filterStackFrame(url, functionName)) {
194227
// Use a clone because the Flight protocol isn't yet resilient to deduping
195228
// objects in the debug info. TODO: Support deduping stacks.
@@ -3568,9 +3601,18 @@ function serializeIONode(
35683601
let stack = null;
35693602
let name = '';
35703603
if (ioNode.stack !== null) {
3571-
stack = filterStackTrace(request, parseStackTrace(ioNode.stack, 1));
3572-
name = '';
3604+
const fullStack = parseStackTrace(ioNode.stack, 1);
3605+
stack = filterStackTrace(request, fullStack);
3606+
name = findCalledFunctionNameFromStackTrace(request, fullStack);
3607+
// The name can include the object that this was called on but sometimes that's
3608+
// just unnecessary context.
3609+
if (name.startsWith('Window.')) {
3610+
name = name.slice(7);
3611+
} else if (name.startsWith('<anonymous>.')) {
3612+
name = name.slice(7);
3613+
}
35733614
}
3615+
35743616
request.pendingChunks++;
35753617
const id = request.nextChunkId++;
35763618
emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, stack);

packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
170170
{
171171
"awaited": {
172172
"end": 0,
173+
"name": "delay",
173174
"stack": [
174175
[
175176
"delay",
@@ -220,6 +221,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
220221
{
221222
"awaited": {
222223
"end": 0,
224+
"name": "delay",
223225
"stack": [
224226
[
225227
"delay",
@@ -321,23 +323,24 @@ describe('ReactFlightAsyncDebugInfo', () => {
321323
[
322324
"Object.<anonymous>",
323325
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
324-
291,
326+
293,
325327
109,
326-
278,
328+
280,
327329
67,
328330
],
329331
],
330332
},
331333
{
332334
"awaited": {
333335
"end": 0,
336+
"name": "setTimeout",
334337
"stack": [
335338
[
336339
"Component",
337340
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
338-
281,
341+
283,
339342
7,
340-
279,
343+
281,
341344
5,
342345
],
343346
],

0 commit comments

Comments
 (0)