@@ -64,6 +64,7 @@ import type {
6464 ReactAsyncInfo ,
6565 ReactTimeInfo ,
6666 ReactStackTrace ,
67+ ReactCallSite ,
6768 ReactFunctionLocation ,
6869 ReactErrorInfo ,
6970 ReactErrorInfoDev ,
@@ -164,55 +165,73 @@ function defaultFilterStackFrame(
164165 ) ;
165166}
166167
167- // DEV-only cache of parsed and filtered stack frames.
168- const stackTraceCache: WeakMap< Error , ReactStackTrace > = __DEV__
169- ? new WeakMap()
170- : (null: any);
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+ }
171182
172- function filterStackTrace (
183+ function findCalledFunctionNameFromStackTrace (
173184 request : Request ,
174- error: Error,
175- skipFrames: number,
176- ): ReactStackTrace {
177- const existing = stackTraceCache . get ( error ) ;
178- if ( existing !== undefined ) {
179- // Return a clone because the Flight protocol isn't yet resilient to deduping
180- // objects in the debug info. TODO: Support deduping stacks.
181- const clone = existing . slice ( 0 ) ;
182- for ( let i = 0 ; i < clone . length ; i ++ ) {
183- // $FlowFixMe[invalid-tuple-arity]
184- clone [ i ] = clone [ i ] . slice ( 0 ) ;
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 ;
185207 }
186- return clone ;
187208 }
209+ return '' ;
210+ }
211+
212+ function filterStackTrace (
213+ request : Request ,
214+ stack : ReactStackTrace ,
215+ ) : ReactStackTrace {
188216 // Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
189217 // to save bandwidth even in DEV. We'll also replay these stacks on the client so by
190218 // stripping them early we avoid that overhead. Otherwise we'd normally just rely on
191219 // the DevTools or framework's ignore lists to filter them out.
192220 const filterStackFrame = request . filterStackFrame ;
193- const stack = parseStackTrace(error, skipFrames) ;
221+ const filteredStack : ReactStackTrace = [ ] ;
194222 for ( let i = 0 ; i < stack . length ; i ++ ) {
195223 const callsite = stack [ i ] ;
196224 const functionName = callsite [ 0 ] ;
197- let url = callsite [ 1 ] ;
198- if ( url . startsWith ( 'rsc://React/' ) ) {
199- // This callsite is a virtual fake callsite that came from another Flight client.
200- // We need to reverse it back into the original location by stripping its prefix
201- // and suffix. We don't need the environment name because it's available on the
202- // parent object that will contain the stack.
203- const envIdx = url . indexOf ( '/' , 12 ) ;
204- const suffixIdx = url . lastIndexOf ( '?' ) ;
205- if ( envIdx > - 1 && suffixIdx > - 1 ) {
206- url = callsite [ 1 ] = url . slice ( envIdx + 1 , suffixIdx ) ;
207- }
208- }
209- if ( ! filterStackFrame ( url , functionName ) ) {
210- stack . splice ( i , 1 ) ;
211- i -- ;
225+ const url = devirtualizeURL ( callsite [ 1 ] ) ;
226+ if ( filterStackFrame ( url , functionName ) ) {
227+ // Use a clone because the Flight protocol isn't yet resilient to deduping
228+ // objects in the debug info. TODO: Support deduping stacks.
229+ const clone : ReactCallSite = ( callsite . slice ( 0 ) : any ) ;
230+ clone [ 1 ] = url ;
231+ filteredStack . push ( clone ) ;
212232 }
213233 }
214- stackTraceCache . set ( error , stack ) ;
215- return stack ;
234+ return filteredStack ;
216235}
217236
218237initAsyncDebugInfo ( ) ;
@@ -240,8 +259,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) {
240259 // one stack frame but keeping it simple for now and include all frames.
241260 const stack = filterStackTrace (
242261 request ,
243- new Error ( 'react-stack-top-frame' ) ,
244- 1 ,
262+ parseStackTrace ( new Error ( 'react-stack-top-frame' ) , 1 ) ,
245263 ) ;
246264 request . pendingChunks ++ ;
247265 const owner : null | ReactComponentInfo = resolveOwner ( ) ;
@@ -1078,7 +1096,7 @@ function callWithDebugContextInDEV<A, T>(
10781096 componentDebugInfo . stack =
10791097 task . debugStack === null
10801098 ? null
1081- : filterStackTrace ( request , task . debugStack , 1 ) ;
1099+ : filterStackTrace ( request , parseStackTrace ( task . debugStack , 1 ) ) ;
10821100 // $FlowFixMe[cannot-write]
10831101 componentDebugInfo . debugStack = task . debugStack ;
10841102 // $FlowFixMe[cannot-write]
@@ -1279,7 +1297,7 @@ function renderFunctionComponent<Props>(
12791297 componentDebugInfo . stack =
12801298 task . debugStack === null
12811299 ? null
1282- : filterStackTrace ( request , task . debugStack , 1 ) ;
1300+ : filterStackTrace ( request , parseStackTrace ( task . debugStack , 1 ) ) ;
12831301 // $FlowFixMe[cannot-write]
12841302 componentDebugInfo . props = props ;
12851303 // $FlowFixMe[cannot-write]
@@ -1615,7 +1633,7 @@ function renderClientElement(
16151633 task . debugOwner ,
16161634 task . debugStack === null
16171635 ? null
1618- : filterStackTrace ( request , task . debugStack , 1 ) ,
1636+ : filterStackTrace ( request , parseStackTrace ( task . debugStack , 1 ) ) ,
16191637 validated ,
16201638 ]
16211639 : [ REACT_ELEMENT_TYPE , type , key , props ] ;
@@ -1748,7 +1766,7 @@ function renderElement(
17481766 stack :
17491767 task . debugStack === null
17501768 ? null
1751- : filterStackTrace ( request , task . debugStack , 1 ) ,
1769+ : filterStackTrace ( request , parseStackTrace ( task . debugStack , 1 ) ) ,
17521770 props : props ,
17531771 debugStack : task . debugStack ,
17541772 debugTask : task . debugTask ,
@@ -1877,7 +1895,10 @@ function visitAsyncNode(
18771895 // We don't log it yet though. We return it to be logged by the point where it's awaited.
18781896 // The ioNode might be another PromiseNode in the case where none of the AwaitNode had
18791897 // unfiltered stacks.
1880- if ( filterStackTrace ( request , node . stack , 1 ) . length === 0 ) {
1898+ if (
1899+ filterStackTrace ( request , parseStackTrace ( node . stack , 1 ) ) . length ===
1900+ 0
1901+ ) {
18811902 // Typically we assume that the outer most Promise that was awaited in user space has the
18821903 // most actionable stack trace for the start of the operation. However, if this Promise
18831904 // was created inside only third party code, then try to use the inner node instead.
@@ -1898,7 +1919,10 @@ function visitAsyncNode(
18981919 if ( awaited !== null ) {
18991920 const ioNode = visitAsyncNode ( request , task , awaited , cutOff , visited ) ;
19001921 if ( ioNode !== null ) {
1901- const stack = filterStackTrace ( request , node . stack , 1 ) ;
1922+ const stack = filterStackTrace (
1923+ request ,
1924+ parseStackTrace ( node . stack , 1 ) ,
1925+ ) ;
19021926 if ( stack . length === 0 ) {
19031927 // If this await was fully filtered out, then it was inside third party code
19041928 // such as in an external library. We return the I/O node and try another await.
@@ -3272,7 +3296,7 @@ function emitPostponeChunk(
32723296 try {
32733297 // eslint-disable-next-line react-internal/safe-string-coercion
32743298 reason = String ( postponeInstance . message ) ;
3275- stack = filterStackTrace ( request , postponeInstance , 0 ) ;
3299+ stack = filterStackTrace ( request , parseStackTrace ( postponeInstance , 0 ) ) ;
32763300 } catch ( x ) {
32773301 stack = [ ] ;
32783302 }
@@ -3295,7 +3319,7 @@ function serializeErrorValue(request: Request, error: Error): string {
32953319 name = error . name ;
32963320 // eslint-disable-next-line react-internal/safe-string-coercion
32973321 message = String ( error . message ) ;
3298- stack = filterStackTrace ( request , error , 0 ) ;
3322+ stack = filterStackTrace ( request , parseStackTrace ( error , 0 ) ) ;
32993323 const errorEnv = ( error : any ) . environmentName ;
33003324 if ( typeof errorEnv === 'string' ) {
33013325 // This probably came from another FlightClient as a pass through.
@@ -3334,7 +3358,7 @@ function emitErrorChunk(
33343358 name = error . name ;
33353359 // eslint-disable-next-line react-internal/safe-string-coercion
33363360 message = String ( error . message ) ;
3337- stack = filterStackTrace ( request , error , 0 ) ;
3361+ stack = filterStackTrace ( request , parseStackTrace ( error , 0 ) ) ;
33383362 const errorEnv = ( error : any ) . environmentName ;
33393363 if ( typeof errorEnv === 'string' ) {
33403364 // This probably came from another FlightClient as a pass through.
@@ -3496,6 +3520,7 @@ function outlineComponentInfo(
34963520function emitIOInfoChunk (
34973521 request : Request ,
34983522 id : number ,
3523+ name : string ,
34993524 start : number ,
35003525 end : number ,
35013526 stack : ?ReactStackTrace ,
@@ -3532,6 +3557,7 @@ function emitIOInfoChunk(
35323557 const relativeStartTimestamp = start - request . timeOrigin ;
35333558 const relativeEndTimestamp = end - request . timeOrigin ;
35343559 const debugIOInfo : Omit < ReactIOInfo , 'debugTask' | 'debugStack' > = {
3560+ name : name ,
35353561 start : relativeStartTimestamp ,
35363562 end : relativeEndTimestamp ,
35373563 stack : stack ,
@@ -3551,7 +3577,14 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
35513577 // We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
35523578 request . pendingChunks ++ ;
35533579 const id = request . nextChunkId ++ ;
3554- emitIOInfoChunk ( request , id , ioInfo . start , ioInfo . end , ioInfo . stack ) ;
3580+ emitIOInfoChunk (
3581+ request ,
3582+ id ,
3583+ ioInfo . name ,
3584+ ioInfo . start ,
3585+ ioInfo . end ,
3586+ ioInfo . stack ,
3587+ ) ;
35553588 request . writtenObjects . set ( ioInfo , serializeByValueID ( id ) ) ;
35563589}
35573590
@@ -3566,12 +3599,23 @@ function serializeIONode(
35663599 }
35673600
35683601 let stack = null ;
3602+ let name = '';
35693603 if ( ioNode . stack !== null ) {
3570- stack = filterStackTrace ( request , ioNode . stack , 1 ) ;
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+ }
35713614 }
3615+
35723616 request . pendingChunks ++ ;
35733617 const id = request . nextChunkId ++ ;
3574- emitIOInfoChunk ( request , id , ioNode . start , ioNode . end , stack ) ;
3618+ emitIOInfoChunk ( request , id , name , ioNode . start , ioNode . end , stack ) ;
35753619 const ref = serializeByValueID ( id ) ;
35763620 request . writtenObjects . set ( ioNode , ref ) ;
35773621 return ref ;
@@ -3712,7 +3756,10 @@ function renderConsoleValue(
37123756 let debugStack : null | ReactStackTrace = null ;
37133757 if ( element . _debugStack != null ) {
37143758 // Outline the debug stack so that it doesn't get cut off.
3715- debugStack = filterStackTrace ( request , element . _debugStack , 1 ) ;
3759+ debugStack = filterStackTrace (
3760+ request ,
3761+ parseStackTrace ( element . _debugStack , 1 ) ,
3762+ ) ;
37163763 doNotLimit . add ( debugStack ) ;
37173764 for ( let i = 0 ; i < debugStack . length ; i ++ ) {
37183765 doNotLimit . add ( debugStack [ i ] ) ;
0 commit comments