main
js 331 lines 10.5 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 {Fragment, useContext, useState, use} from 'react';
12 import {BridgeContext, StoreContext} from '../context';
13 import InspectedElementBadges from './InspectedElementBadges';
14 import InspectedElementContextTree from './InspectedElementContextTree';
15 import InspectedElementErrorsAndWarningsTree from './InspectedElementErrorsAndWarningsTree';
16 import InspectedElementHooksTree from './InspectedElementHooksTree';
17 import InspectedElementPropsTree from './InspectedElementPropsTree';
18 import InspectedElementStateTree from './InspectedElementStateTree';
19 import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin';
20 import InspectedElementSuspendedBy from './InspectedElementSuspendedBy';
21 import NativeStyleEditor from './NativeStyleEditor';
22 import FetchFileWithCachingContext from './FetchFileWithCachingContext';
23 import {enableStyleXFeatures} from 'react-devtools-feature-flags';
24 import InspectedElementSourcePanel from './InspectedElementSourcePanel';
25 import StackTraceView, {IgnoreListToggleButton} from './StackTraceView';
26 import OwnerView from './OwnerView';
27 import Skeleton from './Skeleton';
28 import {
29 ElementTypeSuspense,
30 ElementTypeActivity,
31 } from 'react-devtools-shared/src/frontend/types';
32 import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
33
34 import styles from './InspectedElementView.css';
35
36 import type {
37 Element,
38 InspectedElement,
39 } from 'react-devtools-shared/src/frontend/types';
40 import type {HookNames} from 'react-devtools-shared/src/frontend/types';
41 import type {ToggleParseHookNames} from './InspectedElementContext';
42 import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
43
44 type StackTraceGroupProps = {
45 children: (showIgnoreList: boolean) => React.Node,
46 componentStack: InspectedElement['stack'],
47 owners: InspectedElement['owners'],
48 };
49
50 function StackTraceGroup({
51 children,
52 componentStack,
53 owners,
54 }: StackTraceGroupProps): React.Node {
55 const [showIgnoreList, setShowIgnoreList] = useState(false);
56 const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
57
58 const componentStackHasIgnoredFrames =
59 componentStack !== null &&
60 componentStack.some(callSite => {
61 const [, virtualURL, virtualLine, virtualColumn] = callSite;
62
63 // symbolicated output is cached
64 const symbolicatedCallSite: null | SourceMappedLocation =
65 fetchFileWithCaching !== null
66 ? use(
67 symbolicateSourceWithCache(
68 fetchFileWithCaching,
69 virtualURL,
70 virtualLine,
71 virtualColumn,
72 ),
73 )
74 : null;
75
76 return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
77 });
78
79 const ownerStacksHaveIgnoredFrames =
80 owners !== null &&
81 owners.some(owner => {
82 return (
83 owner.stack !== null &&
84 owner.stack.some(callSite => {
85 const [, virtualURL, virtualLine, virtualColumn] = callSite;
86
87 // symbolicated output is cached
88 const symbolicatedCallSite: null | SourceMappedLocation =
89 fetchFileWithCaching !== null
90 ? use(
91 symbolicateSourceWithCache(
92 fetchFileWithCaching,
93 virtualURL,
94 virtualLine,
95 virtualColumn,
96 ),
97 )
98 : null;
99
100 return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
101 })
102 );
103 });
104
105 const hasIgnoredFrames =
106 componentStackHasIgnoredFrames || ownerStacksHaveIgnoredFrames;
107
108 return (
109 <>
110 {children(showIgnoreList)}
111 {hasIgnoredFrames && (
112 <IgnoreListToggleButton
113 onClick={() => setShowIgnoreList(prev => !prev)}
114 showIgnoreList={showIgnoreList}
115 />
116 )}
117 </>
118 );
119 }
120
121 type Props = {
122 element: Element,
123 hookNames: HookNames | null,
124 inspectedElement: InspectedElement,
125 parseHookNames: boolean,
126 toggleParseHookNames: ToggleParseHookNames,
127 symbolicatedSourcePromise: Promise<SourceMappedLocation | null>,
128 };
129
130 export default function InspectedElementView({
131 element,
132 hookNames,
133 inspectedElement,
134 parseHookNames,
135 toggleParseHookNames,
136 symbolicatedSourcePromise,
137 }: Props): React.Node {
138 const {
139 stack,
140 owners,
141 rendererPackageName,
142 rendererVersion,
143 rootType,
144 source,
145 nativeTag,
146 type,
147 } = inspectedElement;
148
149 const bridge = useContext(BridgeContext);
150 const store = useContext(StoreContext);
151
152 const rendererLabel =
153 rendererPackageName !== null && rendererVersion !== null
154 ? `${rendererPackageName}@${rendererVersion}`
155 : null;
156 const showOwnersList = owners !== null && owners.length > 0;
157 const showStack = stack != null && stack.length > 0;
158 const showRenderedBy =
159 showStack || showOwnersList || rendererLabel !== null || rootType !== null;
160
161 const propsSection = (
162 <div className={styles.InspectedElementSection}>
163 <InspectedElementPropsTree
164 bridge={bridge}
165 element={element}
166 inspectedElement={inspectedElement}
167 store={store}
168 />
169 </div>
170 );
171
172 return (
173 <Fragment>
174 <div className={styles.InspectedElement}>
175 <div className={styles.InspectedElementSection}>
176 <InspectedElementBadges
177 hocDisplayNames={element.hocDisplayNames}
178 compiledWithForget={element.compiledWithForget}
179 nativeTag={nativeTag}
180 />
181 </div>
182
183 {
184 // For Suspense and Activity we show the props further down.
185 type !== ElementTypeSuspense && type !== ElementTypeActivity
186 ? propsSection
187 : null
188 }
189
190 <div className={styles.InspectedElementSection}>
191 <InspectedElementStateTree
192 bridge={bridge}
193 element={element}
194 inspectedElement={inspectedElement}
195 store={store}
196 />
197 </div>
198
199 <div className={styles.InspectedElementSection}>
200 <InspectedElementHooksTree
201 bridge={bridge}
202 element={element}
203 hookNames={hookNames}
204 inspectedElement={inspectedElement}
205 parseHookNames={parseHookNames}
206 store={store}
207 toggleParseHookNames={toggleParseHookNames}
208 />
209 </div>
210
211 <div className={styles.InspectedElementSection}>
212 <InspectedElementContextTree
213 bridge={bridge}
214 element={element}
215 inspectedElement={inspectedElement}
216 store={store}
217 />
218 </div>
219
220 {enableStyleXFeatures && (
221 <div className={styles.InspectedElementSection}>
222 <InspectedElementStyleXPlugin
223 bridge={bridge}
224 element={element}
225 inspectedElement={inspectedElement}
226 store={store}
227 />
228 </div>
229 )}
230
231 <div className={styles.InspectedElementSection}>
232 <InspectedElementErrorsAndWarningsTree
233 bridge={bridge}
234 element={element}
235 inspectedElement={inspectedElement}
236 store={store}
237 />
238 </div>
239
240 <div className={styles.InspectedElementSection}>
241 <NativeStyleEditor />
242 </div>
243
244 <div className={styles.InspectedElementSection}>
245 <InspectedElementSuspendedBy
246 bridge={bridge}
247 element={element}
248 inspectedElement={inspectedElement}
249 store={store}
250 />
251 </div>
252
253 {
254 // For Suspense and Activity we show the props below suspended by to give that more priority.
255 type !== ElementTypeSuspense && type !== ElementTypeActivity
256 ? null
257 : propsSection
258 }
259
260 {showRenderedBy && (
261 <div
262 className={styles.InspectedElementSection}
263 data-testname="InspectedElementView-Owners">
264 <div className={styles.OwnersHeader}>rendered by</div>
265 <React.Suspense
266 fallback={
267 <div className={styles.RenderedBySkeleton}>
268 <Skeleton height={16} width="40%" />
269 </div>
270 }>
271 <StackTraceGroup componentStack={stack} owners={owners}>
272 {(showIgnoreList: boolean) => (
273 <>
274 {showStack ? (
275 <StackTraceView
276 stack={stack}
277 showIgnoreList={showIgnoreList}
278 />
279 ) : null}
280 {showOwnersList &&
281 owners?.map(owner => (
282 <Fragment key={owner.id}>
283 <OwnerView
284 displayName={owner.displayName || 'Anonymous'}
285 hocDisplayNames={owner.hocDisplayNames}
286 environmentName={
287 inspectedElement.env === owner.env
288 ? null
289 : owner.env
290 }
291 compiledWithForget={owner.compiledWithForget}
292 id={owner.id}
293 isInStore={store.containsElement(owner.id)}
294 type={owner.type}
295 />
296 {owner.stack != null && owner.stack.length > 0 ? (
297 <StackTraceView
298 stack={owner.stack}
299 showIgnoreList={showIgnoreList}
300 />
301 ) : null}
302 </Fragment>
303 ))}
304
305 {rootType !== null && (
306 <div className={styles.OwnersMetaField}>{rootType}</div>
307 )}
308 {rendererLabel !== null && (
309 <div className={styles.OwnersMetaField}>
310 {rendererLabel}
311 </div>
312 )}
313 </>
314 )}
315 </StackTraceGroup>
316 </React.Suspense>
317 </div>
318 )}
319
320 {source != null && (
321 <div className={styles.InspectedElementSection}>
322 <InspectedElementSourcePanel
323 source={source}
324 symbolicatedSourcePromise={symbolicatedSourcePromise}
325 />
326 </div>
327 )}
328 </div>
329 </Fragment>
330 );
331 }