main
js 182 lines 4.9 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import * as React from 'react';
11 import {use, useContext} from 'react';
12
13 import Button from '../Button';
14 import useOpenResource from '../useOpenResource';
15
16 import ElementBadges from './ElementBadges';
17
18 import styles from './StackTraceView.css';
19
20 import type {ReactStackTrace, ReactCallSite} from 'shared/ReactTypes';
21
22 import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
23
24 import FetchFileWithCachingContext from './FetchFileWithCachingContext';
25
26 import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
27
28 import formatLocationForDisplay from './formatLocationForDisplay';
29
30 type CallSiteViewProps = {
31 callSite: ReactCallSite,
32 environmentName: null | string,
33 showIgnoreList: boolean,
34 };
35
36 export function CallSiteView({
37 callSite,
38 environmentName,
39 showIgnoreList,
40 }: CallSiteViewProps): React.Node {
41 const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
42
43 const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
44 callSite;
45
46 const symbolicatedCallSite: null | SourceMappedLocation =
47 fetchFileWithCaching !== null
48 ? use(
49 symbolicateSourceWithCache(
50 fetchFileWithCaching,
51 virtualURL,
52 virtualLine,
53 virtualColumn,
54 ),
55 )
56 : null;
57
58 const [linkIsEnabled, viewSource] = useOpenResource(
59 callSite,
60 symbolicatedCallSite == null ? null : symbolicatedCallSite.location,
61 );
62 const [functionName, url, line, column] =
63 symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
64 const ignored =
65 symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
66
67 const isBuiltIn = url === '' || url.startsWith('<anonymous>'); // This looks like a fake anonymous through eval.
68 return (
69 <div
70 className={
71 ignored
72 ? styles.IgnoredCallSite +
73 (showIgnoreList ? ' ' + styles.CallSite : '')
74 : isBuiltIn
75 ? styles.BuiltInCallSite
76 : styles.CallSite
77 }>
78 {functionName || virtualFunctionName}
79 {!isBuiltIn && (
80 <>
81 {' @ '}
82 <span
83 className={linkIsEnabled ? styles.Link : null}
84 onClick={viewSource}
85 title={url + ':' + line}>
86 {formatLocationForDisplay(url, line, column)}
87 </span>
88 </>
89 )}
90 <ElementBadges
91 className={styles.ElementBadges}
92 environmentName={environmentName}
93 />
94 </div>
95 );
96 }
97
98 type IgnoreListToggleButtonProps = {
99 onClick: () => void,
100 showIgnoreList: boolean,
101 };
102
103 export function IgnoreListToggleButton({
104 onClick,
105 showIgnoreList,
106 }: IgnoreListToggleButtonProps): React.Node {
107 const label = showIgnoreList
108 ? 'Hide ignore-listed frames'
109 : 'Show ignore-listed frames';
110
111 return (
112 <div className={styles.IgnoreListToggleContainer}>
113 <Button
114 className={styles.IgnoreListToggleButton}
115 onClick={onClick}
116 title={label}>
117 {label}
118 </Button>
119 </div>
120 );
121 }
122
123 type Props = {
124 stack: ReactStackTrace,
125 environmentName: null | string,
126 showIgnoreList: boolean,
127 };
128
129 export default function StackTraceView({
130 stack,
131 environmentName,
132 showIgnoreList,
133 }: Props): React.Node {
134 const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
135
136 let lastMeaningfulFrameIndex = -1;
137 // Reverse loop to find the last non-ignored, non-built-in index
138 for (let index = stack.length - 1; index >= 0; index--) {
139 const callSite = stack[index];
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 const [, url] =
155 symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
156 const ignored =
157 symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
158 // This looks like a fake anonymous through eval.
159 const isBuiltIn = url === '' || url.startsWith('<anonymous>');
160
161 if (!ignored && !isBuiltIn) {
162 lastMeaningfulFrameIndex = index;
163 break;
164 }
165 }
166
167 return (
168 <div className={styles.StackTraceView}>
169 {stack.map((callSite, index) => (
170 <CallSiteView
171 key={index}
172 callSite={callSite}
173 environmentName={
174 // Badge last meaningful frame (non-ignored, non-built-in)
175 index === lastMeaningfulFrameIndex ? environmentName : null
176 }
177 showIgnoreList={showIgnoreList}
178 />
179 ))}
180 </div>
181 );
182 }