[DevTools] Use source maps to infer name asynchronously (#34212)
Sebastian Markbåge committed
Aug 21, 2025 at 18:38 UTC
11d7bcf88cf13fa8fe6e091d2199c3905f5bb274
2 files changed
+80
-15
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
+2
-15
@@ -20,6 +20,7 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit
20
import StackTraceView from './StackTraceView';
21
import OwnerView from './OwnerView';
22
import {meta} from '../../../hydration';
23
+import useInferredName from '../useInferredName';
24
25
import type {
26
InspectedElement,
@@ -101,21 +102,7 @@ function SuspendedByRow({
102
}: RowProps) {
103
const [isOpen, setIsOpen] = useState(false);
104
const ioInfo = asyncInfo.awaited;
104
- let name = ioInfo.name;
105
- if (name === '' || name === 'Promise') {
106
- // If all we have is a generic name, we can try to infer a better name from
107
- // the stack. We only do this if the stack has more than one frame since
108
- // otherwise it's likely to just be the name of the component which isn't better.
109
- const bestStack = ioInfo.stack || asyncInfo.stack;
110
- if (bestStack !== null && bestStack.length > 1) {
111
- // TODO: Ideally we'd get the name from the last ignore listed frame before the
112
- // first visible frame since this is the same algorithm as the Flight server uses.
113
- // Ideally, we'd also get the name from the source mapped entry instead of the
114
- // original entry. However, that would require suspending the immediate display
115
- // of these rows to first do source mapping before we can show the name.
116
- name = bestStack[0][0];
117
- }
118
- }
105
+ const name = useInferredName(asyncInfo);
106
const description = ioInfo.description;
107
const longName = description === '' ? name : name + ' (' + description + ')';
108
const shortDescription = getShortDescription(name, description);
packages/react-devtools-shared/src/devtools/views/useInferredName.js
new
+78
@@ -0,0 +1,78 @@
1
+import {use, useContext, useDeferredValue} from 'react';
2
+
3
+import type {ReactCallSite} from 'shared/ReactTypes';
4
+
5
+import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
6
+
7
+import type {SerializedAsyncInfo} from 'react-devtools-shared/src/frontend/types';
8
+
9
+import FetchFileWithCachingContext from './Components/FetchFileWithCachingContext';
10
+
11
+import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
12
+
13
+export default function useInferredName(
14
+ asyncInfo: SerializedAsyncInfo,
15
+): string {
16
+ const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
17
+ const name = asyncInfo.awaited.name;
18
+ let inferNameFromStack = null;
19
+ if (!name || name === 'Promise') {
20
+ // If all we have is a generic name, we can try to infer a better name from
21
+ // the stack. We only do this if the stack has more than one frame since
22
+ // otherwise it's likely to just be the name of the component which isn't better.
23
+ const bestStack = asyncInfo.awaited.stack || asyncInfo.stack;
24
+ if (bestStack !== null && bestStack.length > 1) {
25
+ inferNameFromStack = bestStack;
26
+ }
27
+ }
28
+ // Start by not source mapping and just taking the first name and upgrade to
29
+ // the better name asynchronously if we find one. Most of the time it'll just be
30
+ // the top of the stack.
31
+ const shouldSourceMap = useDeferredValue(inferNameFromStack !== null, false);
32
+ if (inferNameFromStack !== null) {
33
+ if (shouldSourceMap) {
34
+ let bestMatch = '';
35
+ for (let i = 0; i < inferNameFromStack.length; i++) {
36
+ const callSite: ReactCallSite = inferNameFromStack[i];
37
+ const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
38
+ callSite;
39
+ const symbolicatedCallSite: null | SourceMappedLocation =
40
+ fetchFileWithCaching !== null
41
+ ? use(
42
+ symbolicateSourceWithCache(
43
+ fetchFileWithCaching,
44
+ virtualURL,
45
+ virtualLine,
46
+ virtualColumn,
47
+ ),
48
+ )
49
+ : null;
50
+ if (symbolicatedCallSite === null) {
51
+ // If we can't source map, we treat it as first party code. We called whatever was
52
+ // the previous callsite.
53
+ if (bestMatch === '') {
54
+ return virtualFunctionName || name;
55
+ } else {
56
+ return bestMatch;
57
+ }
58
+ } else if (!symbolicatedCallSite.ignored) {
59
+ if (bestMatch === '') {
60
+ // If we had no good stack frames for internal calls, just use the last
61
+ // first party function name.
62
+ return symbolicatedCallSite[0] || virtualFunctionName || name;
63
+ } else {
64
+ return bestMatch;
65
+ }
66
+ } else {
67
+ // This line was ignore listed, it might be the one we called into from first party.
68
+ bestMatch = symbolicatedCallSite[0] || virtualFunctionName;
69
+ }
70
+ }
71
+ return name;
72
+ } else {
73
+ return inferNameFromStack[0][0];
74
+ }
75
+ } else {
76
+ return name;
77
+ }
78
+}