[Draft] don't patch console during first render (#22308)
Previously, DevTools always overrode the native console to dim or supress StrictMode double logging. It also overrode console.log (in addition to console.error and console.warn). However, this changes the location shown by the browser console, which causes a bad developer experience. There is currently a TC39 proposal that would allow us to extend console without breaking developer experience, but in the meantime this PR changes the StrictMode console override behavior so that we only patch the console during the StrictMode double render so that, during the first render, the location points to developer code rather than our DevTools console code.
Luna Ruan committed
Sep 21, 2021 at 15:00 UTC
5b57bc6e31689ad715161cdcfa1b1d31eac2ba3b
18 files changed
+676
-454
packages/react-devtools-shared/src/__tests__/console-test.js
+172
-50
@@ -17,11 +17,10 @@ let mockLog;
17
let mockWarn;
18
let patchConsole;
19
let unpatchConsole;
20
+let rendererID;
21
22
describe('console', () => {
23
beforeEach(() => {
23
- jest.resetModules();
24
-
24
const Console = require('react-devtools-shared/src/backend/console');
25
patchConsole = Console.patch;
26
unpatchConsole = Console.unpatch;
@@ -41,21 +40,16 @@ describe('console', () => {
40
};
41
42
Console.dangerous_setTargetConsoleForTesting(fakeConsole);
44
-
45
- // Note the Console module only patches once,
46
- // so it's important to patch the test console before injection.
47
- patchConsole({
48
- appendComponentStack: true,
49
- breakOnWarn: false,
50
- showInlineWarningsAndErrors: false,
51
- hideDoubleLogsInStrictLegacy: false,
52
- });
43
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__.dangerous_setTargetConsoleForTesting(
44
+ fakeConsole,
45
+ );
46
47
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
48
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
56
- inject(internals);
49
+ rendererID = inject(internals);
50
51
Console.registerRenderer(internals);
52
+ return rendererID;
53
};
54
55
React = require('react');
@@ -78,7 +72,7 @@ describe('console', () => {
72
it('should not patch console methods that are not explicitly overriden', () => {
73
expect(fakeConsole.error).not.toBe(mockError);
74
expect(fakeConsole.info).toBe(mockInfo);
81
- expect(fakeConsole.log).not.toBe(mockLog);
75
+ expect(fakeConsole.log).toBe(mockLog);
76
expect(fakeConsole.warn).not.toBe(mockWarn);
77
});
78
@@ -90,7 +84,7 @@ describe('console', () => {
84
85
patchConsole({
86
appendComponentStack: true,
93
- breakOnWarn: false,
87
+ breakOnConsoleErrors: false,
88
showInlineWarningsAndErrors: false,
89
});
90
@@ -98,7 +92,7 @@ describe('console', () => {
92
expect(fakeConsole.warn).not.toBe(mockWarn);
93
});
94
101
- it('should patch the console when breakOnWarn is enabled', () => {
95
+ it('should patch the console when breakOnConsoleErrors is enabled', () => {
96
unpatchConsole();
97
98
expect(fakeConsole.error).toBe(mockError);
@@ -106,7 +100,7 @@ describe('console', () => {
100
101
patchConsole({
102
appendComponentStack: false,
109
- breakOnWarn: true,
103
+ breakOnConsoleErrors: true,
104
showInlineWarningsAndErrors: false,
105
});
106
@@ -122,7 +116,7 @@ describe('console', () => {
116
117
patchConsole({
118
appendComponentStack: false,
125
- breakOnWarn: false,
119
+ breakOnConsoleErrors: false,
120
showInlineWarningsAndErrors: true,
121
});
122
@@ -135,7 +129,7 @@ describe('console', () => {
129
130
patchConsole({
131
appendComponentStack: true,
138
- breakOnWarn: false,
132
+ breakOnConsoleErrors: false,
133
showInlineWarningsAndErrors: false,
134
});
135
@@ -172,6 +166,8 @@ describe('console', () => {
166
});
167
168
it('should not append multiple stacks', () => {
169
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
170
+
171
const Child = ({children}) => {
172
fakeConsole.warn('warn\n in Child (at fake.js:123)');
173
fakeConsole.error('error', '\n in Child (at fake.js:123)');
@@ -192,6 +188,8 @@ describe('console', () => {
188
});
189
190
it('should append component stacks to errors and warnings logged during render', () => {
191
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
192
+
193
const Intermediate = ({children}) => children;
194
const Parent = ({children}) => (
195
<Intermediate>
@@ -277,6 +275,8 @@ describe('console', () => {
275
});
276
277
it('should append component stacks to errors and warnings logged from commit hooks', () => {
278
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
279
+
280
const Intermediate = ({children}) => children;
281
const Parent = ({children}) => (
282
<Intermediate>
@@ -372,13 +372,14 @@ describe('console', () => {
372
});
373
374
it('should append stacks after being uninstalled and reinstalled', () => {
375
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
376
+
377
const Child = ({children}) => {
378
fakeConsole.warn('warn');
379
fakeConsole.error('error');
380
return null;
381
};
382
381
- unpatchConsole();
383
act(() => legacyRender(<Child />, document.createElement('div')));
384
385
expect(mockWarn).toHaveBeenCalledTimes(1);
@@ -390,7 +391,7 @@ describe('console', () => {
391
392
patchConsole({
393
appendComponentStack: true,
393
- breakOnWarn: false,
394
+ breakOnConsoleErrors: false,
395
showInlineWarningsAndErrors: false,
396
});
397
act(() => legacyRender(<Child />, document.createElement('div')));
@@ -410,6 +411,8 @@ describe('console', () => {
411
});
412
413
it('should be resilient to prepareStackTrace', () => {
414
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
415
+
416
Error.prepareStackTrace = function(error, callsites) {
417
const stack = ['An error occurred:', error.message];
418
for (let i = 0; i < callsites.length; i++) {
@@ -469,6 +472,9 @@ describe('console', () => {
472
});
473
474
it('should double log if hideConsoleLogsInStrictMode is disabled in Strict mode', () => {
475
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
476
+ global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
477
+
478
const container = document.createElement('div');
479
const root = ReactDOM.createRoot(container);
480
@@ -479,13 +485,6 @@ describe('console', () => {
485
return <div />;
486
}
487
482
- patchConsole({
483
- appendComponentStack: false,
484
- breakOnWarn: false,
485
- showInlineWarningsAndErrors: false,
486
- hideConsoleLogsInStrictMode: false,
487
- });
488
-
488
act(() =>
489
root.render(
490
<React.StrictMode>
@@ -493,8 +492,6 @@ describe('console', () => {
492
</React.StrictMode>,
493
),
494
);
496
-
497
- expect(mockLog).toHaveBeenCalledTimes(2);
495
expect(mockLog.mock.calls[0]).toHaveLength(1);
496
expect(mockLog.mock.calls[0][0]).toBe('log');
497
expect(mockLog.mock.calls[1]).toHaveLength(2);
@@ -514,6 +511,9 @@ describe('console', () => {
511
});
512
513
it('should not double log if hideConsoleLogsInStrictMode is enabled in Strict mode', () => {
514
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
515
+ global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;
516
+
517
const container = document.createElement('div');
518
const root = ReactDOM.createRoot(container);
519
@@ -524,12 +524,47 @@ describe('console', () => {
524
return <div />;
525
}
526
527
- patchConsole({
528
- appendComponentStack: false,
529
- breakOnWarn: false,
530
- showInlineWarningsAndErrors: false,
531
- hideConsoleLogsInStrictMode: true,
532
- });
527
+ act(() =>
528
+ root.render(
529
+ <React.StrictMode>
530
+ <App />
531
+ </React.StrictMode>,
532
+ ),
533
+ );
534
+
535
+ expect(mockLog).toHaveBeenCalledTimes(1);
536
+ expect(mockLog.mock.calls[0]).toHaveLength(1);
537
+ expect(mockLog.mock.calls[0][0]).toBe('log');
538
+
539
+ expect(mockWarn).toHaveBeenCalledTimes(1);
540
+ expect(mockWarn.mock.calls[0]).toHaveLength(1);
541
+ expect(mockWarn.mock.calls[0][0]).toBe('warn');
542
+
543
+ expect(mockError).toHaveBeenCalledTimes(1);
544
+ expect(mockError.mock.calls[0]).toHaveLength(1);
545
+ expect(mockError.mock.calls[0][0]).toBe('error');
546
+ });
547
+
548
+ it('should double log in Strict mode initial render for extension', () => {
549
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
550
+ global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
551
+
552
+ // This simulates a render that happens before React DevTools have finished
553
+ // their handshake to attach the React DOM renderer functions to DevTools
554
+ // In this case, we should still be able to mock the console in Strict mode
555
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
556
+ rendererID,
557
+ null,
558
+ );
559
+ const container = document.createElement('div');
560
+ const root = ReactDOM.createRoot(container);
561
+
562
+ function App() {
563
+ fakeConsole.log('log');
564
+ fakeConsole.warn('warn');
565
+ fakeConsole.error('error');
566
+ return <div />;
567
+ }
568
569
act(() =>
570
root.render(
@@ -539,6 +574,53 @@ describe('console', () => {
574
),
575
);
576
577
+ expect(mockLog).toHaveBeenCalledTimes(2);
578
+ expect(mockLog.mock.calls[0]).toHaveLength(1);
579
+ expect(mockLog.mock.calls[0][0]).toBe('log');
580
+ expect(mockLog.mock.calls[1]).toHaveLength(2);
581
+ expect(mockLog.mock.calls[1][0]).toBe('%clog');
582
+
583
+ expect(mockWarn).toHaveBeenCalledTimes(2);
584
+ expect(mockWarn.mock.calls[0]).toHaveLength(1);
585
+ expect(mockWarn.mock.calls[0][0]).toBe('warn');
586
+ expect(mockWarn.mock.calls[1]).toHaveLength(2);
587
+ expect(mockWarn.mock.calls[1][0]).toBe('%cwarn');
588
+
589
+ expect(mockError).toHaveBeenCalledTimes(2);
590
+ expect(mockError.mock.calls[0]).toHaveLength(1);
591
+ expect(mockError.mock.calls[0][0]).toBe('error');
592
+ expect(mockError.mock.calls[1]).toHaveLength(2);
593
+ expect(mockError.mock.calls[1][0]).toBe('%cerror');
594
+ });
595
+
596
+ it('should not double log in Strict mode initial render for extension', () => {
597
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
598
+ global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;
599
+
600
+ // This simulates a render that happens before React DevTools have finished
601
+ // their handshake to attach the React DOM renderer functions to DevTools
602
+ // In this case, we should still be able to mock the console in Strict mode
603
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
604
+ rendererID,
605
+ null,
606
+ );
607
+ const container = document.createElement('div');
608
+ const root = ReactDOM.createRoot(container);
609
+
610
+ function App() {
611
+ fakeConsole.log('log');
612
+ fakeConsole.warn('warn');
613
+ fakeConsole.error('error');
614
+ return <div />;
615
+ }
616
+
617
+ act(() =>
618
+ root.render(
619
+ <React.StrictMode>
620
+ <App />
621
+ </React.StrictMode>,
622
+ ),
623
+ );
624
expect(mockLog).toHaveBeenCalledTimes(1);
625
expect(mockLog.mock.calls[0]).toHaveLength(1);
626
expect(mockLog.mock.calls[0][0]).toBe('log');
@@ -551,6 +633,56 @@ describe('console', () => {
633
expect(mockError.mock.calls[0]).toHaveLength(1);
634
expect(mockError.mock.calls[0][0]).toBe('error');
635
});
636
+
637
+ it('should properly dim component stacks during strict mode double log', () => {
638
+ global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
639
+ global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
640
+
641
+ const container = document.createElement('div');
642
+ const root = ReactDOM.createRoot(container);
643
+
644
+ const Intermediate = ({children}) => children;
645
+ const Parent = ({children}) => (
646
+ <Intermediate>
647
+ <Child />
648
+ </Intermediate>
649
+ );
650
+ const Child = ({children}) => {
651
+ fakeConsole.error('error');
652
+ fakeConsole.warn('warn');
653
+ return null;
654
+ };
655
+
656
+ act(() =>
657
+ root.render(
658
+ <React.StrictMode>
659
+ <Parent />
660
+ </React.StrictMode>,
661
+ ),
662
+ );
663
+
664
+ expect(mockWarn).toHaveBeenCalledTimes(2);
665
+ expect(mockWarn.mock.calls[0]).toHaveLength(2);
666
+ expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
667
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
668
+ );
669
+ expect(mockWarn.mock.calls[1]).toHaveLength(2);
670
+ expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][0])).toEqual(
671
+ '%cwarn \n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
672
+ );
673
+ expect(mockWarn.mock.calls[1][1]).toMatch('color: rgba(');
674
+
675
+ expect(mockError).toHaveBeenCalledTimes(2);
676
+ expect(mockError.mock.calls[0]).toHaveLength(2);
677
+ expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toEqual(
678
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
679
+ );
680
+ expect(mockError.mock.calls[1]).toHaveLength(2);
681
+ expect(normalizeCodeLocInfo(mockError.mock.calls[1][0])).toEqual(
682
+ '%cerror \n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
683
+ );
684
+ expect(mockError.mock.calls[1][1]).toMatch('color: rgba(');
685
+ });
686
});
687
688
describe('console error', () => {
@@ -577,23 +709,13 @@ describe('console error', () => {
709
710
Console.dangerous_setTargetConsoleForTesting(fakeConsole);
711
580
- // Note the Console module only patches once,
581
- // so it's important to patch the test console before injection.
582
- patchConsole({
583
- appendComponentStack: true,
584
- breakOnWarn: false,
585
- showInlineWarningsAndErrors: false,
586
- hideDoubleLogsInStrictLegacy: false,
587
- });
588
-
712
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
713
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
591
- internals.getIsStrictMode = () => {
592
- throw Error('foo');
593
- };
714
inject(internals);
715
596
- Console.registerRenderer(internals);
716
+ Console.registerRenderer(internals, () => {
717
+ throw Error('foo');
718
+ });
719
};
720
721
React = require('react');
@@ -617,8 +739,8 @@ describe('console error', () => {
739
740
patchConsole({
741
appendComponentStack: true,
620
- breakOnWarn: false,
621
- showInlineWarningsAndErrors: false,
742
+ breakOnConsoleErrors: false,
743
+ showInlineWarningsAndErrors: true,
744
hideConsoleLogsInStrictMode: false,
745
});
746
packages/react-devtools-shared/src/backend/console.js
+200
-137
@@ -16,7 +16,7 @@ import {getInternalReactConstants} from './renderer';
16
import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
17
import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags';
18
19
-const OVERRIDE_CONSOLE_METHODS = ['error', 'trace', 'warn', 'log'];
19
+const OVERRIDE_CONSOLE_METHODS = ['error', 'trace', 'warn'];
20
const DIMMED_NODE_CONSOLE_COLOR = '\x1b[2m%s\x1b[0m';
21
22
// React's custom built component stack strings match "\s{4}in"
@@ -30,6 +30,37 @@ export function isStringComponentStack(text: string): boolean {
30
return PREFIX_REGEX.test(text) || ROW_COLUMN_NUMBER_REGEX.test(text);
31
}
32
33
+const STYLE_DIRECTIVE_REGEX = /^%c/;
34
+
35
+// This function tells whether or not the arguments for a console
36
+// method has been overridden by the patchForStrictMode function.
37
+// If it has we'll need to do some special formatting of the arguments
38
+// so the console color stays consistent
39
+function isStrictModeOverride(args: Array<string>, method: string): boolean {
40
+ return (
41
+ args.length === 2 &&
42
+ STYLE_DIRECTIVE_REGEX.test(args[0]) &&
43
+ args[1] === `color: ${getConsoleColor(method) || ''}`
44
+ );
45
+}
46
+
47
+function getConsoleColor(method: string): ?string {
48
+ switch (method) {
49
+ case 'warn':
50
+ return consoleSettingsRef.browserTheme === 'light'
51
+ ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR
52
+ : process.env.DARK_MODE_DIMMED_WARNING_COLOR;
53
+ case 'error':
54
+ return consoleSettingsRef.browserTheme === 'light'
55
+ ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR
56
+ : process.env.DARK_MODE_DIMMED_ERROR_COLOR;
57
+ case 'log':
58
+ default:
59
+ return consoleSettingsRef.browserTheme === 'light'
60
+ ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR
61
+ : process.env.DARK_MODE_DIMMED_LOG_COLOR;
62
+ }
63
+}
64
type OnErrorOrWarning = (
65
fiber: Fiber,
66
type: 'error' | 'warn',
@@ -43,7 +74,6 @@ const injectedRenderers: Map<
74
getCurrentFiber: () => Fiber | null,
75
onErrorOrWarning: ?OnErrorOrWarning,
76
workTagMap: WorkTagMap,
46
- getIsStrictMode: ?() => boolean,
77
|},
78
> = new Map();
79
@@ -82,7 +112,6 @@ export function registerRenderer(
112
const {
113
currentDispatcherRef,
114
getCurrentFiber,
85
- getIsStrictMode,
115
findFiberByHostInstance,
116
version,
117
} = renderer;
@@ -100,7 +129,6 @@ export function registerRenderer(
129
injectedRenderers.set(renderer, {
130
currentDispatcherRef,
131
getCurrentFiber,
103
- getIsStrictMode,
132
workTagMap: ReactTypeOfWork,
133
onErrorOrWarning,
134
});
@@ -112,11 +140,11 @@ const consoleSettingsRef = {
140
breakOnConsoleErrors: false,
141
showInlineWarningsAndErrors: false,
142
hideConsoleLogsInStrictMode: false,
143
+ browserTheme: 'dark',
144
};
145
146
// Patches console methods to append component stack for the current fiber.
147
// Call unpatch() to remove the injected behavior.
119
-// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInExtension
148
export function patch({
149
appendComponentStack,
150
breakOnConsoleErrors,
@@ -136,141 +164,176 @@ export function patch({
164
consoleSettingsRef.breakOnConsoleErrors = breakOnConsoleErrors;
165
consoleSettingsRef.showInlineWarningsAndErrors = showInlineWarningsAndErrors;
166
consoleSettingsRef.hideConsoleLogsInStrictMode = hideConsoleLogsInStrictMode;
167
+ consoleSettingsRef.browserTheme = browserTheme;
168
+
169
+ if (
170
+ appendComponentStack ||
171
+ breakOnConsoleErrors ||
172
+ showInlineWarningsAndErrors
173
+ ) {
174
+ if (unpatchFn !== null) {
175
+ // Don't patch twice.
176
+ return;
177
+ }
178
140
- if (unpatchFn !== null) {
141
- // Don't patch twice.
142
- return;
143
- }
179
+ const originalConsoleMethods = {};
180
145
- const originalConsoleMethods = {};
181
+ unpatchFn = () => {
182
+ for (const method in originalConsoleMethods) {
183
+ try {
184
+ // $FlowFixMe property error|warn is not writable.
185
+ targetConsole[method] = originalConsoleMethods[method];
186
+ } catch (error) {}
187
+ }
188
+ };
189
147
- unpatchFn = () => {
148
- for (const method in originalConsoleMethods) {
190
+ OVERRIDE_CONSOLE_METHODS.forEach(method => {
191
try {
150
- // $FlowFixMe property error|warn is not writable.
151
- targetConsole[method] = originalConsoleMethods[method];
152
- } catch (error) {}
153
- }
154
- };
155
-
156
- OVERRIDE_CONSOLE_METHODS.forEach(method => {
157
- try {
158
- const originalMethod = (originalConsoleMethods[method] = targetConsole[
159
- method
160
- ].__REACT_DEVTOOLS_ORIGINAL_METHOD__
161
- ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__
162
- : targetConsole[method]);
163
-
164
- const overrideMethod = (...args) => {
165
- let shouldAppendWarningStack = false;
166
- if (method !== 'log') {
167
- if (consoleSettingsRef.appendComponentStack) {
168
- const lastArg = args.length > 0 ? args[args.length - 1] : null;
169
- const alreadyHasComponentStack =
170
- typeof lastArg === 'string' && isStringComponentStack(lastArg);
171
-
172
- // If we are ever called with a string that already has a component stack,
173
- // e.g. a React error/warning, don't append a second stack.
174
- shouldAppendWarningStack = !alreadyHasComponentStack;
192
+ const originalMethod = (originalConsoleMethods[method] = targetConsole[
193
+ method
194
+ ].__REACT_DEVTOOLS_ORIGINAL_METHOD__
195
+ ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__
196
+ : targetConsole[method]);
197
+
198
+ const overrideMethod = (...args) => {
199
+ let shouldAppendWarningStack = false;
200
+ if (method !== 'log') {
201
+ if (consoleSettingsRef.appendComponentStack) {
202
+ const lastArg = args.length > 0 ? args[args.length - 1] : null;
203
+ const alreadyHasComponentStack =
204
+ typeof lastArg === 'string' && isStringComponentStack(lastArg);
205
+
206
+ // If we are ever called with a string that already has a component stack,
207
+ // e.g. a React error/warning, don't append a second stack.
208
+ shouldAppendWarningStack = !alreadyHasComponentStack;
209
+ }
210
}
176
- }
177
-
178
- const shouldShowInlineWarningsAndErrors =
179
- consoleSettingsRef.showInlineWarningsAndErrors &&
180
- (method === 'error' || method === 'warn');
181
-
182
- let isInStrictMode = false;
183
-
184
- // Search for the first renderer that has a current Fiber.
185
- // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
186
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
187
- for (const {
188
- currentDispatcherRef,
189
- getCurrentFiber,
190
- onErrorOrWarning,
191
- workTagMap,
192
- getIsStrictMode,
193
- } of injectedRenderers.values()) {
194
- const current: ?Fiber = getCurrentFiber();
195
- if (current != null) {
196
- try {
197
- if (typeof getIsStrictMode === 'function' && getIsStrictMode()) {
198
- isInStrictMode = true;
199
- }
211
201
- if (shouldShowInlineWarningsAndErrors) {
202
- // patch() is called by two places: (1) the hook and (2) the renderer backend.
203
- // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning.
204
- if (typeof onErrorOrWarning === 'function') {
205
- onErrorOrWarning(
206
- current,
207
- ((method: any): 'error' | 'warn'),
208
- // Copy args before we mutate them (e.g. adding the component stack)
209
- args.slice(),
210
- );
212
+ const shouldShowInlineWarningsAndErrors =
213
+ consoleSettingsRef.showInlineWarningsAndErrors &&
214
+ (method === 'error' || method === 'warn');
215
+
216
+ // Search for the first renderer that has a current Fiber.
217
+ // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
218
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
219
+ for (const {
220
+ currentDispatcherRef,
221
+ getCurrentFiber,
222
+ onErrorOrWarning,
223
+ workTagMap,
224
+ } of injectedRenderers.values()) {
225
+ const current: ?Fiber = getCurrentFiber();
226
+ if (current != null) {
227
+ try {
228
+ if (shouldShowInlineWarningsAndErrors) {
229
+ // patch() is called by two places: (1) the hook and (2) the renderer backend.
230
+ // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning.
231
+ if (typeof onErrorOrWarning === 'function') {
232
+ onErrorOrWarning(
233
+ current,
234
+ ((method: any): 'error' | 'warn'),
235
+ // Copy args before we mutate them (e.g. adding the component stack)
236
+ args.slice(),
237
+ );
238
+ }
239
}
212
- }
240
214
- if (shouldAppendWarningStack) {
215
- const componentStack = getStackByFiberInDevAndProd(
216
- workTagMap,
217
- current,
218
- currentDispatcherRef,
219
- );
220
- if (componentStack !== '') {
221
- args.push(componentStack);
241
+ if (shouldAppendWarningStack) {
242
+ const componentStack = getStackByFiberInDevAndProd(
243
+ workTagMap,
244
+ current,
245
+ currentDispatcherRef,
246
+ );
247
+ if (componentStack !== '') {
248
+ if (isStrictModeOverride(args, method)) {
249
+ args[0] = format(args[0], componentStack);
250
+ } else {
251
+ args.push(componentStack);
252
+ }
253
+ }
254
}
255
+ } catch (error) {
256
+ // Don't let a DevTools or React internal error interfere with logging.
257
+ setTimeout(() => {
258
+ throw error;
259
+ }, 0);
260
+ } finally {
261
+ break;
262
}
224
- } catch (error) {
225
- // Don't let a DevTools or React internal error interfere with logging.
226
- setTimeout(() => {
227
- throw error;
228
- }, 0);
229
- } finally {
230
- break;
263
}
264
}
233
- }
234
-
235
- if (consoleSettingsRef.breakOnConsoleErrors) {
236
- // --- Welcome to debugging with React DevTools ---
237
- // This debugger statement means that you've enabled the "break on warnings" feature.
238
- // Use the browser's Call Stack panel to step out of this override function-
239
- // to where the original warning or error was logged.
240
- // eslint-disable-next-line no-debugger
241
- debugger;
242
- }
243
-
244
- if (consoleManagedByDevToolsDuringStrictMode && isInStrictMode) {
265
+
266
+ if (consoleSettingsRef.breakOnConsoleErrors) {
267
+ // --- Welcome to debugging with React DevTools ---
268
+ // This debugger statement means that you've enabled the "break on warnings" feature.
269
+ // Use the browser's Call Stack panel to step out of this override function-
270
+ // to where the original warning or error was logged.
271
+ // eslint-disable-next-line no-debugger
272
+ debugger;
273
+ }
274
+
275
+ originalMethod(...args);
276
+ };
277
+
278
+ overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod;
279
+ originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod;
280
+
281
+ // $FlowFixMe property error|warn is not writable.
282
+ targetConsole[method] = overrideMethod;
283
+ } catch (error) {}
284
+ });
285
+ } else {
286
+ unpatch();
287
+ }
288
+}
289
+
290
+// Removed component stack patch from console methods.
291
+export function unpatch(): void {
292
+ if (unpatchFn !== null) {
293
+ unpatchFn();
294
+ unpatchFn = null;
295
+ }
296
+}
297
+
298
+let unpatchForStrictModeFn: null | (() => void) = null;
299
+
300
+// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInStrictMode
301
+export function patchForStrictMode() {
302
+ if (consoleManagedByDevToolsDuringStrictMode) {
303
+ const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
304
+
305
+ if (unpatchForStrictModeFn !== null) {
306
+ // Don't patch twice.
307
+ return;
308
+ }
309
+
310
+ const originalConsoleMethods = {};
311
+
312
+ unpatchForStrictModeFn = () => {
313
+ for (const method in originalConsoleMethods) {
314
+ try {
315
+ // $FlowFixMe property error|warn is not writable.
316
+ targetConsole[method] = originalConsoleMethods[method];
317
+ } catch (error) {}
318
+ }
319
+ };
320
+
321
+ overrideConsoleMethods.forEach(method => {
322
+ try {
323
+ const originalMethod = (originalConsoleMethods[method] = targetConsole[
324
+ method
325
+ ].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__
326
+ ? targetConsole[method].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__
327
+ : targetConsole[method]);
328
+
329
+ const overrideMethod = (...args) => {
330
if (!consoleSettingsRef.hideConsoleLogsInStrictMode) {
331
// Dim the text color of the double logs if we're not
332
// hiding them.
333
if (isNode) {
334
originalMethod(DIMMED_NODE_CONSOLE_COLOR, format(...args));
335
} else {
251
- let color;
252
- switch (method) {
253
- case 'warn':
254
- color =
255
- browserTheme === 'light'
256
- ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR
257
- : process.env.DARK_MODE_DIMMED_WARNING_COLOR;
258
- break;
259
- case 'error':
260
- color =
261
- browserTheme === 'light'
262
- ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR
263
- : process.env.DARK_MODE_DIMMED_ERROR_COLOR;
264
- break;
265
- case 'log':
266
- default:
267
- color =
268
- browserTheme === 'light'
269
- ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR
270
- : process.env.DARK_MODE_DIMMED_LOG_COLOR;
271
- break;
272
- }
273
-
336
+ const color = getConsoleColor(method);
337
if (color) {
338
originalMethod(`%c${format(...args)}`, `color: ${color}`);
339
} else {
@@ -278,24 +341,24 @@ export function patch({
341
}
342
}
343
}
281
- } else {
282
- originalMethod(...args);
283
- }
284
- };
344
+ };
345
286
- overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod;
287
- originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod;
346
+ overrideMethod.__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ = originalMethod;
347
+ originalMethod.__REACT_DEVTOOLS_STRICT_MODE_OVERRIDE_METHOD__ = overrideMethod;
348
289
- // $FlowFixMe property error|warn is not writable.
290
- targetConsole[method] = overrideMethod;
291
- } catch (error) {}
292
- });
349
+ // $FlowFixMe property error|warn is not writable.
350
+ targetConsole[method] = overrideMethod;
351
+ } catch (error) {}
352
+ });
353
+ }
354
}
355
295
-// Removed component stack patch from console methods.
296
-export function unpatch(): void {
297
- if (unpatchFn !== null) {
298
- unpatchFn();
299
- unpatchFn = null;
356
+// NOTE: KEEP IN SYNC with src/hook.js:unpatchConsoleForInitialRenderInStrictMode
357
+export function unpatchForStrictMode(): void {
358
+ if (consoleManagedByDevToolsDuringStrictMode) {
359
+ if (unpatchForStrictModeFn !== null) {
360
+ unpatchForStrictModeFn();
361
+ unpatchForStrictModeFn = null;
362
+ }
363
}
364
}
packages/react-devtools-shared/src/backend/legacy/renderer.js
+6
@@ -1073,6 +1073,10 @@ export function attach(
1073
// Not implemented
1074
}
1075
1076
+ function patchConsoleForStrictMode() {}
1077
+
1078
+ function unpatchConsoleForStrictMode() {}
1079
+
1080
return {
1081
clearErrorsAndWarnings,
1082
clearErrorsForFiberID,
@@ -1101,6 +1105,7 @@ export function attach(
1105
overrideSuspense,
1106
overrideValueAtPath,
1107
renamePath,
1108
+ patchConsoleForStrictMode,
1109
prepareViewAttributeSource,
1110
prepareViewElementSource,
1111
renderer,
@@ -1109,6 +1114,7 @@ export function attach(
1114
startProfiling,
1115
stopProfiling,
1116
storeAsGlobal,
1117
+ unpatchConsoleForStrictMode,
1118
updateComponentFilters,
1119
};
1120
}
packages/react-devtools-shared/src/backend/renderer.js
+4
@@ -59,6 +59,8 @@ import {inspectHooksOfFiber} from 'react-debug-tools';
59
import {
60
patch as patchConsole,
61
registerRenderer as registerRendererWithConsole,
62
+ patchForStrictMode as patchConsoleForStrictMode,
63
+ unpatchForStrictMode as unpatchConsoleForStrictMode,
64
} from './console';
65
import {
66
CONCURRENT_MODE_NUMBER,
@@ -4249,6 +4251,7 @@ export function attach(
4251
handlePostCommitFiberRoot,
4252
inspectElement,
4253
logElementToConsole,
4254
+ patchConsoleForStrictMode,
4255
prepareViewAttributeSource,
4256
prepareViewElementSource,
4257
overrideError,
@@ -4261,6 +4264,7 @@ export function attach(
4264
startProfiling,
4265
stopProfiling,
4266
storeAsGlobal,
4267
+ unpatchConsoleForStrictMode,
4268
updateComponentFilters,
4269
};
4270
}
packages/react-devtools-shared/src/backend/types.js
+5
-2
@@ -136,8 +136,6 @@ export type ReactRenderer = {
136
// Only injected by React v16.9+ in DEV mode.
137
// Enables DevTools to append owners-only component stack to error messages.
138
getCurrentFiber?: () => Fiber | null,
139
-
140
- getIsStrictMode?: () => boolean,
139
// 17.0.2+
140
reconcilerVersion?: string,
141
// Uniquely identifies React DOM v15.
@@ -352,6 +350,7 @@ export type RendererInterface = {
350
path: Array<string | number>,
351
value: any,
352
) => void,
353
+ patchConsoleForStrictMode: () => void,
354
prepareViewAttributeSource: (
355
id: number,
356
path: Array<string | number>,
@@ -374,6 +373,7 @@ export type RendererInterface = {
373
path: Array<string | number>,
374
count: number,
375
) => void,
376
+ unpatchConsoleForStrictMode: () => void,
377
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
378
...
379
};
@@ -408,5 +408,8 @@ export type DevToolsHook = {
408
// Added in v16.9 to support Fast Refresh
409
didError?: boolean,
410
) => void,
411
+
412
+ // Testing
413
+ dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
414
...
415
};
packages/react-devtools-shared/src/hook.js
+189
-161
@@ -8,15 +8,12 @@
8
* @flow
9
*/
10
11
-import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
12
-import type {ReactRenderer} from './backend/types';
11
import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools';
12
13
import {
14
patch as patchConsole,
15
registerRenderer as registerRendererWithConsole,
16
} from './backend/console';
19
-import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags';
17
18
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
19
@@ -27,6 +24,23 @@ export function installHook(target: any): DevToolsHook | null {
24
return null;
25
}
26
27
+ let targetConsole: Object = console;
28
+ let targetConsoleMethods = {};
29
+ for (const method in console) {
30
+ targetConsoleMethods[method] = console[method];
31
+ }
32
+
33
+ function dangerous_setTargetConsoleForTesting(
34
+ targetConsoleForTesting: Object,
35
+ ): void {
36
+ targetConsole = targetConsoleForTesting;
37
+
38
+ targetConsoleMethods = {};
39
+ for (const method in targetConsole) {
40
+ targetConsoleMethods[method] = console[method];
41
+ }
42
+ }
43
+
44
function detectReactBuildType(renderer) {
45
try {
46
if (typeof renderer.version === 'string') {
@@ -163,156 +177,148 @@ export function installHook(target: any): DevToolsHook | null {
177
maybeMessage: any,
178
...inputArgs: $ReadOnlyArray<any>
179
): string {
166
- if (consoleManagedByDevToolsDuringStrictMode) {
167
- const args = inputArgs.slice();
168
-
169
- // Symbols cannot be concatenated with Strings.
170
- let formatted: string =
171
- typeof maybeMessage === 'symbol'
172
- ? maybeMessage.toString()
173
- : '' + maybeMessage;
174
-
175
- // If the first argument is a string, check for substitutions.
176
- if (typeof maybeMessage === 'string') {
177
- if (args.length) {
178
- const REGEXP = /(%?)(%([jds]))/g;
179
-
180
- formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => {
181
- let arg = args.shift();
182
- switch (flag) {
183
- case 's':
184
- arg += '';
185
- break;
186
- case 'd':
187
- case 'i':
188
- arg = parseInt(arg, 10).toString();
189
- break;
190
- case 'f':
191
- arg = parseFloat(arg).toString();
192
- break;
193
- }
194
- if (!escaped) {
195
- return arg;
196
- }
197
- args.unshift(arg);
198
- return match;
199
- });
200
- }
201
- }
180
+ const args = inputArgs.slice();
181
203
- // Arguments that remain after formatting.
204
- if (args.length) {
205
- for (let i = 0; i < args.length; i++) {
206
- const arg = args[i];
182
+ // Symbols cannot be concatenated with Strings.
183
+ let formatted: string =
184
+ typeof maybeMessage === 'symbol'
185
+ ? maybeMessage.toString()
186
+ : '' + maybeMessage;
187
208
- // Symbols cannot be concatenated with Strings.
209
- formatted += ' ' + (typeof arg === 'symbol' ? arg.toString() : arg);
210
- }
188
+ // If the first argument is a string, check for substitutions.
189
+ if (typeof maybeMessage === 'string') {
190
+ if (args.length) {
191
+ const REGEXP = /(%?)(%([jds]))/g;
192
+
193
+ formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => {
194
+ let arg = args.shift();
195
+ switch (flag) {
196
+ case 's':
197
+ arg += '';
198
+ break;
199
+ case 'd':
200
+ case 'i':
201
+ arg = parseInt(arg, 10).toString();
202
+ break;
203
+ case 'f':
204
+ arg = parseFloat(arg).toString();
205
+ break;
206
+ }
207
+ if (!escaped) {
208
+ return arg;
209
+ }
210
+ args.unshift(arg);
211
+ return match;
212
+ });
213
}
214
+ }
215
213
- // Update escaped %% values.
214
- formatted = formatted.replace(/%{2,2}/g, '%');
216
+ // Arguments that remain after formatting.
217
+ if (args.length) {
218
+ for (let i = 0; i < args.length; i++) {
219
+ const arg = args[i];
220
216
- return '' + formatted;
221
+ // Symbols cannot be concatenated with Strings.
222
+ formatted += ' ' + (typeof arg === 'symbol' ? arg.toString() : arg);
223
+ }
224
}
225
219
- return '';
226
+ // Update escaped %% values.
227
+ formatted = formatted.replace(/%{2,2}/g, '%');
228
+
229
+ return '' + formatted;
230
}
231
222
- // NOTE: KEEP IN SYNC with src/backend/console.js:patch
223
- function patchConsoleForInitialRenderInExtension(
224
- renderer: ReactRenderer,
225
- {
226
- hideConsoleLogsInStrictMode,
227
- browserTheme,
228
- }: {hideConsoleLogsInStrictMode: boolean, browserTheme: BrowserTheme},
229
- ): void {
230
- if (consoleManagedByDevToolsDuringStrictMode) {
231
- const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
232
-
233
- if (__EXTENSION__) {
234
- const targetConsole = console;
235
-
236
- const originalConsoleMethods = {};
237
-
238
- overrideConsoleMethods.forEach(method => {
239
- try {
240
- const originalMethod = (originalConsoleMethods[
241
- method
242
- ] = targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__
243
- ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__
244
- : targetConsole[method]);
245
-
246
- const overrideMethod = (...args) => {
247
- let isInStrictMode = false;
248
-
249
- // Search for the first renderer that has a current Fiber.
250
- // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
251
- const {getCurrentFiber, getIsStrictMode} = renderer;
252
- if (typeof getCurrentFiber !== 'function') {
253
- return;
254
- }
255
-
256
- const current: ?Fiber = getCurrentFiber();
257
- if (current != null) {
258
- try {
259
- if (
260
- typeof getIsStrictMode === 'function' &&
261
- getIsStrictMode()
262
- ) {
263
- isInStrictMode = true;
264
- }
265
- } catch (error) {
266
- // Don't let a DevTools or React internal error interfere with logging.
267
- }
268
- }
269
-
270
- if (isInStrictMode) {
271
- if (!hideConsoleLogsInStrictMode) {
272
- // Dim the text color of the double logs if we're not
273
- // hiding them.
274
- let color;
275
- switch (method) {
276
- case 'warn':
277
- color =
278
- browserTheme === 'light'
279
- ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR
280
- : process.env.DARK_MODE_DIMMED_WARNING_COLOR;
281
- break;
282
- case 'error':
283
- color =
284
- browserTheme === 'light'
285
- ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR
286
- : process.env.DARK_MODE_DIMMED_ERROR_COLOR;
287
- break;
288
- case 'log':
289
- default:
290
- color =
291
- browserTheme === 'light'
292
- ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR
293
- : process.env.DARK_MODE_DIMMED_LOG_COLOR;
294
- break;
295
- }
296
-
297
- if (color) {
298
- originalMethod(`%c${format(...args)}`, `color: ${color}`);
299
- } else {
300
- throw Error('Console color is not defined');
301
- }
302
- }
303
- } else {
304
- originalMethod(...args);
305
- }
306
- };
307
-
308
- overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod;
309
- originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod;
310
-
311
- // $FlowFixMe property error|warn is not writable.
312
- targetConsole[method] = overrideMethod;
313
- } catch (error) {}
314
- });
232
+ let unpatchFn = null;
233
+
234
+ // NOTE: KEEP IN SYNC with src/backend/console.js:patchForStrictMode
235
+ // This function hides or dims console logs during the initial double renderer
236
+ // in Strict Mode. We need this function because during initial render,
237
+ // React and DevTools are connecting and the renderer interface isn't avaiable
238
+ // and we want to be able to have consistent logging behavior for double logs
239
+ // during the initial renderer.
240
+ function patchConsoleForInitialRenderInStrictMode({
241
+ hideConsoleLogsInStrictMode,
242
+ browserTheme,
243
+ }: {
244
+ hideConsoleLogsInStrictMode: boolean,
245
+ browserTheme: BrowserTheme,
246
+ }) {
247
+ const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
248
+
249
+ if (unpatchFn !== null) {
250
+ // Don't patch twice.
251
+ return;
252
+ }
253
+
254
+ const originalConsoleMethods = {};
255
+
256
+ unpatchFn = () => {
257
+ for (const method in originalConsoleMethods) {
258
+ try {
259
+ // $FlowFixMe property error|warn is not writable.
260
+ targetConsole[method] = originalConsoleMethods[method];
261
+ } catch (error) {}
262
}
263
+ };
264
+
265
+ overrideConsoleMethods.forEach(method => {
266
+ try {
267
+ const originalMethod = (originalConsoleMethods[method] = targetConsole[
268
+ method
269
+ ].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__
270
+ ? targetConsole[method].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__
271
+ : targetConsole[method]);
272
+
273
+ const overrideMethod = (...args) => {
274
+ if (!hideConsoleLogsInStrictMode) {
275
+ // Dim the text color of the double logs if we're not
276
+ // hiding them.
277
+ let color;
278
+ switch (method) {
279
+ case 'warn':
280
+ color =
281
+ browserTheme === 'light'
282
+ ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR
283
+ : process.env.DARK_MODE_DIMMED_WARNING_COLOR;
284
+ break;
285
+ case 'error':
286
+ color =
287
+ browserTheme === 'light'
288
+ ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR
289
+ : process.env.DARK_MODE_DIMMED_ERROR_COLOR;
290
+ break;
291
+ case 'log':
292
+ default:
293
+ color =
294
+ browserTheme === 'light'
295
+ ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR
296
+ : process.env.DARK_MODE_DIMMED_LOG_COLOR;
297
+ break;
298
+ }
299
+
300
+ if (color) {
301
+ originalMethod(`%c${format(...args)}`, `color: ${color}`);
302
+ } else {
303
+ throw Error('Console color is not defined');
304
+ }
305
+ }
306
+ };
307
+
308
+ overrideMethod.__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ = originalMethod;
309
+ originalMethod.__REACT_DEVTOOLS_STRICT_MODE_OVERRIDE_METHOD__ = overrideMethod;
310
+
311
+ // $FlowFixMe property error|warn is not writable.
312
+ targetConsole[method] = overrideMethod;
313
+ } catch (error) {}
314
+ });
315
+ }
316
+
317
+ // NOTE: KEEP IN SYNC with src/backend/console.js:unpatchForStrictMode
318
+ function unpatchConsoleForInitialRenderInStrictMode() {
319
+ if (unpatchFn !== null) {
320
+ unpatchFn();
321
+ unpatchFn = null;
322
}
323
}
324
@@ -343,7 +349,7 @@ export function installHook(target: any): DevToolsHook | null {
349
// Note that because this function is inlined, this conditional check must only use static booleans.
350
// Otherwise the extension will throw with an undefined error.
351
// (See comments in the try/catch below for more context on inlining.)
346
- if (!__TEST__) {
352
+ if (!__TEST__ && !__EXTENSION__) {
353
try {
354
const appendComponentStack =
355
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false;
@@ -362,23 +368,14 @@ export function installHook(target: any): DevToolsHook | null {
368
// but Webpack wraps imports with an object (e.g. _backend_console__WEBPACK_IMPORTED_MODULE_0__)
369
// and the object itself will be undefined as well for the reasons mentioned above,
370
// so we use try/catch instead.
365
- if (!__EXTENSION__) {
366
- registerRendererWithConsole(renderer);
367
- patchConsole({
368
- appendComponentStack,
369
- breakOnConsoleErrors,
370
- showInlineWarningsAndErrors,
371
- hideConsoleLogsInStrictMode,
372
- browserTheme,
373
- });
374
- } else {
375
- if (consoleManagedByDevToolsDuringStrictMode) {
376
- patchConsoleForInitialRenderInExtension(renderer, {
377
- hideConsoleLogsInStrictMode,
378
- browserTheme,
379
- });
380
- }
381
- }
371
+ registerRendererWithConsole(renderer);
372
+ patchConsole({
373
+ appendComponentStack,
374
+ breakOnConsoleErrors,
375
+ showInlineWarningsAndErrors,
376
+ hideConsoleLogsInStrictMode,
377
+ browserTheme,
378
+ });
379
} catch (error) {}
380
}
381
@@ -473,6 +470,32 @@ export function installHook(target: any): DevToolsHook | null {
470
}
471
}
472
473
+ function setStrictMode(rendererID, isStrictMode) {
474
+ const rendererInterface = rendererInterfaces.get(rendererID);
475
+ if (rendererInterface != null) {
476
+ if (isStrictMode) {
477
+ rendererInterface.patchConsoleForStrictMode();
478
+ } else {
479
+ rendererInterface.unpatchConsoleForStrictMode();
480
+ }
481
+ } else {
482
+ // This should only happen during initial render in the extension before DevTools
483
+ // finishes its handshake with the injected renderer
484
+ if (isStrictMode) {
485
+ const hideConsoleLogsInStrictMode =
486
+ window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true;
487
+ const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__;
488
+
489
+ patchConsoleForInitialRenderInStrictMode({
490
+ hideConsoleLogsInStrictMode,
491
+ browserTheme,
492
+ });
493
+ } else {
494
+ unpatchConsoleForInitialRenderInStrictMode();
495
+ }
496
+ }
497
+ }
498
+
499
// TODO: More meaningful names for "rendererInterfaces" and "renderers".
500
const fiberRoots = {};
501
const rendererInterfaces = new Map();
@@ -502,8 +525,13 @@ export function installHook(target: any): DevToolsHook | null {
525
onCommitFiberUnmount,
526
onCommitFiberRoot,
527
onPostCommitFiberRoot,
528
+ setStrictMode,
529
};
530
531
+ if (__TEST__) {
532
+ hook.dangerous_setTargetConsoleForTesting = dangerous_setTargetConsoleForTesting;
533
+ }
534
+
535
Object.defineProperty(
536
target,
537
'__REACT_DEVTOOLS_GLOBAL_HOOK__',
packages/react-devtools-shell/src/app/console.js
+6
-7
@@ -11,12 +11,6 @@ function ignoreStrings(
11
methodName: string,
12
stringsToIgnore: Array<string>,
13
): void {
14
- // HACKY In the test harness, DevTools overrides the parent window's console.
15
- // Our test app code uses the iframe's console though.
16
- // To simulate a more accurate end-to-end environment,
17
- // the shell's console patching should pass through to the parent override methods.
18
- const originalMethod = window.parent.console[methodName];
19
-
14
console[methodName] = (...args) => {
15
const maybeString = args[0];
16
if (typeof maybeString === 'string') {
@@ -26,7 +20,12 @@ function ignoreStrings(
20
}
21
}
22
}
29
- originalMethod(...args);
23
+
24
+ // HACKY In the test harness, DevTools overrides the parent window's console.
25
+ // Our test app code uses the iframe's console though.
26
+ // To simulate a more accurate end-to-end environment,
27
+ // the shell's console patching should pass through to the parent override methods.
28
+ window.parent.console[methodName](...args);
29
};
30
}
31
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+2
-5
@@ -149,11 +149,7 @@ import {
149
getOffscreenContainerProps,
150
} from './ReactFiberHostConfig';
151
import type {SuspenseInstance} from './ReactFiberHostConfig';
152
-import {
153
- shouldError,
154
- shouldSuspend,
155
- setIsStrictModeForDevtools,
156
-} from './ReactFiberReconciler';
152
+import {shouldError, shouldSuspend} from './ReactFiberReconciler';
153
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
154
import {
155
suspenseStackCursor,
@@ -235,6 +231,7 @@ import {createCapturedValue} from './ReactCapturedValue';
231
import {createClassErrorUpdate} from './ReactFiberThrow.new';
232
import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.new';
233
import is from 'shared/objectIs';
234
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
235
236
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
237
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+2
-5
@@ -149,11 +149,7 @@ import {
149
getOffscreenContainerProps,
150
} from './ReactFiberHostConfig';
151
import type {SuspenseInstance} from './ReactFiberHostConfig';
152
-import {
153
- shouldError,
154
- shouldSuspend,
155
- setIsStrictModeForDevtools,
156
-} from './ReactFiberReconciler';
152
+import {shouldError, shouldSuspend} from './ReactFiberReconciler';
153
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old';
154
import {
155
suspenseStackCursor,
@@ -235,6 +231,7 @@ import {createCapturedValue} from './ReactCapturedValue';
231
import {createClassErrorUpdate} from './ReactFiberThrow.old';
232
import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.old';
233
import is from 'shared/objectIs';
234
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
235
236
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
237
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+1
-1
@@ -38,7 +38,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType';
38
import invariant from 'shared/invariant';
39
import isArray from 'shared/isArray';
40
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
41
-import {setIsStrictModeForDevtools} from './ReactFiberReconciler';
41
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
42
43
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
44
import {
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+1
-1
@@ -38,7 +38,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType';
38
import invariant from 'shared/invariant';
39
import isArray from 'shared/isArray';
40
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
41
-import {setIsStrictModeForDevtools} from './ReactFiberReconciler';
41
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
42
43
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
44
import {
packages/react-reconciler/src/ReactFiberDevToolsHook.new.js
+42
-1
@@ -7,7 +7,10 @@
7
* @flow
8
*/
9
10
-import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
10
+import {
11
+ consoleManagedByDevToolsDuringStrictMode,
12
+ enableProfilerTimer,
13
+} from 'shared/ReactFeatureFlags';
14
15
import type {Fiber, FiberRoot} from './ReactInternalTypes';
16
import type {ReactNodeList} from 'shared/ReactTypes';
@@ -25,7 +28,11 @@ import {
28
UserBlockingPriority as UserBlockingSchedulerPriority,
29
NormalPriority as NormalSchedulerPriority,
30
IdlePriority as IdleSchedulerPriority,
31
+ unstable_yieldValue,
32
+ unstable_setDisableYieldValue,
33
} from './Scheduler';
34
+import {setSuppressWarning} from 'shared/consoleWithStackDev';
35
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
36
37
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
38
@@ -171,3 +178,37 @@ export function onCommitUnmount(fiber: Fiber) {
178
}
179
}
180
}
181
+
182
+export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
183
+ if (consoleManagedByDevToolsDuringStrictMode) {
184
+ if (typeof unstable_yieldValue === 'function') {
185
+ // We're in a test because Scheduler.unstable_yieldValue only exists
186
+ // in SchedulerMock. To reduce the noise in strict mode tests,
187
+ // suppress warnings and disable scheduler yielding during the double render
188
+ unstable_setDisableYieldValue(newIsStrictMode);
189
+ setSuppressWarning(newIsStrictMode);
190
+ }
191
+
192
+ if (injectedHook && typeof injectedHook.setStrictMode === 'function') {
193
+ try {
194
+ injectedHook.setStrictMode(rendererID, newIsStrictMode);
195
+ } catch (err) {
196
+ if (__DEV__) {
197
+ if (!hasLoggedError) {
198
+ hasLoggedError = true;
199
+ console.error(
200
+ 'React instrumentation encountered an error: %s',
201
+ err,
202
+ );
203
+ }
204
+ }
205
+ }
206
+ }
207
+ } else {
208
+ if (newIsStrictMode) {
209
+ disableLogs();
210
+ } else {
211
+ reenableLogs();
212
+ }
213
+ }
214
+}
packages/react-reconciler/src/ReactFiberDevToolsHook.old.js
+42
-1
@@ -7,7 +7,10 @@
7
* @flow
8
*/
9
10
-import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
10
+import {
11
+ consoleManagedByDevToolsDuringStrictMode,
12
+ enableProfilerTimer,
13
+} from 'shared/ReactFeatureFlags';
14
15
import type {Fiber, FiberRoot} from './ReactInternalTypes';
16
import type {ReactNodeList} from 'shared/ReactTypes';
@@ -25,7 +28,11 @@ import {
28
UserBlockingPriority as UserBlockingSchedulerPriority,
29
NormalPriority as NormalSchedulerPriority,
30
IdlePriority as IdleSchedulerPriority,
31
+ unstable_yieldValue,
32
+ unstable_setDisableYieldValue,
33
} from './Scheduler';
34
+import {setSuppressWarning} from 'shared/consoleWithStackDev';
35
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
36
37
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
38
@@ -171,3 +178,37 @@ export function onCommitUnmount(fiber: Fiber) {
178
}
179
}
180
}
181
+
182
+export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
183
+ if (consoleManagedByDevToolsDuringStrictMode) {
184
+ if (typeof unstable_yieldValue === 'function') {
185
+ // We're in a test because Scheduler.unstable_yieldValue only exists
186
+ // in SchedulerMock. To reduce the noise in strict mode tests,
187
+ // suppress warnings and disable scheduler yielding during the double render
188
+ unstable_setDisableYieldValue(newIsStrictMode);
189
+ setSuppressWarning(newIsStrictMode);
190
+ }
191
+
192
+ if (injectedHook && typeof injectedHook.setStrictMode === 'function') {
193
+ try {
194
+ injectedHook.setStrictMode(rendererID, newIsStrictMode);
195
+ } catch (err) {
196
+ if (__DEV__) {
197
+ if (!hasLoggedError) {
198
+ hasLoggedError = true;
199
+ console.error(
200
+ 'React instrumentation encountered an error: %s',
201
+ err,
202
+ );
203
+ }
204
+ }
205
+ }
206
+ }
207
+ } else {
208
+ if (newIsStrictMode) {
209
+ disableLogs();
210
+ } else {
211
+ reenableLogs();
212
+ }
213
+ }
214
+}
packages/react-reconciler/src/ReactFiberReconciler.js
-11
@@ -48,8 +48,6 @@ import {
48
observeVisibleRects as observeVisibleRects_old,
49
runWithPriority as runWithPriority_old,
50
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
51
- getIsStrictModeForDevtools as getIsStrictModeForDevtools_old,
52
- setIsStrictModeForDevtools as setIsStrictModeForDevtools_old,
51
} from './ReactFiberReconciler.old';
52
53
import {
@@ -86,8 +84,6 @@ import {
84
observeVisibleRects as observeVisibleRects_new,
85
runWithPriority as runWithPriority_new,
86
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
89
- getIsStrictModeForDevtools as getIsStrictModeForDevtools_new,
90
- setIsStrictModeForDevtools as setIsStrictModeForDevtools_new,
87
} from './ReactFiberReconciler.new';
88
89
export const createContainer = enableNewReconciler
@@ -189,10 +185,3 @@ export const observeVisibleRects = enableNewReconciler
185
export const runWithPriority = enableNewReconciler
186
? runWithPriority_new
187
: runWithPriority_old;
192
-
193
-export const getIsStrictModeForDevtools = enableNewReconciler
194
- ? getIsStrictModeForDevtools_new
195
- : getIsStrictModeForDevtools_old;
196
-export const setIsStrictModeForDevtools = enableNewReconciler
197
- ? setIsStrictModeForDevtools_new
198
- : setIsStrictModeForDevtools_old;
packages/react-reconciler/src/ReactFiberReconciler.new.js
+1
-35
@@ -35,10 +35,7 @@ import {
35
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
36
import invariant from 'shared/invariant';
37
import isArray from 'shared/isArray';
38
-import {
39
- enableSchedulingProfiler,
40
- consoleManagedByDevToolsDuringStrictMode,
41
-} from 'shared/ReactFeatureFlags';
38
+import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
39
import ReactSharedInternals from 'shared/ReactSharedInternals';
40
import {getPublicInstance} from './ReactFiberHostConfig';
41
import {
@@ -107,10 +104,6 @@ export {
104
observeVisibleRects,
105
} from './ReactTestSelectors';
106
110
-import * as Scheduler from './Scheduler';
111
-import {setSuppressWarning} from 'shared/consoleWithStackDev';
112
-import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
113
-
107
type OpaqueRoot = FiberRoot;
108
109
// 0 is PROD, 1 is DEV.
@@ -464,8 +457,6 @@ export function shouldSuspend(fiber: Fiber): boolean {
457
return shouldSuspendImpl(fiber);
458
}
459
467
-let isStrictMode = false;
468
-
460
let overrideHookState = null;
461
let overrideHookStateDeletePath = null;
462
let overrideHookStateRenamePath = null;
@@ -715,30 +706,6 @@ function getCurrentFiberForDevTools() {
706
return ReactCurrentFiberCurrent;
707
}
708
718
-export function getIsStrictModeForDevtools() {
719
- return isStrictMode;
720
-}
721
-
722
-export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
723
- isStrictMode = newIsStrictMode;
724
-
725
- if (consoleManagedByDevToolsDuringStrictMode) {
726
- // We're in a test because Scheduler.unstable_yieldValue only exists
727
- // in SchedulerMock. To reduce the noise in strict mode tests,
728
- // suppress warnings and disable scheduler yielding during the double render
729
- if (typeof Scheduler.unstable_yieldValue === 'function') {
730
- Scheduler.unstable_setDisableYieldValue(newIsStrictMode);
731
- setSuppressWarning(newIsStrictMode);
732
- }
733
- } else {
734
- if (newIsStrictMode) {
735
- disableLogs();
736
- } else {
737
- reenableLogs();
738
- }
739
- }
740
-}
741
-
709
export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
710
const {findFiberByHostInstance} = devToolsConfig;
711
const {ReactCurrentDispatcher} = ReactSharedInternals;
@@ -768,7 +735,6 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
735
setRefreshHandler: __DEV__ ? setRefreshHandler : null,
736
// Enables DevTools to append owner stacks to error messages in DEV mode.
737
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
771
- getIsStrictMode: __DEV__ ? getIsStrictModeForDevtools : null,
738
// Enables DevTools to detect reconciler version rather than renderer version
739
// which may not match for third party renderers.
740
reconcilerVersion: ReactVersion,
packages/react-reconciler/src/ReactFiberReconciler.old.js
+1
-35
@@ -35,10 +35,7 @@ import {
35
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
36
import invariant from 'shared/invariant';
37
import isArray from 'shared/isArray';
38
-import {
39
- enableSchedulingProfiler,
40
- consoleManagedByDevToolsDuringStrictMode,
41
-} from 'shared/ReactFeatureFlags';
38
+import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
39
import ReactSharedInternals from 'shared/ReactSharedInternals';
40
import {getPublicInstance} from './ReactFiberHostConfig';
41
import {
@@ -107,10 +104,6 @@ export {
104
observeVisibleRects,
105
} from './ReactTestSelectors';
106
110
-import * as Scheduler from './Scheduler';
111
-import {setSuppressWarning} from 'shared/consoleWithStackDev';
112
-import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
113
-
107
type OpaqueRoot = FiberRoot;
108
109
// 0 is PROD, 1 is DEV.
@@ -464,8 +457,6 @@ export function shouldSuspend(fiber: Fiber): boolean {
457
return shouldSuspendImpl(fiber);
458
}
459
467
-let isStrictMode = false;
468
-
460
let overrideHookState = null;
461
let overrideHookStateDeletePath = null;
462
let overrideHookStateRenamePath = null;
@@ -715,30 +706,6 @@ function getCurrentFiberForDevTools() {
706
return ReactCurrentFiberCurrent;
707
}
708
718
-export function getIsStrictModeForDevtools() {
719
- return isStrictMode;
720
-}
721
-
722
-export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
723
- isStrictMode = newIsStrictMode;
724
-
725
- if (consoleManagedByDevToolsDuringStrictMode) {
726
- // We're in a test because Scheduler.unstable_yieldValue only exists
727
- // in SchedulerMock. To reduce the noise in strict mode tests,
728
- // suppress warnings and disable scheduler yielding during the double render
729
- if (typeof Scheduler.unstable_yieldValue === 'function') {
730
- Scheduler.unstable_setDisableYieldValue(newIsStrictMode);
731
- setSuppressWarning(newIsStrictMode);
732
- }
733
- } else {
734
- if (newIsStrictMode) {
735
- disableLogs();
736
- } else {
737
- reenableLogs();
738
- }
739
- }
740
-}
741
-
709
export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
710
const {findFiberByHostInstance} = devToolsConfig;
711
const {ReactCurrentDispatcher} = ReactSharedInternals;
@@ -768,7 +735,6 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
735
setRefreshHandler: __DEV__ ? setRefreshHandler : null,
736
// Enables DevTools to append owner stacks to error messages in DEV mode.
737
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
771
- getIsStrictMode: __DEV__ ? getIsStrictModeForDevtools : null,
738
// Enables DevTools to detect reconciler version rather than renderer version
739
// which may not match for third party renderers.
740
reconcilerVersion: ReactVersion,
packages/react-reconciler/src/ReactUpdateQueue.new.js
+1
-1
@@ -110,7 +110,7 @@ import {
110
isInterleavedUpdate,
111
} from './ReactFiberWorkLoop.new';
112
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new';
113
-import {setIsStrictModeForDevtools} from './ReactFiberReconciler';
113
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
114
115
import invariant from 'shared/invariant';
116
packages/react-reconciler/src/ReactUpdateQueue.old.js
+1
-1
@@ -110,7 +110,7 @@ import {
110
isInterleavedUpdate,
111
} from './ReactFiberWorkLoop.old';
112
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
113
-import {setIsStrictModeForDevtools} from './ReactFiberReconciler';
113
+import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
114
115
import invariant from 'shared/invariant';
116