@@ -19,8 +19,10 @@ import type {
1919 FulfilledThenable ,
2020 RejectedThenable ,
2121} from 'shared/ReactTypes' ;
22+ import type { ComponentStackNode } from './ReactFizzComponentStack' ;
2223
2324import noop from 'shared/noop' ;
25+ import { currentTaskInDEV } from './ReactFizzCurrentTask' ;
2426
2527export opaque type ThenableState = Array < Thenable < any >> ;
2628
@@ -126,6 +128,9 @@ export function trackUsedThenable<T>(
126128 // get captured by the work loop, log a warning, because that means
127129 // something in userspace must have caught it.
128130 suspendedThenable = thenable;
131+ if (__DEV__ && shouldCaptureSuspendedCallSite ) {
132+ captureSuspendedCallSite ( ) ;
133+ }
129134 throw SuspenseException;
130135 }
131136 }
@@ -163,3 +168,77 @@ export function getSuspendedThenable(): Thenable<mixed> {
163168 suspendedThenable = null;
164169 return thenable;
165170}
171+
172+ let shouldCaptureSuspendedCallSite : boolean = false ;
173+ export function setCaptureSuspendedCallSiteDEV ( capture : boolean ) : void {
174+ if ( ! __DEV__ ) {
175+ // eslint-disable-next-line react-internal/prod-error-codes
176+ throw new Error (
177+ 'setCaptureSuspendedCallSiteDEV was called in a production environment. ' +
178+ 'This is a bug in React.' ,
179+ ) ;
180+ }
181+ shouldCaptureSuspendedCallSite = capture;
182+ }
183+
184+ // DEV-only
185+ let suspendedCallSiteStack : ComponentStackNode | null = null ;
186+ let suspendedCallSiteDebugTask : ConsoleTask | null = null ;
187+ function captureSuspendedCallSite ( ) : void {
188+ const currentTask = currentTaskInDEV ;
189+ if ( currentTask === null ) {
190+ // eslint-disable-next-line react-internal/prod-error-codes -- not a prod error
191+ throw new Error (
192+ 'Expected to have a current task when tracking a suspend call site. ' +
193+ 'This is a bug in React.' ,
194+ ) ;
195+ }
196+ const currentComponentStack = currentTask . componentStack ;
197+ if ( currentComponentStack === null ) {
198+ // eslint-disable-next-line react-internal/prod-error-codes -- not a prod error
199+ throw new Error (
200+ 'Expected to have a component stack on the current task when ' +
201+ 'tracking a suspended call site. This is a bug in React.' ,
202+ ) ;
203+ }
204+ suspendedCallSiteStack = {
205+ parent : currentComponentStack . parent ,
206+ type : currentComponentStack . type ,
207+ owner : currentComponentStack . owner ,
208+ stack : Error ( 'react-stack-top-frame' ) ,
209+ } ;
210+ suspendedCallSiteDebugTask = currentTask . debugTask ;
211+ }
212+ export function getSuspendedCallSiteStackDEV(): ComponentStackNode | null {
213+ if ( __DEV__ ) {
214+ if ( suspendedCallSiteStack === null ) {
215+ return null ;
216+ }
217+ const callSite = suspendedCallSiteStack;
218+ suspendedCallSiteStack = null;
219+ return callSite;
220+ } else {
221+ // eslint-disable-next-line react-internal/prod-error-codes
222+ throw new Error (
223+ 'getSuspendedCallSiteDEV was called in a production environment. ' +
224+ 'This is a bug in React.' ,
225+ ) ;
226+ }
227+ }
228+
229+ export function getSuspendedCallSiteDebugTaskDEV ( ) : ConsoleTask | null {
230+ if ( __DEV__ ) {
231+ if ( suspendedCallSiteDebugTask === null ) {
232+ return null ;
233+ }
234+ const debugTask = suspendedCallSiteDebugTask;
235+ suspendedCallSiteDebugTask = null;
236+ return debugTask;
237+ } else {
238+ // eslint-disable-next-line react-internal/prod-error-codes
239+ throw new Error (
240+ 'getSuspendedCallSiteDebugTaskDEV was called in a production environment. ' +
241+ 'This is a bug in React.' ,
242+ ) ;
243+ }
244+ }
0 commit comments