Devtools disable log dimming strict mode setting (#35207)

<!--

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).

-->

## Summary

Currently, every second console log is dimmed, receiving a special style
that indicates to user that it was raising because of [React Strict
Mode](https://react.dev/reference/react/StrictMode) second rendering.
This introduces a setting to disable this.

## How did you test this change?
Test in console-test.js


https://github.com/user-attachments/assets/af6663ac-f79b-4824-95c0-d46b0c8dec12

Browser extension react devtools


https://github.com/user-attachments/assets/7e2ecb7a-fbdf-4c72-ab45-7e3a1c6e5e44

React native dev tools:


https://github.com/user-attachments/assets/d875b3ac-1f27-43f8-8d9d-12b2d65fa6e6

---------

Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
This commit is contained in:
emily8rown
2025-12-15 13:41:43 +00:00
committed by GitHub
parent ba5b843692
commit bcf97c7564
9 changed files with 166 additions and 16 deletions

View File

@@ -32,7 +32,7 @@ if (process.env.NODE_ENV !== 'production') {
#### `Settings`
| Spec | Default value |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false<br>}</pre> |
| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean,<br> disableSecondConsoleLogDimmingInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false,<br> disableSecondConsoleLogDimmingInStrictMode: false<br>}</pre> |
### `connectToDevTools` options
| Prop | Default | Description |
@@ -53,7 +53,7 @@ if (process.env.NODE_ENV !== 'production') {
| `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
| `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
| `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |
| `onSettingsUpdated` | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. |
| `onSettingsUpdated` | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. |
Unlike `connectToDevTools`, `connectWithCustomMessagingProtocol` returns a callback, which can be used for unsubscribing the backend from the global DevTools hook.

View File

@@ -24,6 +24,11 @@ async function messageListener(event: MessageEvent) {
if (typeof settings.hideConsoleLogsInStrictMode !== 'boolean') {
settings.hideConsoleLogsInStrictMode = false;
}
if (
typeof settings.disableSecondConsoleLogDimmingInStrictMode !== 'boolean'
) {
settings.disableSecondConsoleLogDimmingInStrictMode = false;
}
window.postMessage({
source: 'react-devtools-hook-settings-injector',

View File

@@ -27,6 +27,7 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
componentFilters,
showInlineWarningsAndErrors,
hideConsoleLogsInStrictMode,
disableSecondConsoleLogDimmingInStrictMode,
} = data;
contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ =
@@ -38,6 +39,8 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
showInlineWarningsAndErrors;
contentWindow.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ =
hideConsoleLogsInStrictMode;
contentWindow.__REACT_DEVTOOLS_DISABLE_SECOND_CONSOLE_LOG_DIMMING_IN_STRICT_MODE__ =
disableSecondConsoleLogDimmingInStrictMode;
// TRICKY
// The backend entry point may be required in the context of an iframe or the parent window.
@@ -53,6 +56,8 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
showInlineWarningsAndErrors;
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ =
hideConsoleLogsInStrictMode;
window.__REACT_DEVTOOLS_DISABLE_SECOND_CONSOLE_LOG_DIMMING_IN_STRICT_MODE__ =
disableSecondConsoleLogDimmingInStrictMode;
}
finishActivation(contentWindow, bridge);

View File

@@ -733,4 +733,85 @@ describe('console', () => {
: 'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
]);
});
it('should not dim console logs if disableSecondConsoleLogDimmingInStrictMode is enabled', () => {
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode =
false;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode =
true;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
function App() {
console.log('log');
console.warn('warn');
console.error('error');
return <div />;
}
act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
// Both logs should be called (double logging)
expect(global.consoleLogMock).toHaveBeenCalledTimes(2);
expect(global.consoleWarnMock).toHaveBeenCalledTimes(2);
expect(global.consoleErrorMock).toHaveBeenCalledTimes(2);
// The second log should NOT have dimming (no ANSI codes)
expect(global.consoleLogMock.mock.calls[1]).toEqual(['log']);
expect(global.consoleWarnMock.mock.calls[1]).toEqual(['warn']);
expect(global.consoleErrorMock.mock.calls[1]).toEqual(['error']);
});
it('should dim console logs if disableSecondConsoleLogDimmingInStrictMode is disabled', () => {
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode =
false;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode =
false;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
function App() {
console.log('log');
console.warn('warn');
console.error('error');
return <div />;
}
act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
// Both logs should be called (double logging)
expect(global.consoleLogMock).toHaveBeenCalledTimes(2);
expect(global.consoleWarnMock).toHaveBeenCalledTimes(2);
expect(global.consoleErrorMock).toHaveBeenCalledTimes(2);
// The second log should have dimming (ANSI codes present)
expect(global.consoleLogMock.mock.calls[1]).toEqual([
'\x1b[2;38;2;124;124;124m%s\x1b[0m',
'log',
]);
expect(global.consoleWarnMock.mock.calls[1]).toEqual([
'\x1b[2;38;2;124;124;124m%s\x1b[0m',
'warn',
]);
expect(global.consoleErrorMock.mock.calls[1]).toEqual([
'\x1b[2;38;2;124;124;124m%s\x1b[0m',
'error',
]);
});
});

View File

@@ -248,6 +248,7 @@ beforeEach(() => {
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: true,
hideConsoleLogsInStrictMode: false,
disableSecondConsoleLogDimmingInStrictMode: false,
});
const bridgeListeners = [];

View File

@@ -597,4 +597,5 @@ export type DevToolsHookSettings = {
breakOnConsoleErrors: boolean,
showInlineWarningsAndErrors: boolean,
hideConsoleLogsInStrictMode: boolean,
disableSecondConsoleLogDimmingInStrictMode: boolean,
};

View File

@@ -36,6 +36,10 @@ export default function DebuggingSettings({
useState(usedHookSettings.hideConsoleLogsInStrictMode);
const [showInlineWarningsAndErrors, setShowInlineWarningsAndErrors] =
useState(usedHookSettings.showInlineWarningsAndErrors);
const [
disableSecondConsoleLogDimmingInStrictMode,
setDisableSecondConsoleLogDimmingInStrictMode,
] = useState(usedHookSettings.disableSecondConsoleLogDimmingInStrictMode);
useEffect(() => {
store.setShouldShowWarningsAndErrors(showInlineWarningsAndErrors);
@@ -47,6 +51,7 @@ export default function DebuggingSettings({
breakOnConsoleErrors,
showInlineWarningsAndErrors,
hideConsoleLogsInStrictMode,
disableSecondConsoleLogDimmingInStrictMode,
});
}, [
store,
@@ -54,6 +59,7 @@ export default function DebuggingSettings({
breakOnConsoleErrors,
showInlineWarningsAndErrors,
hideConsoleLogsInStrictMode,
disableSecondConsoleLogDimmingInStrictMode,
]);
return (
@@ -105,9 +111,12 @@ export default function DebuggingSettings({
<input
type="checkbox"
checked={hideConsoleLogsInStrictMode}
onChange={({currentTarget}) =>
setHideConsoleLogsInStrictMode(currentTarget.checked)
}
onChange={({currentTarget}) => {
setHideConsoleLogsInStrictMode(currentTarget.checked);
if (currentTarget.checked) {
setDisableSecondConsoleLogDimmingInStrictMode(false);
}
}}
className={styles.SettingRowCheckbox}
/>
Hide logs during additional invocations in&nbsp;
@@ -120,6 +129,40 @@ export default function DebuggingSettings({
</a>
</label>
</div>
<div
className={
hideConsoleLogsInStrictMode
? `${styles.SettingDisabled} ${styles.SettingWrapper}`
: styles.SettingWrapper
}>
<label
className={
hideConsoleLogsInStrictMode
? `${styles.SettingDisabled} ${styles.SettingRow}`
: styles.SettingRow
}>
<input
type="checkbox"
checked={disableSecondConsoleLogDimmingInStrictMode}
disabled={hideConsoleLogsInStrictMode}
onChange={({currentTarget}) =>
setDisableSecondConsoleLogDimmingInStrictMode(
currentTarget.checked,
)
}
className={styles.SettingRowCheckbox}
/>
Disable log dimming during additional invocations in&nbsp;
<a
className={styles.StrictModeLink}
target="_blank"
rel="noopener noreferrer"
href="https://react.dev/reference/react/StrictMode">
Strict Mode
</a>
</label>
</div>
</div>
);
}

View File

@@ -26,6 +26,11 @@
margin: 0.125rem 0.25rem 0.125rem 0;
}
.SettingDisabled {
opacity: 0.5;
cursor: not-allowed;
}
.OptionGroup {
display: inline-flex;
flex-direction: row;

View File

@@ -367,17 +367,22 @@ export function installHook(
return;
}
// Dim the text color of the double logs if we're not hiding them.
// Firefox doesn't support ANSI escape sequences
if (__IS_FIREFOX__) {
originalMethod(
...formatWithStyles(args, FIREFOX_CONSOLE_DIMMING_COLOR),
);
if (settings.disableSecondConsoleLogDimmingInStrictMode) {
// Don't dim the console logs
originalMethod(...args);
} else {
originalMethod(
ANSI_STYLE_DIMMING_TEMPLATE,
...formatConsoleArguments(...args),
);
// Dim the text color of the double logs if we're not hiding them.
// Firefox doesn't support ANSI escape sequences
if (__IS_FIREFOX__) {
originalMethod(
...formatWithStyles(args, FIREFOX_CONSOLE_DIMMING_COLOR),
);
} else {
originalMethod(
ANSI_STYLE_DIMMING_TEMPLATE,
...formatConsoleArguments(...args),
);
}
}
};
@@ -579,7 +584,10 @@ export function installHook(
debugger;
}
if (isRunningDuringStrictModeInvocation) {
if (
isRunningDuringStrictModeInvocation &&
!settings.disableSecondConsoleLogDimmingInStrictMode
) {
// Dim the text color of the double logs if we're not hiding them.
// Firefox doesn't support ANSI escape sequences
if (__IS_FIREFOX__) {
@@ -667,6 +675,7 @@ export function installHook(
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: true,
hideConsoleLogsInStrictMode: false,
disableSecondConsoleLogDimmingInStrictMode: false,
};
patchConsoleForErrorsAndWarnings();
} else {