99
1010import { copy } from 'clipboard-js' ;
1111import * as React from 'react' ;
12- import { useState , useTransition } from 'react' ;
12+ import { use , useContext , useState , useTransition } from 'react' ;
1313import Button from '../Button' ;
1414import ButtonIcon from '../ButtonIcon' ;
1515import KeyValue from './KeyValue' ;
1616import { serializeDataForCopy , pluralize } from '../utils' ;
1717import Store from '../../store' ;
1818import styles from './InspectedElementSharedStyles.css' ;
1919import { withPermissionsCheck } from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck' ;
20- import StackTraceView from './StackTraceView' ;
20+ import FetchFileWithCachingContext from './FetchFileWithCachingContext' ;
21+ import StackTraceView , { IgnoreListToggleButton } from './StackTraceView' ;
2122import OwnerView from './OwnerView' ;
2223import { meta } from '../../../hydration' ;
24+ import Skeleton from './Skeleton' ;
2325import useInferredName from '../useInferredName' ;
26+ import { symbolicateSourceWithCache } from 'react-devtools-shared/src/symbolicateSource' ;
2427
2528import { getClassNameForEnvironment } from '../SuspenseTab/SuspenseEnvironmentColors.js' ;
2629
@@ -29,6 +32,8 @@ import type {
2932 SerializedAsyncInfo ,
3033} from 'react-devtools-shared/src/frontend/types' ;
3134import type { FrontendBridge } from 'react-devtools-shared/src/bridge' ;
35+ import type { ReactStackTrace } from 'shared/ReactTypes' ;
36+ import type { SourceMappedLocation } from 'react-devtools-shared/src/symbolicateSource' ;
3237
3338import {
3439 UNKNOWN_SUSPENDERS_NONE ,
@@ -94,6 +99,78 @@ function formatBytes(bytes: number) {
9499 return ( bytes / 1_000_000_000 ) . toFixed ( 1 ) + ' gB' ;
95100}
96101
102+ type StackTraceGroupProps = {
103+ children : ( showIgnoreList : boolean ) => React . Node ,
104+ ioStack : null | ReactStackTrace ,
105+ asyncInfoStack : null | ReactStackTrace ,
106+ } ;
107+
108+ function StackTraceGroup ( {
109+ children,
110+ ioStack,
111+ asyncInfoStack,
112+ } : StackTraceGroupProps ) : React . Node {
113+ const [ showIgnoreList , setShowIgnoreList ] = useState ( false ) ;
114+ const fetchFileWithCaching = useContext ( FetchFileWithCachingContext ) ;
115+
116+ const ioStackHasIgnoredFrames =
117+ ioStack !== null &&
118+ ioStack . some ( callSite => {
119+ const [ , virtualURL , virtualLine , virtualColumn ] = callSite ;
120+
121+ // symbolicated output is cached
122+ const symbolicatedCallSite : null | SourceMappedLocation =
123+ fetchFileWithCaching !== null
124+ ? use (
125+ symbolicateSourceWithCache (
126+ fetchFileWithCaching ,
127+ virtualURL ,
128+ virtualLine ,
129+ virtualColumn ,
130+ ) ,
131+ )
132+ : null ;
133+
134+ return symbolicatedCallSite !== null && symbolicatedCallSite . ignored ;
135+ } ) ;
136+
137+ const asyncInfoStackHasIgnoredFrames =
138+ asyncInfoStack !== null &&
139+ asyncInfoStack . some ( callSite => {
140+ const [ , virtualURL , virtualLine , virtualColumn ] = callSite ;
141+
142+ // symbolicated output is cached
143+ const symbolicatedCallSite : null | SourceMappedLocation =
144+ fetchFileWithCaching !== null
145+ ? use (
146+ symbolicateSourceWithCache (
147+ fetchFileWithCaching ,
148+ virtualURL ,
149+ virtualLine ,
150+ virtualColumn ,
151+ ) ,
152+ )
153+ : null ;
154+
155+ return symbolicatedCallSite !== null && symbolicatedCallSite . ignored ;
156+ } ) ;
157+
158+ const hasIgnoredFrames =
159+ ioStackHasIgnoredFrames || asyncInfoStackHasIgnoredFrames ;
160+
161+ return (
162+ < >
163+ { children ( showIgnoreList ) }
164+ { hasIgnoredFrames && (
165+ < IgnoreListToggleButton
166+ onClick = { ( ) => setShowIgnoreList ( prev => ! prev ) }
167+ showIgnoreList = { showIgnoreList }
168+ />
169+ ) }
170+ </ >
171+ ) ;
172+ }
173+
97174function SuspendedByRow ( {
98175 bridge,
99176 element,
@@ -203,102 +280,126 @@ function SuspendedByRow({
203280 </ Button >
204281 { isOpen && (
205282 < div className = { styles . CollapsableContent } >
206- { showIOStack && (
207- < StackTraceView
208- stack = { ioInfo . stack }
209- environmentName = {
210- ioOwner !== null && ioOwner . env === ioInfo . env
211- ? null
212- : ioInfo . env
213- }
214- />
215- ) }
216- { ioOwner !== null &&
217- ioOwner . id !== inspectedElement . id &&
218- ( showIOStack ||
219- ! showAwaitStack ||
220- asyncOwner === null ||
221- ioOwner . id !== asyncOwner . id ) ? (
222- < OwnerView
223- key = { ioOwner . id }
224- displayName = { ioOwner . displayName || 'Anonymous' }
225- environmentName = {
226- ioOwner . env === inspectedElement . env &&
227- ioOwner . env === ioInfo . env
228- ? null
229- : ioOwner . env
230- }
231- hocDisplayNames = { ioOwner . hocDisplayNames }
232- compiledWithForget = { ioOwner . compiledWithForget }
233- id = { ioOwner . id }
234- isInStore = { store . containsElement ( ioOwner . id ) }
235- type = { ioOwner . type }
236- />
237- ) : null }
238- { showAwaitStack ? (
239- < >
240- < div className = { styles . SmallHeader } > awaited at:</ div >
241- { asyncInfo . stack !== null && asyncInfo . stack . length > 0 && (
242- < StackTraceView
243- stack = { asyncInfo . stack }
244- environmentName = {
245- asyncOwner !== null && asyncOwner . env === asyncInfo . env
246- ? null
247- : asyncInfo . env
248- }
249- />
283+ < React . Suspense
284+ fallback = {
285+ < div className = { styles . SuspendedBySkeleton } >
286+ < Skeleton height = { 16 } width = "40%" />
287+ </ div >
288+ } >
289+ < StackTraceGroup
290+ ioStack = { showIOStack ? ioInfo . stack : null }
291+ asyncInfoStack = { showAwaitStack ? asyncInfo . stack : null } >
292+ { ( showIgnoreList : boolean ) => (
293+ < >
294+ { showIOStack && (
295+ < StackTraceView
296+ stack = { ioInfo . stack }
297+ environmentName = {
298+ ioOwner !== null && ioOwner . env === ioInfo . env
299+ ? null
300+ : ioInfo . env
301+ }
302+ showIgnoreList = { showIgnoreList }
303+ />
304+ ) }
305+ { ioOwner !== null &&
306+ ioOwner . id !== inspectedElement . id &&
307+ ( showIOStack ||
308+ ! showAwaitStack ||
309+ asyncOwner === null ||
310+ ioOwner . id !== asyncOwner . id ) ? (
311+ < OwnerView
312+ key = { ioOwner . id }
313+ displayName = { ioOwner . displayName || 'Anonymous' }
314+ environmentName = {
315+ ioOwner . env === inspectedElement . env &&
316+ ioOwner . env === ioInfo . env
317+ ? null
318+ : ioOwner . env
319+ }
320+ hocDisplayNames = { ioOwner . hocDisplayNames }
321+ compiledWithForget = { ioOwner . compiledWithForget }
322+ id = { ioOwner . id }
323+ isInStore = { store . containsElement ( ioOwner . id ) }
324+ type = { ioOwner . type }
325+ />
326+ ) : null }
327+ { showAwaitStack ? (
328+ < >
329+ < div className = { styles . SmallHeader } > awaited at:</ div >
330+ { asyncInfo . stack !== null &&
331+ asyncInfo . stack . length > 0 && (
332+ < StackTraceView
333+ stack = { asyncInfo . stack }
334+ environmentName = {
335+ asyncOwner !== null &&
336+ asyncOwner . env === asyncInfo . env
337+ ? null
338+ : asyncInfo . env
339+ }
340+ showIgnoreList = { showIgnoreList }
341+ />
342+ ) }
343+ { asyncOwner !== null &&
344+ asyncOwner . id !== inspectedElement . id ? (
345+ < OwnerView
346+ key = { asyncOwner . id }
347+ displayName = { asyncOwner . displayName || 'Anonymous' }
348+ environmentName = {
349+ asyncOwner . env === inspectedElement . env &&
350+ asyncOwner . env === asyncInfo . env
351+ ? null
352+ : asyncOwner . env
353+ }
354+ hocDisplayNames = { asyncOwner . hocDisplayNames }
355+ compiledWithForget = { asyncOwner . compiledWithForget }
356+ id = { asyncOwner . id }
357+ isInStore = { store . containsElement ( asyncOwner . id ) }
358+ type = { asyncOwner . type }
359+ />
360+ ) : null }
361+ </ >
362+ ) : null }
363+ < div className = { styles . PreviewContainer } >
364+ < KeyValue
365+ alphaSort = { true }
366+ bridge = { bridge }
367+ canDeletePaths = { false }
368+ canEditValues = { false }
369+ canRenamePaths = { false }
370+ depth = { 1 }
371+ element = { element }
372+ hidden = { false }
373+ inspectedElement = { inspectedElement }
374+ name = {
375+ isFulfilled
376+ ? 'awaited value'
377+ : isRejected
378+ ? 'rejected with'
379+ : 'pending value'
380+ }
381+ path = {
382+ isFulfilled
383+ ? [ index , 'awaited' , 'value' , 'value' ]
384+ : isRejected
385+ ? [ index , 'awaited' , 'value' , 'reason' ]
386+ : [ index , 'awaited' , 'value' ]
387+ }
388+ pathRoot = "suspendedBy"
389+ store = { store }
390+ value = {
391+ isFulfilled
392+ ? value . value
393+ : isRejected
394+ ? value . reason
395+ : value
396+ }
397+ />
398+ </ div >
399+ </ >
250400 ) }
251- { asyncOwner !== null && asyncOwner . id !== inspectedElement . id ? (
252- < OwnerView
253- key = { asyncOwner . id }
254- displayName = { asyncOwner . displayName || 'Anonymous' }
255- environmentName = {
256- asyncOwner . env === inspectedElement . env &&
257- asyncOwner . env === asyncInfo . env
258- ? null
259- : asyncOwner . env
260- }
261- hocDisplayNames = { asyncOwner . hocDisplayNames }
262- compiledWithForget = { asyncOwner . compiledWithForget }
263- id = { asyncOwner . id }
264- isInStore = { store . containsElement ( asyncOwner . id ) }
265- type = { asyncOwner . type }
266- />
267- ) : null }
268- </ >
269- ) : null }
270- < div className = { styles . PreviewContainer } >
271- < KeyValue
272- alphaSort = { true }
273- bridge = { bridge }
274- canDeletePaths = { false }
275- canEditValues = { false }
276- canRenamePaths = { false }
277- depth = { 1 }
278- element = { element }
279- hidden = { false }
280- inspectedElement = { inspectedElement }
281- name = {
282- isFulfilled
283- ? 'awaited value'
284- : isRejected
285- ? 'rejected with'
286- : 'pending value'
287- }
288- path = {
289- isFulfilled
290- ? [ index , 'awaited' , 'value' , 'value' ]
291- : isRejected
292- ? [ index , 'awaited' , 'value' , 'reason' ]
293- : [ index , 'awaited' , 'value' ]
294- }
295- pathRoot = "suspendedBy"
296- store = { store }
297- value = {
298- isFulfilled ? value . value : isRejected ? value . reason : value
299- }
300- />
301- </ div >
401+ </ StackTraceGroup >
402+ </ React . Suspense >
302403 </ div >
303404 ) }
304405 </ div >
0 commit comments