diff --git a/packages/react-devtools-shared/src/backend/console.js b/packages/react-devtools-shared/src/backend/console.js index 26168f8d3d463..11afce6a0ec73 100644 --- a/packages/react-devtools-shared/src/backend/console.js +++ b/packages/react-devtools-shared/src/backend/console.js @@ -16,7 +16,7 @@ import {getInternalReactConstants} from './renderer'; import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack'; import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags'; -const OVERRIDE_CONSOLE_METHODS = ['error', 'trace', 'warn', 'log']; +const OVERRIDE_CONSOLE_METHODS = ['error', 'trace', 'warn']; const DIMMED_NODE_CONSOLE_COLOR = '\x1b[2m%s\x1b[0m'; // React's custom built component stack strings match "\s{4}in" @@ -30,6 +30,33 @@ export function isStringComponentStack(text: string): boolean { return PREFIX_REGEX.test(text) || ROW_COLUMN_NUMBER_REGEX.test(text); } +const STYLE_DIRECTIVE_REGEX = /^%c/; + +function isStrictModeOverride(args: Array, method: string): boolean { + return ( + args.length === 2 && + STYLE_DIRECTIVE_REGEX.test(args[0]) && + args[1] === `color: ${getConsoleColor(method) || ''}` + ); +} + +function getConsoleColor(method: string): ?string { + switch (method) { + case 'warn': + return consoleSettingsRef.browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR + : process.env.DARK_MODE_DIMMED_WARNING_COLOR; + case 'error': + return consoleSettingsRef.browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR + : process.env.DARK_MODE_DIMMED_ERROR_COLOR; + case 'log': + default: + return consoleSettingsRef.browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR + : process.env.DARK_MODE_DIMMED_LOG_COLOR; + } +} type OnErrorOrWarning = ( fiber: Fiber, type: 'error' | 'warn', @@ -43,7 +70,6 @@ const injectedRenderers: Map< getCurrentFiber: () => Fiber | null, onErrorOrWarning: ?OnErrorOrWarning, workTagMap: WorkTagMap, - getIsStrictMode: ?() => boolean, |}, > = new Map(); @@ -82,7 +108,6 @@ export function registerRenderer( const { currentDispatcherRef, getCurrentFiber, - getIsStrictMode, findFiberByHostInstance, version, } = renderer; @@ -100,7 +125,6 @@ export function registerRenderer( injectedRenderers.set(renderer, { currentDispatcherRef, getCurrentFiber, - getIsStrictMode, workTagMap: ReactTypeOfWork, onErrorOrWarning, }); @@ -112,11 +136,11 @@ const consoleSettingsRef = { breakOnConsoleErrors: false, showInlineWarningsAndErrors: false, hideConsoleLogsInStrictMode: false, + browserTheme: 'dark', }; // Patches console methods to append component stack for the current fiber. // Call unpatch() to remove the injected behavior. -// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInExtension export function patch({ appendComponentStack, breakOnConsoleErrors, @@ -136,141 +160,180 @@ export function patch({ consoleSettingsRef.breakOnConsoleErrors = breakOnConsoleErrors; consoleSettingsRef.showInlineWarningsAndErrors = showInlineWarningsAndErrors; consoleSettingsRef.hideConsoleLogsInStrictMode = hideConsoleLogsInStrictMode; + consoleSettingsRef.browserTheme = browserTheme; + + if ( + appendComponentStack || + breakOnConsoleErrors || + showInlineWarningsAndErrors + ) { + if (unpatchFn !== null) { + // Don't patch twice. + return; + } - if (unpatchFn !== null) { - // Don't patch twice. - return; - } + const originalConsoleMethods = {}; - const originalConsoleMethods = {}; + unpatchFn = () => { + for (const method in originalConsoleMethods) { + try { + // $FlowFixMe property error|warn is not writable. + targetConsole[method] = originalConsoleMethods[method]; + } catch (error) {} + } + }; - unpatchFn = () => { - for (const method in originalConsoleMethods) { + OVERRIDE_CONSOLE_METHODS.forEach(method => { try { - // $FlowFixMe property error|warn is not writable. - targetConsole[method] = originalConsoleMethods[method]; - } catch (error) {} - } - }; - - OVERRIDE_CONSOLE_METHODS.forEach(method => { - try { - const originalMethod = (originalConsoleMethods[method] = targetConsole[ - method - ].__REACT_DEVTOOLS_ORIGINAL_METHOD__ - ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__ - : targetConsole[method]); - - const overrideMethod = (...args) => { - let shouldAppendWarningStack = false; - if (method !== 'log') { - if (consoleSettingsRef.appendComponentStack) { - const lastArg = args.length > 0 ? args[args.length - 1] : null; - const alreadyHasComponentStack = - typeof lastArg === 'string' && isStringComponentStack(lastArg); - - // If we are ever called with a string that already has a component stack, - // e.g. a React error/warning, don't append a second stack. - shouldAppendWarningStack = !alreadyHasComponentStack; + const originalMethod = (originalConsoleMethods[method] = targetConsole[ + method + ].__REACT_DEVTOOLS_ORIGINAL_METHOD__ + ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__ + : targetConsole[method]); + + const overrideMethod = (...args) => { + let shouldAppendWarningStack = false; + if (method !== 'log') { + if (consoleSettingsRef.appendComponentStack) { + const lastArg = args.length > 0 ? args[args.length - 1] : null; + const alreadyHasComponentStack = + typeof lastArg === 'string' && isStringComponentStack(lastArg); + + // If we are ever called with a string that already has a component stack, + // e.g. a React error/warning, don't append a second stack. + shouldAppendWarningStack = !alreadyHasComponentStack; + } } - } - - const shouldShowInlineWarningsAndErrors = - consoleSettingsRef.showInlineWarningsAndErrors && - (method === 'error' || method === 'warn'); - - let isInStrictMode = false; - - // Search for the first renderer that has a current Fiber. - // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?) - // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const { - currentDispatcherRef, - getCurrentFiber, - onErrorOrWarning, - workTagMap, - getIsStrictMode, - } of injectedRenderers.values()) { - const current: ?Fiber = getCurrentFiber(); - if (current != null) { - try { - if (typeof getIsStrictMode === 'function' && getIsStrictMode()) { - isInStrictMode = true; - } - if (shouldShowInlineWarningsAndErrors) { - // patch() is called by two places: (1) the hook and (2) the renderer backend. - // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning. - if (typeof onErrorOrWarning === 'function') { - onErrorOrWarning( - current, - ((method: any): 'error' | 'warn'), - // Copy args before we mutate them (e.g. adding the component stack) - args.slice(), - ); + const shouldShowInlineWarningsAndErrors = + consoleSettingsRef.showInlineWarningsAndErrors && + (method === 'error' || method === 'warn'); + + // Search for the first renderer that has a current Fiber. + // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?) + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (const { + currentDispatcherRef, + getCurrentFiber, + onErrorOrWarning, + workTagMap, + } of injectedRenderers.values()) { + const current: ?Fiber = getCurrentFiber(); + if (current != null) { + try { + if (shouldShowInlineWarningsAndErrors) { + // patch() is called by two places: (1) the hook and (2) the renderer backend. + // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning. + if (typeof onErrorOrWarning === 'function') { + onErrorOrWarning( + current, + ((method: any): 'error' | 'warn'), + // Copy args before we mutate them (e.g. adding the component stack) + args.slice(), + ); + } } - } - if (shouldAppendWarningStack) { - const componentStack = getStackByFiberInDevAndProd( - workTagMap, - current, - currentDispatcherRef, - ); - if (componentStack !== '') { - args.push(componentStack); + if (shouldAppendWarningStack) { + const componentStack = getStackByFiberInDevAndProd( + workTagMap, + current, + currentDispatcherRef, + ); + if (componentStack !== '') { + if (isStrictModeOverride(args, method)) { + args[0] = format(args[0], componentStack); + } else { + args.push(componentStack); + } + } } + } catch (error) { + // Don't let a DevTools or React internal error interfere with logging. + setTimeout(() => { + throw error; + }, 0); + } finally { + break; } - } catch (error) { - // Don't let a DevTools or React internal error interfere with logging. - setTimeout(() => { - throw error; - }, 0); - } finally { - break; } } - } - - if (consoleSettingsRef.breakOnConsoleErrors) { - // --- Welcome to debugging with React DevTools --- - // This debugger statement means that you've enabled the "break on warnings" feature. - // Use the browser's Call Stack panel to step out of this override function- - // to where the original warning or error was logged. - // eslint-disable-next-line no-debugger - debugger; - } - - if (consoleManagedByDevToolsDuringStrictMode && isInStrictMode) { + + if (consoleSettingsRef.breakOnConsoleErrors) { + // --- Welcome to debugging with React DevTools --- + // This debugger statement means that you've enabled the "break on warnings" feature. + // Use the browser's Call Stack panel to step out of this override function- + // to where the original warning or error was logged. + // eslint-disable-next-line no-debugger + debugger; + } + + originalMethod(...args); + }; + + overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod; + originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod; + + // $FlowFixMe property error|warn is not writable. + targetConsole[method] = overrideMethod; + } catch (error) {} + }); + } else { + unpatch(); + } +} + +// Removed component stack patch from console methods. +export function unpatch(): void { + if (unpatchFn !== null) { + unpatchFn(); + unpatchFn = null; + } +} + +let unpatchForStrictModeFn: null | (() => void) = null; + +// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInStrictMode +export function patchForStrictMode() { + if (consoleManagedByDevToolsDuringStrictMode) { + const overrideConsoleMethods = ['error', 'trace', 'warn', 'log']; + + const strictModeTargetConsole = console; + + if (unpatchForStrictModeFn !== null) { + // Don't patch twice. + return; + } + + const originalConsoleMethods = {}; + + unpatchForStrictModeFn = () => { + for (const method in originalConsoleMethods) { + try { + // $FlowFixMe property error|warn is not writable. + targetConsole[method] = originalConsoleMethods[method]; + } catch (error) {} + } + }; + + overrideConsoleMethods.forEach(method => { + try { + const originalMethod = (originalConsoleMethods[ + method + ] = strictModeTargetConsole[method] + .__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ + ? strictModeTargetConsole[method] + .__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ + : strictModeTargetConsole[method]); + + const overrideMethod = (...args) => { if (!consoleSettingsRef.hideConsoleLogsInStrictMode) { // Dim the text color of the double logs if we're not // hiding them. if (isNode) { originalMethod(DIMMED_NODE_CONSOLE_COLOR, format(...args)); } else { - let color; - switch (method) { - case 'warn': - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR - : process.env.DARK_MODE_DIMMED_WARNING_COLOR; - break; - case 'error': - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR - : process.env.DARK_MODE_DIMMED_ERROR_COLOR; - break; - case 'log': - default: - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR - : process.env.DARK_MODE_DIMMED_LOG_COLOR; - break; - } - + const color = getConsoleColor(method); if (color) { originalMethod(`%c${format(...args)}`, `color: ${color}`); } else { @@ -278,24 +341,24 @@ export function patch({ } } } - } else { - originalMethod(...args); - } - }; + }; - overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod; - originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod; + overrideMethod.__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ = originalMethod; + originalMethod.__REACT_DEVTOOLS_STRICT_MODE_OVERRIDE_METHOD__ = overrideMethod; - // $FlowFixMe property error|warn is not writable. - targetConsole[method] = overrideMethod; - } catch (error) {} - }); + // $FlowFixMe property error|warn is not writable. + strictModeTargetConsole[method] = overrideMethod; + } catch (error) {} + }); + } } -// Removed component stack patch from console methods. -export function unpatch(): void { - if (unpatchFn !== null) { - unpatchFn(); - unpatchFn = null; +// NOTE: KEEP IN SYNC with src/hook.js:unpatchConsoleForInitialRenderInStrictMode +export function unpatchForStrictMode(): void { + if (consoleManagedByDevToolsDuringStrictMode) { + if (unpatchForStrictModeFn !== null) { + unpatchForStrictModeFn(); + unpatchForStrictModeFn = null; + } } } diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 7b612ff346171..24f30b87bc78a 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -1073,6 +1073,10 @@ export function attach( // Not implemented } + function patchConsoleForStrictMode() {} + + function unpatchConsoleForStrictMode() {} + return { clearErrorsAndWarnings, clearErrorsForFiberID, @@ -1101,6 +1105,7 @@ export function attach( overrideSuspense, overrideValueAtPath, renamePath, + patchConsoleForStrictMode, prepareViewAttributeSource, prepareViewElementSource, renderer, @@ -1109,6 +1114,7 @@ export function attach( startProfiling, stopProfiling, storeAsGlobal, + unpatchConsoleForStrictMode, updateComponentFilters, }; } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 3f03315fa5ce7..54c24b0b94b0b 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -59,6 +59,8 @@ import {inspectHooksOfFiber} from 'react-debug-tools'; import { patch as patchConsole, registerRenderer as registerRendererWithConsole, + patchForStrictMode, + unpatchForStrictMode, } from './console'; import { CONCURRENT_MODE_NUMBER, @@ -753,6 +755,14 @@ export function attach( }); } + function patchConsoleForStrictMode() { + patchForStrictMode(); + } + + function unpatchConsoleForStrictMode() { + unpatchForStrictMode(); + } + const debug = ( name: string, fiber: Fiber, @@ -4242,6 +4252,7 @@ export function attach( handlePostCommitFiberRoot, inspectElement, logElementToConsole, + patchConsoleForStrictMode, prepareViewAttributeSource, prepareViewElementSource, overrideError, @@ -4254,6 +4265,7 @@ export function attach( startProfiling, stopProfiling, storeAsGlobal, + unpatchConsoleForStrictMode, updateComponentFilters, }; } diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 0a7166d040983..da8c992cc25a6 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -136,8 +136,6 @@ export type ReactRenderer = { // Only injected by React v16.9+ in DEV mode. // Enables DevTools to append owners-only component stack to error messages. getCurrentFiber?: () => Fiber | null, - - getIsStrictMode?: () => boolean, // 17.0.2+ reconcilerVersion?: string, // Uniquely identifies React DOM v15. @@ -352,6 +350,7 @@ export type RendererInterface = { path: Array, value: any, ) => void, + patchConsoleForStrictMode: () => void, prepareViewAttributeSource: ( id: number, path: Array, @@ -374,6 +373,7 @@ export type RendererInterface = { path: Array, count: number, ) => void, + unpatchConsoleForStrictMode: () => void, updateComponentFilters: (componentFilters: Array) => void, ... }; diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 28cfd027afe43..53efa04c2b380 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -16,7 +16,6 @@ import { patch as patchConsole, registerRenderer as registerRendererWithConsole, } from './backend/console'; -import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags'; import type {DevToolsHook} from 'react-devtools-shared/src/backend/types'; @@ -163,156 +162,149 @@ export function installHook(target: any): DevToolsHook | null { maybeMessage: any, ...inputArgs: $ReadOnlyArray ): string { - if (consoleManagedByDevToolsDuringStrictMode) { - const args = inputArgs.slice(); - - // Symbols cannot be concatenated with Strings. - let formatted: string = - typeof maybeMessage === 'symbol' - ? maybeMessage.toString() - : '' + maybeMessage; - - // If the first argument is a string, check for substitutions. - if (typeof maybeMessage === 'string') { - if (args.length) { - const REGEXP = /(%?)(%([jds]))/g; - - formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => { - let arg = args.shift(); - switch (flag) { - case 's': - arg += ''; - break; - case 'd': - case 'i': - arg = parseInt(arg, 10).toString(); - break; - case 'f': - arg = parseFloat(arg).toString(); - break; - } - if (!escaped) { - return arg; - } - args.unshift(arg); - return match; - }); - } - } + const args = inputArgs.slice(); - // Arguments that remain after formatting. - if (args.length) { - for (let i = 0; i < args.length; i++) { - const arg = args[i]; + // Symbols cannot be concatenated with Strings. + let formatted: string = + typeof maybeMessage === 'symbol' + ? maybeMessage.toString() + : '' + maybeMessage; - // Symbols cannot be concatenated with Strings. - formatted += ' ' + (typeof arg === 'symbol' ? arg.toString() : arg); - } + // If the first argument is a string, check for substitutions. + if (typeof maybeMessage === 'string') { + if (args.length) { + const REGEXP = /(%?)(%([jds]))/g; + + formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => { + let arg = args.shift(); + switch (flag) { + case 's': + arg += ''; + break; + case 'd': + case 'i': + arg = parseInt(arg, 10).toString(); + break; + case 'f': + arg = parseFloat(arg).toString(); + break; + } + if (!escaped) { + return arg; + } + args.unshift(arg); + return match; + }); } + } - // Update escaped %% values. - formatted = formatted.replace(/%{2,2}/g, '%'); + // Arguments that remain after formatting. + if (args.length) { + for (let i = 0; i < args.length; i++) { + const arg = args[i]; - return '' + formatted; + // Symbols cannot be concatenated with Strings. + formatted += ' ' + (typeof arg === 'symbol' ? arg.toString() : arg); + } } - return ''; + // Update escaped %% values. + formatted = formatted.replace(/%{2,2}/g, '%'); + + return '' + formatted; } - // NOTE: KEEP IN SYNC with src/backend/console.js:patch - function patchConsoleForInitialRenderInExtension( + let unpatchFn = null; + + // NOTE: KEEP IN SYNC with src/backend/console.js:patchForStrictMode + function patchConsoleForInitialRenderInStrictMode( renderer: ReactRenderer, { hideConsoleLogsInStrictMode, browserTheme, - }: {hideConsoleLogsInStrictMode: boolean, browserTheme: BrowserTheme}, - ): void { - if (consoleManagedByDevToolsDuringStrictMode) { - const overrideConsoleMethods = ['error', 'trace', 'warn', 'log']; - - if (__EXTENSION__) { - const targetConsole = console; - - const originalConsoleMethods = {}; - - overrideConsoleMethods.forEach(method => { - try { - const originalMethod = (originalConsoleMethods[ - method - ] = targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__ - ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__ - : targetConsole[method]); - - const overrideMethod = (...args) => { - let isInStrictMode = false; - - // Search for the first renderer that has a current Fiber. - // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?) - const {getCurrentFiber, getIsStrictMode} = renderer; - if (typeof getCurrentFiber !== 'function') { - return; - } - - const current: ?Fiber = getCurrentFiber(); - if (current != null) { - try { - if ( - typeof getIsStrictMode === 'function' && - getIsStrictMode() - ) { - isInStrictMode = true; - } - } catch (error) { - // Don't let a DevTools or React internal error interfere with logging. - } - } - - if (isInStrictMode) { - if (!hideConsoleLogsInStrictMode) { - // Dim the text color of the double logs if we're not - // hiding them. - let color; - switch (method) { - case 'warn': - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR - : process.env.DARK_MODE_DIMMED_WARNING_COLOR; - break; - case 'error': - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR - : process.env.DARK_MODE_DIMMED_ERROR_COLOR; - break; - case 'log': - default: - color = - browserTheme === 'light' - ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR - : process.env.DARK_MODE_DIMMED_LOG_COLOR; - break; - } - - if (color) { - originalMethod(`%c${format(...args)}`, `color: ${color}`); - } else { - throw Error('Console color is not defined'); - } - } - } else { - originalMethod(...args); - } - }; - - overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod; - originalMethod.__REACT_DEVTOOLS_OVERRIDE_METHOD__ = overrideMethod; - - // $FlowFixMe property error|warn is not writable. - targetConsole[method] = overrideMethod; - } catch (error) {} - }); + }: { + hideConsoleLogsInStrictMode: boolean, + browserTheme: BrowserTheme, + }, + ) { + const overrideConsoleMethods = ['error', 'trace', 'warn', 'log']; + + const targetConsole = console; + + if (unpatchFn !== null) { + // Don't patch twice. + return; + } + + const originalConsoleMethods = {}; + + unpatchFn = () => { + for (const method in originalConsoleMethods) { + try { + // $FlowFixMe property error|warn is not writable. + targetConsole[method] = originalConsoleMethods[method]; + } catch (error) {} } + }; + + overrideConsoleMethods.forEach(method => { + try { + const originalMethod = (originalConsoleMethods[method] = targetConsole[ + method + ].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ + ? targetConsole[method].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ + : targetConsole[method]); + + const overrideMethod = (...args) => { + if (!hideConsoleLogsInStrictMode) { + // Dim the text color of the double logs if we're not + // hiding them. + let color; + switch (method) { + case 'warn': + color = + browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_WARNING_COLOR + : process.env.DARK_MODE_DIMMED_WARNING_COLOR; + break; + case 'error': + color = + browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_ERROR_COLOR + : process.env.DARK_MODE_DIMMED_ERROR_COLOR; + break; + case 'log': + default: + color = + browserTheme === 'light' + ? process.env.LIGHT_MODE_DIMMED_LOG_COLOR + : process.env.DARK_MODE_DIMMED_LOG_COLOR; + break; + } + + if (color) { + originalMethod(`%c${format(...args)}`, `color: ${color}`); + } else { + throw Error('Console color is not defined'); + } + } + }; + + overrideMethod.__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ = originalMethod; + originalMethod.__REACT_DEVTOOLS_STRICT_MODE_OVERRIDE_METHOD__ = overrideMethod; + + // $FlowFixMe property error|warn is not writable. + targetConsole[method] = overrideMethod; + } catch (error) {} + }); + } + + // NOTE: KEEP IN SYNC with src/backend/console.js:unpatchhForStrictMode + + function unpatchConsoleForInitialRenderInStrictMode(renderer) { + if (unpatchFn !== null) { + unpatchFn(); + unpatchFn = null; } } @@ -343,7 +335,7 @@ export function installHook(target: any): DevToolsHook | null { // Note that because this function is inlined, this conditional check must only use static booleans. // Otherwise the extension will throw with an undefined error. // (See comments in the try/catch below for more context on inlining.) - if (!__TEST__) { + if (!__TEST__ && !__EXTENSION__) { try { const appendComponentStack = window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false; @@ -362,23 +354,14 @@ export function installHook(target: any): DevToolsHook | null { // but Webpack wraps imports with an object (e.g. _backend_console__WEBPACK_IMPORTED_MODULE_0__) // and the object itself will be undefined as well for the reasons mentioned above, // so we use try/catch instead. - if (!__EXTENSION__) { - registerRendererWithConsole(renderer); - patchConsole({ - appendComponentStack, - breakOnConsoleErrors, - showInlineWarningsAndErrors, - hideConsoleLogsInStrictMode, - browserTheme, - }); - } else { - if (consoleManagedByDevToolsDuringStrictMode) { - patchConsoleForInitialRenderInExtension(renderer, { - hideConsoleLogsInStrictMode, - browserTheme, - }); - } - } + registerRendererWithConsole(renderer); + patchConsole({ + appendComponentStack, + breakOnConsoleErrors, + showInlineWarningsAndErrors, + hideConsoleLogsInStrictMode, + browserTheme, + }); } catch (error) {} } @@ -473,6 +456,33 @@ export function installHook(target: any): DevToolsHook | null { } } + function setStrictMode(rendererID, isStrictMode) { + const rendererInterface = rendererInterfaces.get(rendererID); + if (rendererInterface != null) { + if (isStrictMode) { + rendererInterface.patchConsoleForStrictMode(); + } else { + rendererInterface.unpatchConsoleForStrictMode(); + } + } else { + const renderer = renderers.get(rendererID); + if (renderer != null) { + if (isStrictMode) { + const hideConsoleLogsInStrictMode = + window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true; + const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__; + + patchConsoleForInitialRenderInStrictMode(renderer, { + hideConsoleLogsInStrictMode, + browserTheme, + }); + } else { + unpatchConsoleForInitialRenderInStrictMode(renderer); + } + } + } + } + // TODO: More meaningful names for "rendererInterfaces" and "renderers". const fiberRoots = {}; const rendererInterfaces = new Map(); @@ -502,6 +512,7 @@ export function installHook(target: any): DevToolsHook | null { onCommitFiberUnmount, onCommitFiberRoot, onPostCommitFiberRoot, + setStrictMode, }; Object.defineProperty( diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index d9874bba34119..9ad53ec00f27f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -151,11 +151,7 @@ import { getOffscreenContainerProps, } from './ReactFiberHostConfig'; import type {SuspenseInstance} from './ReactFiberHostConfig'; -import { - shouldError, - shouldSuspend, - setIsStrictModeForDevtools, -} from './ReactFiberReconciler'; +import {shouldError, shouldSuspend} from './ReactFiberReconciler'; import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new'; import { suspenseStackCursor, @@ -238,6 +234,7 @@ import {createCapturedValue} from './ReactCapturedValue'; import {createClassErrorUpdate} from './ReactFiberThrow.new'; import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.new'; import is from 'shared/objectIs'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 74037fbe966f2..bef7863638ff0 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -151,11 +151,7 @@ import { getOffscreenContainerProps, } from './ReactFiberHostConfig'; import type {SuspenseInstance} from './ReactFiberHostConfig'; -import { - shouldError, - shouldSuspend, - setIsStrictModeForDevtools, -} from './ReactFiberReconciler'; +import {shouldError, shouldSuspend} from './ReactFiberReconciler'; import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old'; import { suspenseStackCursor, @@ -238,6 +234,7 @@ import {createCapturedValue} from './ReactCapturedValue'; import {createClassErrorUpdate} from './ReactFiberThrow.old'; import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.old'; import is from 'shared/objectIs'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js index 490fa5f430f21..3cb67d045d410 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.new.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.new.js @@ -38,7 +38,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; -import {setIsStrictModeForDevtools} from './ReactFiberReconciler'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new'; import {resolveDefaultProps} from './ReactFiberLazyComponent.new'; import { diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.old.js b/packages/react-reconciler/src/ReactFiberClassComponent.old.js index 8c08eb581892b..0e30466bf82be 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.old.js @@ -38,7 +38,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; -import {setIsStrictModeForDevtools} from './ReactFiberReconciler'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old'; import {resolveDefaultProps} from './ReactFiberLazyComponent.old'; import { diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js index 52d1c68730c61..9c2b4db9b25c5 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js @@ -7,7 +7,10 @@ * @flow */ -import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; +import { + consoleManagedByDevToolsDuringStrictMode, + enableProfilerTimer, +} from 'shared/ReactFeatureFlags'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {ReactNodeList} from 'shared/ReactTypes'; @@ -25,7 +28,11 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, + unstable_yieldValue, + unstable_setDisableYieldValue, } from './Scheduler'; +import {setSuppressWarning} from 'shared/consoleWithStackDev'; +import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev'; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; @@ -171,3 +178,44 @@ export function onCommitUnmount(fiber: Fiber) { } } } + +let isStrictMode = false; + +export function setIsStrictModeForDevtools(newIsStrictMode: boolean) { + isStrictMode = newIsStrictMode; + if (consoleManagedByDevToolsDuringStrictMode) { + // We're in a test because Scheduler.unstable_yieldValue only exists + // in SchedulerMock. To reduce the noise in strict mode tests, + // suppress warnings and disable scheduler yielding during the double render + if (typeof unstable_yieldValue === 'function') { + unstable_setDisableYieldValue(newIsStrictMode); + setSuppressWarning(newIsStrictMode); + } else { + if (injectedHook && typeof injectedHook.setStrictMode === 'function') { + try { + injectedHook.setStrictMode(rendererID, newIsStrictMode); + } catch (err) { + if (__DEV__) { + if (!hasLoggedError) { + hasLoggedError = true; + console.error( + 'React instrumentation encountered an error: %s', + err, + ); + } + } + } + } + } + } else { + if (newIsStrictMode) { + disableLogs(); + } else { + reenableLogs(); + } + } +} + +export function getIsStrictModeForDevtools() { + return isStrictMode; +} diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js index 42fd68a93a8bc..a46555a4754ed 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js @@ -7,7 +7,10 @@ * @flow */ -import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; +import { + consoleManagedByDevToolsDuringStrictMode, + enableProfilerTimer, +} from 'shared/ReactFeatureFlags'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {ReactNodeList} from 'shared/ReactTypes'; @@ -25,7 +28,11 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, + unstable_yieldValue, + unstable_setDisableYieldValue, } from './Scheduler'; +import {setSuppressWarning} from 'shared/consoleWithStackDev'; +import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev'; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; @@ -171,3 +178,44 @@ export function onCommitUnmount(fiber: Fiber) { } } } + +let isStrictMode = false; + +export function setIsStrictModeForDevtools(newIsStrictMode: boolean) { + isStrictMode = newIsStrictMode; + if (consoleManagedByDevToolsDuringStrictMode) { + // We're in a test because Scheduler.unstable_yieldValue only exists + // in SchedulerMock. To reduce the noise in strict mode tests, + // suppress warnings and disable scheduler yielding during the double render + if (typeof unstable_yieldValue === 'function') { + unstable_setDisableYieldValue(newIsStrictMode); + setSuppressWarning(newIsStrictMode); + } else { + if (injectedHook && typeof injectedHook.setStrictMode === 'function') { + try { + injectedHook.setStrictMode(rendererID, newIsStrictMode); + } catch (err) { + if (__DEV__) { + if (!hasLoggedError) { + hasLoggedError = true; + console.error( + 'React instrumentation encountered an error: %s', + err, + ); + } + } + } + } + } + } else { + if (newIsStrictMode) { + disableLogs(); + } else { + reenableLogs(); + } + } +} + +export function getIsStrictModeForDevtools() { + return isStrictMode; +} diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 64a4b446ba516..0a585f4990d63 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -115,7 +115,7 @@ import { entangleTransitions, } from './ReactUpdateQueue.new'; import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new'; -import {getIsStrictModeForDevtools} from './ReactFiberReconciler.new'; +import {getIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new'; import {warnOnSubscriptionInsideStartTransition} from 'shared/ReactFeatureFlags'; const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals; diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 11573e3b5e0a3..4aefce34caea5 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -115,7 +115,7 @@ import { entangleTransitions, } from './ReactUpdateQueue.old'; import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old'; -import {getIsStrictModeForDevtools} from './ReactFiberReconciler.old'; +import {getIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old'; import {warnOnSubscriptionInsideStartTransition} from 'shared/ReactFeatureFlags'; const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals; diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 033b9eb0adbec..d25783164e984 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -49,8 +49,6 @@ import { registerMutableSourceForHydration as registerMutableSourceForHydration_old, runWithPriority as runWithPriority_old, getCurrentUpdatePriority as getCurrentUpdatePriority_old, - getIsStrictModeForDevtools as getIsStrictModeForDevtools_old, - setIsStrictModeForDevtools as setIsStrictModeForDevtools_old, } from './ReactFiberReconciler.old'; import { @@ -88,8 +86,6 @@ import { registerMutableSourceForHydration as registerMutableSourceForHydration_new, runWithPriority as runWithPriority_new, getCurrentUpdatePriority as getCurrentUpdatePriority_new, - getIsStrictModeForDevtools as getIsStrictModeForDevtools_new, - setIsStrictModeForDevtools as setIsStrictModeForDevtools_new, } from './ReactFiberReconciler.new'; export const createContainer = enableNewReconciler @@ -194,10 +190,3 @@ export const registerMutableSourceForHydration = enableNewReconciler export const runWithPriority = enableNewReconciler ? runWithPriority_new : runWithPriority_old; - -export const getIsStrictModeForDevtools = enableNewReconciler - ? getIsStrictModeForDevtools_new - : getIsStrictModeForDevtools_old; -export const setIsStrictModeForDevtools = enableNewReconciler - ? setIsStrictModeForDevtools_new - : setIsStrictModeForDevtools_old; diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index 06acfa11cbc3b..5a778e2a1f73a 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -465,8 +465,6 @@ export function shouldSuspend(fiber: Fiber): boolean { return shouldSuspendImpl(fiber); } -let isStrictMode = false; - let overrideHookState = null; let overrideHookStateDeletePath = null; let overrideHookStateRenamePath = null; @@ -716,30 +714,6 @@ function getCurrentFiberForDevTools() { return ReactCurrentFiberCurrent; } -export function getIsStrictModeForDevtools() { - return isStrictMode; -} - -export function setIsStrictModeForDevtools(newIsStrictMode: boolean) { - isStrictMode = newIsStrictMode; - - if (consoleManagedByDevToolsDuringStrictMode) { - // We're in a test because Scheduler.unstable_yieldValue only exists - // in SchedulerMock. To reduce the noise in strict mode tests, - // suppress warnings and disable scheduler yielding during the double render - if (typeof Scheduler.unstable_yieldValue === 'function') { - Scheduler.unstable_setDisableYieldValue(newIsStrictMode); - setSuppressWarning(newIsStrictMode); - } - } else { - if (newIsStrictMode) { - disableLogs(); - } else { - reenableLogs(); - } - } -} - export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { const {findFiberByHostInstance} = devToolsConfig; const {ReactCurrentDispatcher} = ReactSharedInternals; @@ -769,7 +743,6 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { setRefreshHandler: __DEV__ ? setRefreshHandler : null, // Enables DevTools to append owner stacks to error messages in DEV mode. getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null, - getIsStrictMode: __DEV__ ? getIsStrictModeForDevtools : null, // Enables DevTools to detect reconciler version rather than renderer version // which may not match for third party renderers. reconcilerVersion: ReactVersion, diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index 5a75a312c42b4..be8438dd2405d 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -465,8 +465,6 @@ export function shouldSuspend(fiber: Fiber): boolean { return shouldSuspendImpl(fiber); } -let isStrictMode = false; - let overrideHookState = null; let overrideHookStateDeletePath = null; let overrideHookStateRenamePath = null; @@ -716,30 +714,6 @@ function getCurrentFiberForDevTools() { return ReactCurrentFiberCurrent; } -export function getIsStrictModeForDevtools() { - return isStrictMode; -} - -export function setIsStrictModeForDevtools(newIsStrictMode: boolean) { - isStrictMode = newIsStrictMode; - - if (consoleManagedByDevToolsDuringStrictMode) { - // We're in a test because Scheduler.unstable_yieldValue only exists - // in SchedulerMock. To reduce the noise in strict mode tests, - // suppress warnings and disable scheduler yielding during the double render - if (typeof Scheduler.unstable_yieldValue === 'function') { - Scheduler.unstable_setDisableYieldValue(newIsStrictMode); - setSuppressWarning(newIsStrictMode); - } - } else { - if (newIsStrictMode) { - disableLogs(); - } else { - reenableLogs(); - } - } -} - export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { const {findFiberByHostInstance} = devToolsConfig; const {ReactCurrentDispatcher} = ReactSharedInternals; @@ -769,7 +743,6 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { setRefreshHandler: __DEV__ ? setRefreshHandler : null, // Enables DevTools to append owner stacks to error messages in DEV mode. getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null, - getIsStrictMode: __DEV__ ? getIsStrictModeForDevtools : null, // Enables DevTools to detect reconciler version rather than renderer version // which may not match for third party renderers. reconcilerVersion: ReactVersion, diff --git a/packages/react-reconciler/src/ReactUpdateQueue.new.js b/packages/react-reconciler/src/ReactUpdateQueue.new.js index a42384322fcdb..cbfe307b903f2 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.new.js +++ b/packages/react-reconciler/src/ReactUpdateQueue.new.js @@ -110,7 +110,7 @@ import { isInterleavedUpdate, } from './ReactFiberWorkLoop.new'; import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new'; -import {setIsStrictModeForDevtools} from './ReactFiberReconciler'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new'; import invariant from 'shared/invariant'; diff --git a/packages/react-reconciler/src/ReactUpdateQueue.old.js b/packages/react-reconciler/src/ReactUpdateQueue.old.js index 724dfa0d7171b..ece4321d6bfdb 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.old.js +++ b/packages/react-reconciler/src/ReactUpdateQueue.old.js @@ -110,7 +110,7 @@ import { isInterleavedUpdate, } from './ReactFiberWorkLoop.old'; import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old'; -import {setIsStrictModeForDevtools} from './ReactFiberReconciler'; +import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old'; import invariant from 'shared/invariant';