main
js 232 lines 7.59 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 // This is a DevTools fork of ReactFiberComponentStack.
11 // This fork enables DevTools to use the same "native" component stack format,
12 // while still maintaining support for multiple renderer versions
13 // (which use different values for ReactTypeOfWork).
14
15 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
16 import type {CurrentDispatcherRef, WorkTagMap} from '../types';
17
18 import type {ReactComponentInfo} from 'shared/ReactTypes';
19
20 import {
21 describeBuiltInComponentFrame,
22 describeFunctionComponentFrame,
23 describeClassComponentFrame,
24 describeDebugInfoFrame,
25 } from '../shared/DevToolsComponentStackFrame';
26
27 import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
28
29 export function describeFiber(
30 workTagMap: WorkTagMap,
31 workInProgress: Fiber,
32 currentDispatcherRef: CurrentDispatcherRef,
33 ): string {
34 const {
35 HostHoistable,
36 HostSingleton,
37 HostComponent,
38 LazyComponent,
39 SuspenseComponent,
40 SuspenseListComponent,
41 FunctionComponent,
42 IndeterminateComponent,
43 SimpleMemoComponent,
44 ForwardRef,
45 ClassComponent,
46 ViewTransitionComponent,
47 ActivityComponent,
48 } = workTagMap;
49
50 switch (workInProgress.tag) {
51 case HostHoistable:
52 case HostSingleton:
53 case HostComponent:
54 return describeBuiltInComponentFrame(workInProgress.type);
55 case LazyComponent:
56 // TODO: When we support Thenables as component types we should rename this.
57 return describeBuiltInComponentFrame('Lazy');
58 case SuspenseComponent:
59 return describeBuiltInComponentFrame('Suspense');
60 case SuspenseListComponent:
61 return describeBuiltInComponentFrame('SuspenseList');
62 case ViewTransitionComponent:
63 return describeBuiltInComponentFrame('ViewTransition');
64 case ActivityComponent:
65 return describeBuiltInComponentFrame('Activity');
66 case FunctionComponent:
67 case IndeterminateComponent:
68 case SimpleMemoComponent:
69 return describeFunctionComponentFrame(
70 workInProgress.type,
71 currentDispatcherRef,
72 );
73 case ForwardRef:
74 return describeFunctionComponentFrame(
75 workInProgress.type.render,
76 currentDispatcherRef,
77 );
78 case ClassComponent:
79 return describeClassComponentFrame(
80 workInProgress.type,
81 currentDispatcherRef,
82 );
83 default:
84 return '';
85 }
86 }
87
88 export function getStackByFiberInDevAndProd(
89 workTagMap: WorkTagMap,
90 workInProgress: Fiber,
91 currentDispatcherRef: CurrentDispatcherRef,
92 ): string {
93 try {
94 let info = '';
95 let node: Fiber = workInProgress;
96 do {
97 info += describeFiber(workTagMap, node, currentDispatcherRef);
98 // Add any Server Component stack frames in reverse order.
99 const debugInfo = node._debugInfo;
100 if (debugInfo) {
101 for (let i = debugInfo.length - 1; i >= 0; i--) {
102 const entry = debugInfo[i];
103 if (typeof entry.name === 'string') {
104 info += describeDebugInfoFrame(entry.name, entry.env);
105 }
106 }
107 }
108 // $FlowFixMe[incompatible-type] we bail out when we get a null
109 node = node.return;
110 } while (node);
111 return info;
112 } catch (x) {
113 return '\nError generating stack: ' + x.message + '\n' + x.stack;
114 }
115 }
116
117 export function getSourceLocationByFiber(
118 workTagMap: WorkTagMap,
119 fiber: Fiber,
120 currentDispatcherRef: CurrentDispatcherRef,
121 ): null | string {
122 // This is like getStackByFiberInDevAndProd but just the first stack frame.
123 try {
124 const info = describeFiber(workTagMap, fiber, currentDispatcherRef);
125 if (info !== '') {
126 return info.slice(1); // skip the leading newline
127 }
128 } catch (x) {
129 console.error(x);
130 }
131 return null;
132 }
133
134 export function supportsConsoleTasks(fiber: Fiber): boolean {
135 // If this Fiber supports native console.createTask then we are already running
136 // inside a native async stack trace if it's active - meaning the DevTools is open.
137 // Ideally we'd detect if this task was created while the DevTools was open or not.
138 return !!fiber._debugTask;
139 }
140
141 export function supportsOwnerStacks(fiber: Fiber): boolean {
142 // If this Fiber supports owner stacks then it'll have the _debugStack field.
143 // It might be null but that still means we should use the owner stack logic.
144 return fiber._debugStack !== undefined;
145 }
146
147 export function getOwnerStackByFiberInDev(
148 workTagMap: WorkTagMap,
149 workInProgress: Fiber,
150 currentDispatcherRef: CurrentDispatcherRef,
151 ): string {
152 const {
153 HostHoistable,
154 HostSingleton,
155 HostText,
156 HostComponent,
157 SuspenseComponent,
158 SuspenseListComponent,
159 ViewTransitionComponent,
160 ActivityComponent,
161 } = workTagMap;
162 try {
163 let info = '';
164
165 if (workInProgress.tag === HostText) {
166 // Text nodes never have an owner/stack because they're not created through JSX.
167 // We use the parent since text nodes are always created through a host parent.
168 workInProgress = workInProgress.return as any;
169 }
170
171 // The owner stack of the current fiber will be where it was created, i.e. inside its owner.
172 // There's no actual name of the currently executing component. Instead, that is available
173 // on the regular stack that's currently executing. However, for built-ins there is no such
174 // named stack frame and it would be ignored as being internal anyway. Therefore we add
175 // add one extra frame just to describe the "current" built-in component by name.
176 switch (workInProgress.tag) {
177 case HostHoistable:
178 case HostSingleton:
179 case HostComponent:
180 info += describeBuiltInComponentFrame(workInProgress.type);
181 break;
182 case SuspenseComponent:
183 info += describeBuiltInComponentFrame('Suspense');
184 break;
185 case SuspenseListComponent:
186 info += describeBuiltInComponentFrame('SuspenseList');
187 break;
188 case ViewTransitionComponent:
189 info += describeBuiltInComponentFrame('ViewTransition');
190 break;
191 case ActivityComponent:
192 info += describeBuiltInComponentFrame('Activity');
193 break;
194 }
195
196 let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
197
198 while (owner) {
199 if (typeof owner.tag === 'number') {
200 const fiber: Fiber = owner as any;
201 owner = fiber._debugOwner;
202 let debugStack: void | null | string | Error = fiber._debugStack;
203 // If we don't actually print the stack if there is no owner of this JSX element.
204 // In a real app it's typically not useful since the root app is always controlled
205 // by the framework. These also tend to have noisy stacks because they're not rooted
206 // in a React render but in some imperative bootstrapping code. It could be useful
207 // if the element was created in module scope. E.g. hoisted. We could add a a single
208 // stack frame for context for example but it doesn't say much if that's a wrapper.
209 if (owner && debugStack) {
210 if (typeof debugStack !== 'string') {
211 debugStack = formatOwnerStack(debugStack);
212 }
213 if (debugStack !== '') {
214 info += '\n' + debugStack;
215 }
216 }
217 } else if (owner.debugStack != null) {
218 // Server Component
219 const ownerStack: Error = owner.debugStack;
220 owner = owner.owner;
221 if (owner && ownerStack) {
222 info += '\n' + formatOwnerStack(ownerStack);
223 }
224 } else {
225 break;
226 }
227 }
228 return info;
229 } catch (x) {
230 return '\nError generating stack: ' + x.message + '\n' + x.stack;
231 }
232 }