Skip to content

Commit 11d7bcf

Browse files
authored
[DevTools] Use source maps to infer name asynchronously (facebook#34212)
1 parent a85ec04 commit 11d7bcf

File tree

2 files changed

+80
-15
lines changed

2 files changed

+80
-15
lines changed

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit
2020
import StackTraceView from './StackTraceView';
2121
import OwnerView from './OwnerView';
2222
import {meta} from '../../../hydration';
23+
import useInferredName from '../useInferredName';
2324

2425
import type {
2526
InspectedElement,
@@ -101,21 +102,7 @@ function SuspendedByRow({
101102
}: RowProps) {
102103
const [isOpen, setIsOpen] = useState(false);
103104
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);
119106
const description = ioInfo.description;
120107
const longName = description === '' ? name : name + ' (' + description + ')';
121108
const shortDescription = getShortDescription(name, description);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)