@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252 const url = devirtualizeURL ( callsite [ 1 ] ) ;
253253 const lineNumber = callsite [ 2 ] ;
254254 const columnNumber = callsite [ 3 ] ;
255- if ( filterStackFrame ( url , functionName , lineNumber , columnNumber ) ) {
255+ if (
256+ filterStackFrame ( url , functionName , lineNumber , columnNumber ) &&
257+ // Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+ url !== ''
259+ ) {
256260 if ( bestMatch === '' ) {
257261 // If we had no good stack frames for internal calls, just use the last
258262 // first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312 const isAsync = callsite [ 6 ] ;
309313 if (
310314 ! isAsync &&
311- filterStackFrame ( url , functionName , lineNumber , columnNumber )
315+ filterStackFrame ( url , functionName , lineNumber , columnNumber ) &&
316+ // Ignore anonymous stack frames like internals. They are also not in first party
317+ // code even though it might be useful to include them in the final stack.
318+ url !== ''
312319 ) {
313320 return true ;
314321 }
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374 const url = devirtualizeURL ( callsite [ 1 ] ) ;
368375 const lineNumber = callsite [ 2 ] ;
369376 const columnNumber = callsite [ 3 ] ;
370- return filterStackFrame ( url , functionName , lineNumber , columnNumber ) ;
377+ return (
378+ filterStackFrame ( url , functionName , lineNumber , columnNumber ) &&
379+ url !== ''
380+ ) ;
371381 }
372382 return false;
373383}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357 }
23482358 const awaited = node.awaited;
23492359 let match: void | null | PromiseNode | IONode = previousIONode;
2360+ const promise = node.promise.deref();
23502361 if (awaited !== null) {
23512362 const ioNode = visitAsyncNode ( request , task , awaited , visited , cutOff ) ;
23522363 if ( ioNode === undefined ) {
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372 if ( ioNode . tag === PROMISE_NODE ) {
23622373 // If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374 // we would've returned an IO node. We assume this has the best stack.
2375+ // Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+ // We could potentially favor the outer Promise if it has a stack but not the inner.
23642377 match = ioNode ;
23652378 } else if (
2366- node . stack === null ||
2367- ! hasUnfilteredFrame ( request , node . stack )
2379+ ( node . stack !== null && hasUnfilteredFrame ( request , node . stack ) ) ||
2380+ ( promise !== undefined &&
2381+ // $FlowFixMe[prop-missing]
2382+ typeof promise . displayName === 'string' &&
2383+ ( ioNode . stack === null ||
2384+ ! hasUnfilteredFrame ( request , ioNode . stack ) ) )
23682385 ) {
2386+ // If this Promise has a stack trace then we favor that over the I/O node since we're
2387+ // mainly dealing with Promises as the abstraction.
2388+ // If it has no stack but at least has a displayName and the io doesn't have a better
2389+ // stack anyway, then also use this Promise instead since at least it has a name.
2390+ match = node ;
2391+ } else {
23692392 // If this Promise was created inside only third party code, then try to use
23702393 // the inner I/O node instead. This could happen if third party calls into first
23712394 // party to perform some I/O.
23722395 match = ioNode ;
2373- } else {
2374- match = node ;
23752396 }
23762397 } else if ( request . status === ABORTING ) {
23772398 if ( node . start < request . abortTime && node . end > request . abortTime ) {
23782399 // We aborted this render. If this Promise spanned the abort time it was probably the
23792400 // Promise that was aborted. This won't necessarily have I/O associated with it but
23802401 // it's a point of interest.
23812402 if (
2382- node . stack !== null &&
2383- hasUnfilteredFrame ( request , node . stack )
2403+ ( node . stack !== null &&
2404+ hasUnfilteredFrame ( request , node . stack ) ) ||
2405+ ( promise !== undefined &&
2406+ // $FlowFixMe[prop-missing]
2407+ typeof promise . displayName === 'string' )
23842408 ) {
23852409 match = node ;
23862410 }
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413 }
23902414 // We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415 // the thing that generated this node and its virtual children.
2392- const promise = node . promise . deref ( ) ;
23932416 if ( promise !== undefined ) {
23942417 const debugInfo = promise . _debugInfo ;
23952418 if ( debugInfo != null && ! visited . has ( debugInfo ) ) {
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520
44984521 let stack = null ;
44994522 let name = '' ;
4523+ if ( ioNode . promise !== null ) {
4524+ // Pick an explicit name from the Promise itself if it exists.
4525+ // Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+ // which is the value observed but it's likely not the one with the name on it.
4527+ const promise = ioNode . promise . deref ( ) ;
4528+ if (
4529+ promise !== undefined &&
4530+ // $FlowFixMe[prop-missing]
4531+ typeof promise . displayName === 'string'
4532+ ) {
4533+ name = promise . displayName ;
4534+ }
4535+ }
45004536 if ( ioNode . stack !== null ) {
45014537 // The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538 const fullStack = stripLeadingPromiseCreationFrames ( ioNode . stack ) ;
45034539 stack = filterStackTrace ( request , fullStack ) ;
4504- name = findCalledFunctionNameFromStackTrace ( request , fullStack ) ;
4505- // The name can include the object that this was called on but sometimes that's
4506- // just unnecessary context.
4507- if ( name . startsWith ( 'Window.' ) ) {
4508- name = name . slice ( 7 ) ;
4509- } else if ( name . startsWith ( '<anonymous>.' ) ) {
4510- name = name . slice ( 7 ) ;
4540+ if ( name === '' ) {
4541+ // If we didn't have an explicit name, try finding one from the stack.
4542+ name = findCalledFunctionNameFromStackTrace ( request , fullStack ) ;
4543+ // The name can include the object that this was called on but sometimes that's
4544+ // just unnecessary context.
4545+ if ( name . startsWith ( 'Window.' ) ) {
4546+ name = name . slice ( 7 ) ;
4547+ } else if ( name . startsWith ( '<anonymous>.' ) ) {
4548+ name = name . slice ( 7 ) ;
4549+ }
45114550 }
45124551 }
45134552 const owner = ioNode . owner ;
0 commit comments