77 * @flow
88 */
99
10- import type { Fiber } from 'react-reconciler/src/ReactInternalTypes' ;
11- import type {
12- LegacyDispatcherRef ,
13- CurrentDispatcherRef ,
14- ReactRenderer ,
15- WorkTagMap ,
16- ConsolePatchSettings ,
17- } from './types' ;
10+ import type { ConsolePatchSettings } from './types' ;
1811
1912import {
2013 formatConsoleArguments ,
@@ -25,14 +18,6 @@ import {
2518 ANSI_STYLE_DIMMING_TEMPLATE ,
2619 ANSI_STYLE_DIMMING_TEMPLATE_WITH_COMPONENT_STACK ,
2720} from 'react-devtools-shared/src/constants' ;
28- import { getInternalReactConstants , getDispatcherRef } from './fiber/renderer' ;
29- import {
30- getStackByFiberInDevAndProd ,
31- getOwnerStackByFiberInDev ,
32- supportsOwnerStacks ,
33- supportsConsoleTasks ,
34- } from './fiber/DevToolsFiberComponentStack' ;
35- import { formatOwnerStack } from './shared/DevToolsOwnerStack' ;
3621import { castBool , castBrowserTheme } from '../utils' ;
3722
3823const OVERRIDE_CONSOLE_METHODS = [ 'error' , 'trace' , 'warn' ] ;
@@ -90,21 +75,15 @@ function restorePotentiallyModifiedArgs(args: Array<any>): Array<any> {
9075 }
9176}
9277
93- type OnErrorOrWarning = (
94- fiber : Fiber ,
95- type : 'error' | 'warn' ,
96- args : Array < any > ,
97- ) => void ;
98-
99- const injectedRenderers : Map <
100- ReactRenderer ,
101- {
102- currentDispatcherRef : LegacyDispatcherRef | CurrentDispatcherRef ,
103- getCurrentFiber : ( ) => Fiber | null ,
104- onErrorOrWarning : ?OnErrorOrWarning ,
105- workTagMap : WorkTagMap ,
106- } ,
107- > = new Map ( ) ;
78+ type OnErrorOrWarning = ( type : 'error' | 'warn' , args : Array < any > ) => void ;
79+ type GetComponentStack = (
80+ topFrame : Error ,
81+ ) => null | { enableOwnerStacks : boolean , componentStack : string } ;
82+
83+ const injectedRenderers : Array < {
84+ onErrorOrWarning : ?OnErrorOrWarning ,
85+ getComponentStack : ?GetComponentStack ,
86+ } > = [ ] ;
10887
10988let targetConsole : Object = console ;
11089let targetConsoleMethods : { [ string ] : $FlowFixMe } = { } ;
@@ -132,23 +111,13 @@ export function dangerous_setTargetConsoleForTesting(
132111// These internals will be used if the console is patched.
133112// Injecting them separately allows the console to easily be patched or un-patched later (at runtime).
134113export function registerRenderer (
135- renderer : ReactRenderer ,
136114 onErrorOrWarning ?: OnErrorOrWarning ,
115+ getComponentStack ?: GetComponentStack ,
137116) : void {
138- const { currentDispatcherRef, getCurrentFiber, version} = renderer ;
139-
140- // currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
141- // getCurrentFiber gets injected for v16.9+.
142- if ( currentDispatcherRef != null && typeof getCurrentFiber === 'function' ) {
143- const { ReactTypeOfWork } = getInternalReactConstants ( version ) ;
144-
145- injectedRenderers . set ( renderer , {
146- currentDispatcherRef,
147- getCurrentFiber,
148- workTagMap : ReactTypeOfWork ,
149- onErrorOrWarning,
150- } ) ;
151- }
117+ injectedRenderers . push ( {
118+ onErrorOrWarning ,
119+ getComponentStack ,
120+ } ) ;
152121}
153122
154123const consoleSettingsRef : ConsolePatchSettings = {
@@ -219,63 +188,47 @@ export function patch({
219188
220189 // Search for the first renderer that has a current Fiber.
221190 // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
222- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
223- for ( const renderer of injectedRenderers . values ( ) ) {
224- const currentDispatcherRef = getDispatcherRef ( renderer ) ;
225- const { getCurrentFiber, onErrorOrWarning, workTagMap} = renderer ;
226- const current : ?Fiber = getCurrentFiber ( ) ;
227- if ( current != null ) {
228- try {
229- if ( shouldShowInlineWarningsAndErrors ) {
230- // patch() is called by two places: (1) the hook and (2) the renderer backend.
231- // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning.
232- if ( typeof onErrorOrWarning === 'function' ) {
233- onErrorOrWarning (
234- current ,
235- ( ( method : any ) : 'error' | 'warn' ) ,
236- // Restore and copy args before we mutate them (e.g. adding the component stack)
237- restorePotentiallyModifiedArgs ( args ) ,
238- ) ;
239- }
191+ for ( let i = 0 ; i < injectedRenderers . length ; i ++ ) {
192+ const renderer = injectedRenderers [ i ] ;
193+ const { getComponentStack, onErrorOrWarning} = renderer ;
194+ try {
195+ if ( shouldShowInlineWarningsAndErrors ) {
196+ // patch() is called by two places: (1) the hook and (2) the renderer backend.
197+ // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning.
198+ if ( onErrorOrWarning != null ) {
199+ onErrorOrWarning (
200+ ( ( method : any ) : 'error' | 'warn' ) ,
201+ // Restore and copy args before we mutate them (e.g. adding the component stack)
202+ restorePotentiallyModifiedArgs ( args ) ,
203+ ) ;
240204 }
241-
242- if (
243- consoleSettingsRef . appendComponentStack &&
244- ! supportsConsoleTasks ( current )
245- ) {
246- const enableOwnerStacks = supportsOwnerStacks ( current ) ;
247- let componentStack = '' ;
248- if ( enableOwnerStacks ) {
249- // Prefix the owner stack with the current stack. I.e. what called
250- // console.error. While this will also be part of the native stack,
251- // it is hidden and not presented alongside this argument so we print
252- // them all together.
253- const topStackFrames = formatOwnerStack (
254- new Error ( 'react-stack-top-frame' ) ,
255- ) ;
256- if ( topStackFrames ) {
257- componentStack += '\n' + topStackFrames ;
258- }
259- componentStack += getOwnerStackByFiberInDev (
260- workTagMap ,
261- current ,
262- ( currentDispatcherRef : any ) ,
263- ) ;
264- } else {
265- componentStack = getStackByFiberInDevAndProd (
266- workTagMap ,
267- current ,
268- ( currentDispatcherRef : any ) ,
269- ) ;
270- }
205+ }
206+ } catch ( error ) {
207+ // Don't let a DevTools or React internal error interfere with logging.
208+ setTimeout ( ( ) => {
209+ throw error ;
210+ } , 0 ) ;
211+ }
212+ try {
213+ if (
214+ consoleSettingsRef . appendComponentStack &&
215+ getComponentStack != null
216+ ) {
217+ // This needs to be directly in the wrapper so we can pop exactly one frame.
218+ const topFrame = Error ( 'react-stack-top-frame' ) ;
219+ const match = getComponentStack ( topFrame ) ;
220+ if ( match !== null ) {
221+ const { enableOwnerStacks, componentStack} = match ;
222+ // Empty string means we have a match but no component stack.
223+ // We don't need to look in other renderers but we also don't add anything.
271224 if ( componentStack !== '' ) {
272225 // Create a fake Error so that when we print it we get native source maps. Every
273226 // browser will print the .stack property of the error and then parse it back for source
274227 // mapping. Rather than print the internal slot. So it doesn't matter that the internal
275228 // slot doesn't line up.
276229 const fakeError = new Error ( '' ) ;
277230 // In Chromium, only the stack property is printed but in Firefox the <name>:<message>
278- // gets printed so to make the colon make sense, we name it so we print Component Stack:
231+ // gets printed so to make the colon make sense, we name it so we print Stack:
279232 // and similarly Safari leave an expandable slot.
280233 fakeError . name = enableOwnerStacks
281234 ? 'Stack'
@@ -289,6 +242,7 @@ export function patch({
289242 ? 'Error Stack:'
290243 : 'Error Component Stack:' ) + componentStack
291244 : componentStack ;
245+
292246 if ( alreadyHasComponentStack ) {
293247 // Only modify the component stack if it matches what we would've added anyway.
294248 // Otherwise we assume it was a non-React stack.
@@ -324,15 +278,15 @@ export function patch({
324278 }
325279 }
326280 }
281+ // Don't add stacks from other renderers.
282+ break ;
327283 }
328- } catch ( error ) {
329- // Don't let a DevTools or React internal error interfere with logging.
330- setTimeout ( ( ) => {
331- throw error ;
332- } , 0 ) ;
333- } finally {
334- break ;
335284 }
285+ } catch ( error ) {
286+ // Don't let a DevTools or React internal error interfere with logging.
287+ setTimeout ( ( ) => {
288+ throw error ;
289+ } , 0 ) ;
336290 }
337291 }
338292
0 commit comments