Skip to content

Commit 0a921d6

Browse files
author
Brian Vaughn
committed
Forked ComponentStackFrame and FiberComponentStack into DevTools
1 parent e781580 commit 0a921d6

File tree

9 files changed

+580
-160
lines changed

9 files changed

+580
-160
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its 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 ReactComponentStackFrame.
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 {Source} from 'shared/ReactElementType';
16+
import type {LazyComponent} from 'react/src/ReactLazy';
17+
import type {CurrentDispatcherRef} from './types';
18+
19+
import {
20+
BLOCK_NUMBER,
21+
BLOCK_SYMBOL_STRING,
22+
FORWARD_REF_NUMBER,
23+
FORWARD_REF_SYMBOL_STRING,
24+
LAZY_NUMBER,
25+
LAZY_SYMBOL_STRING,
26+
MEMO_NUMBER,
27+
MEMO_SYMBOL_STRING,
28+
SUSPENSE_NUMBER,
29+
SUSPENSE_SYMBOL_STRING,
30+
SUSPENSE_LIST_NUMBER,
31+
SUSPENSE_LIST_SYMBOL_STRING,
32+
} from './ReactSymbols';
33+
34+
// These methods are safe to import from shared;
35+
// there is no React-specific logic here.
36+
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
37+
38+
let prefix;
39+
export function describeBuiltInComponentFrame(
40+
name: string,
41+
source: void | null | Source,
42+
ownerFn: void | null | Function,
43+
): string {
44+
if (prefix === undefined) {
45+
// Extract the VM specific prefix used by each line.
46+
try {
47+
throw Error();
48+
} catch (x) {
49+
const match = x.stack.trim().match(/\n( *(at )?)/);
50+
prefix = (match && match[1]) || '';
51+
}
52+
}
53+
// We use the prefix to ensure our stacks line up with native stack frames.
54+
return '\n' + prefix + name;
55+
}
56+
57+
let reentry = false;
58+
let componentFrameCache;
59+
if (__DEV__) {
60+
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
61+
componentFrameCache = new PossiblyWeakMap();
62+
}
63+
64+
export function describeNativeComponentFrame(
65+
fn: Function,
66+
construct: boolean,
67+
currentDispatcherRef: CurrentDispatcherRef,
68+
): string {
69+
// If something asked for a stack inside a fake render, it should get ignored.
70+
if (!fn || reentry) {
71+
return '';
72+
}
73+
74+
if (__DEV__) {
75+
const frame = componentFrameCache.get(fn);
76+
if (frame !== undefined) {
77+
return frame;
78+
}
79+
}
80+
81+
let control;
82+
83+
reentry = true;
84+
let previousDispatcher;
85+
if (__DEV__) {
86+
previousDispatcher = currentDispatcherRef.current;
87+
// Set the dispatcher in DEV because this might be call in the render function
88+
// for warnings.
89+
currentDispatcherRef.current = null;
90+
disableLogs();
91+
}
92+
try {
93+
// This should throw.
94+
if (construct) {
95+
// Something should be setting the props in the constructor.
96+
const Fake = function() {
97+
throw Error();
98+
};
99+
// $FlowFixMe
100+
Object.defineProperty(Fake.prototype, 'props', {
101+
set: function() {
102+
// We use a throwing setter instead of frozen or non-writable props
103+
// because that won't throw in a non-strict mode function.
104+
throw Error();
105+
},
106+
});
107+
if (typeof Reflect === 'object' && Reflect.construct) {
108+
// We construct a different control for this case to include any extra
109+
// frames added by the construct call.
110+
try {
111+
Reflect.construct(Fake, []);
112+
} catch (x) {
113+
control = x;
114+
}
115+
Reflect.construct(fn, [], Fake);
116+
} else {
117+
try {
118+
Fake.call();
119+
} catch (x) {
120+
control = x;
121+
}
122+
fn.call(Fake.prototype);
123+
}
124+
} else {
125+
try {
126+
throw Error();
127+
} catch (x) {
128+
control = x;
129+
}
130+
fn();
131+
}
132+
} catch (sample) {
133+
// This is inlined manually because closure doesn't do it for us.
134+
if (sample && control && typeof sample.stack === 'string') {
135+
// This extracts the first frame from the sample that isn't also in the control.
136+
// Skipping one frame that we assume is the frame that calls the two.
137+
const sampleLines = sample.stack.split('\n');
138+
const controlLines = control.stack.split('\n');
139+
let s = sampleLines.length - 1;
140+
let c = controlLines.length - 1;
141+
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
142+
// We expect at least one stack frame to be shared.
143+
// Typically this will be the root most one. However, stack frames may be
144+
// cut off due to maximum stack limits. In this case, one maybe cut off
145+
// earlier than the other. We assume that the sample is longer or the same
146+
// and there for cut off earlier. So we should find the root most frame in
147+
// the sample somewhere in the control.
148+
c--;
149+
}
150+
for (; s >= 1 && c >= 0; s--, c--) {
151+
// Next we find the first one that isn't the same which should be the
152+
// frame that called our sample function and the control.
153+
if (sampleLines[s] !== controlLines[c]) {
154+
// In V8, the first line is describing the message but other VMs don't.
155+
// If we're about to return the first line, and the control is also on the same
156+
// line, that's a pretty good indicator that our sample threw at same line as
157+
// the control. I.e. before we entered the sample frame. So we ignore this result.
158+
// This can happen if you passed a class to function component, or non-function.
159+
if (s !== 1 || c !== 1) {
160+
do {
161+
s--;
162+
c--;
163+
// We may still have similar intermediate frames from the construct call.
164+
// The next one that isn't the same should be our match though.
165+
if (c < 0 || sampleLines[s] !== controlLines[c]) {
166+
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
167+
const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
168+
if (__DEV__) {
169+
if (typeof fn === 'function') {
170+
componentFrameCache.set(fn, frame);
171+
}
172+
}
173+
// Return the line we found.
174+
return frame;
175+
}
176+
} while (s >= 1 && c >= 0);
177+
}
178+
break;
179+
}
180+
}
181+
}
182+
} finally {
183+
reentry = false;
184+
if (__DEV__) {
185+
currentDispatcherRef.current = previousDispatcher;
186+
reenableLogs();
187+
}
188+
}
189+
// Fallback to just using the name if we couldn't make it throw.
190+
const name = fn ? fn.displayName || fn.name : '';
191+
const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
192+
if (__DEV__) {
193+
if (typeof fn === 'function') {
194+
componentFrameCache.set(fn, syntheticFrame);
195+
}
196+
}
197+
return syntheticFrame;
198+
}
199+
200+
export function describeClassComponentFrame(
201+
ctor: Function,
202+
source: void | null | Source,
203+
ownerFn: void | null | Function,
204+
currentDispatcherRef: CurrentDispatcherRef,
205+
): string {
206+
return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
207+
}
208+
209+
export function describeFunctionComponentFrame(
210+
fn: Function,
211+
source: void | null | Source,
212+
ownerFn: void | null | Function,
213+
currentDispatcherRef: CurrentDispatcherRef,
214+
): string {
215+
return describeNativeComponentFrame(fn, false, currentDispatcherRef);
216+
}
217+
218+
function shouldConstruct(Component: Function) {
219+
const prototype = Component.prototype;
220+
return !!(prototype && prototype.isReactComponent);
221+
}
222+
223+
export function describeUnknownElementTypeFrameInDEV(
224+
type: any,
225+
source: void | null | Source,
226+
ownerFn: void | null | Function,
227+
currentDispatcherRef: CurrentDispatcherRef,
228+
): string {
229+
if (!__DEV__) {
230+
return '';
231+
}
232+
if (type == null) {
233+
return '';
234+
}
235+
if (typeof type === 'function') {
236+
return describeNativeComponentFrame(
237+
type,
238+
shouldConstruct(type),
239+
currentDispatcherRef,
240+
);
241+
}
242+
if (typeof type === 'string') {
243+
return describeBuiltInComponentFrame(type, source, ownerFn);
244+
}
245+
switch (type) {
246+
case SUSPENSE_NUMBER:
247+
case SUSPENSE_SYMBOL_STRING:
248+
return describeBuiltInComponentFrame('Suspense', source, ownerFn);
249+
case SUSPENSE_LIST_NUMBER:
250+
case SUSPENSE_LIST_SYMBOL_STRING:
251+
return describeBuiltInComponentFrame('SuspenseList', source, ownerFn);
252+
}
253+
if (typeof type === 'object') {
254+
switch (type.$$typeof) {
255+
case FORWARD_REF_NUMBER:
256+
case FORWARD_REF_SYMBOL_STRING:
257+
return describeFunctionComponentFrame(
258+
type.render,
259+
source,
260+
ownerFn,
261+
currentDispatcherRef,
262+
);
263+
case MEMO_NUMBER:
264+
case MEMO_SYMBOL_STRING:
265+
// Memo may contain any component type so we recursively resolve it.
266+
return describeUnknownElementTypeFrameInDEV(
267+
type.type,
268+
source,
269+
ownerFn,
270+
currentDispatcherRef,
271+
);
272+
case BLOCK_NUMBER:
273+
case BLOCK_SYMBOL_STRING:
274+
return describeFunctionComponentFrame(
275+
type._render,
276+
source,
277+
ownerFn,
278+
currentDispatcherRef,
279+
);
280+
case LAZY_NUMBER:
281+
case LAZY_SYMBOL_STRING: {
282+
const lazyComponent: LazyComponent<any, any> = (type: any);
283+
const payload = lazyComponent._payload;
284+
const init = lazyComponent._init;
285+
try {
286+
// Lazy may contain any component type so we recursively resolve it.
287+
return describeUnknownElementTypeFrameInDEV(
288+
init(payload),
289+
source,
290+
ownerFn,
291+
currentDispatcherRef,
292+
);
293+
} catch (x) {}
294+
}
295+
}
296+
}
297+
return '';
298+
}

0 commit comments

Comments
 (0)