],
viewElementSource: [ElementAndRendererID],
diff --git a/src/constants.js b/src/constants.js
index 9267c53e4b5e..da55631a01e4 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -20,4 +20,7 @@ export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
export const SESSION_STORAGE_RELOAD_AND_PROFILE_KEY =
'React::DevTools::reloadAndProfile';
+export const LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY =
+ 'React::DevTools::appendComponentStack';
+
export const PROFILER_EXPORT_VERSION = 4;
diff --git a/src/devtools/views/Settings/GeneralSettings.js b/src/devtools/views/Settings/GeneralSettings.js
index 899eaafefaea..60d61bb817fa 100644
--- a/src/devtools/views/Settings/GeneralSettings.js
+++ b/src/devtools/views/Settings/GeneralSettings.js
@@ -1,27 +1,25 @@
// @flow
-import React, { useCallback, useContext } from 'react';
+import React, { useContext } from 'react';
import { SettingsContext } from './SettingsContext';
import styles from './SettingsShared.css';
export default function GeneralSettings(_: {||}) {
- const { displayDensity, setDisplayDensity, theme, setTheme } = useContext(
- SettingsContext
- );
+ const {
+ displayDensity,
+ setDisplayDensity,
+ theme,
+ setTheme,
+ appendComponentStack,
+ setAppendComponentStack,
+ } = useContext(SettingsContext);
- const updateDisplayDensity = useCallback(
- ({ currentTarget }) => {
- setDisplayDensity(currentTarget.value);
- },
- [setDisplayDensity]
- );
- const updateTheme = useCallback(
- ({ currentTarget }) => {
- setTheme(currentTarget.value);
- },
- [setTheme]
- );
+ const updateDisplayDensity = ({ currentTarget }) =>
+ setDisplayDensity(currentTarget.value);
+ const updateTheme = ({ currentTarget }) => setTheme(currentTarget.value);
+ const updateappendComponentStack = ({ currentTarget }) =>
+ setAppendComponentStack(currentTarget.checked);
return (
@@ -45,6 +43,17 @@ export default function GeneralSettings(_: {||}) {
+
+
+
+
);
}
diff --git a/src/devtools/views/Settings/SettingsContext.js b/src/devtools/views/Settings/SettingsContext.js
index 70ad39f6134a..4286e3d65df4 100644
--- a/src/devtools/views/Settings/SettingsContext.js
+++ b/src/devtools/views/Settings/SettingsContext.js
@@ -1,7 +1,15 @@
// @flow
-import React, { createContext, useLayoutEffect, useMemo } from 'react';
+import React, {
+ createContext,
+ useContext,
+ useEffect,
+ useLayoutEffect,
+ useMemo,
+} from 'react';
+import { LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY } from 'src/constants';
import { useLocalStorage } from '../hooks';
+import { BridgeContext } from '../context';
import type { BrowserTheme } from '../DevTools';
@@ -16,6 +24,9 @@ type Context = {|
// Specified as a separate prop so it can trigger a re-render of FixedSizeList.
lineHeight: number,
+ appendComponentStack: boolean,
+ setAppendComponentStack: (value: boolean) => void,
+
theme: Theme,
setTheme(value: Theme): void,
|};
@@ -40,6 +51,8 @@ function SettingsContextController({
profilerPortalContainer,
settingsPortalContainer,
}: Props) {
+ const bridge = useContext(BridgeContext);
+
const [displayDensity, setDisplayDensity] = useLocalStorage(
'React::DevTools::displayDensity',
'compact'
@@ -48,6 +61,10 @@ function SettingsContextController({
'React::DevTools::theme',
'auto'
);
+ const [
+ appendComponentStack,
+ setAppendComponentStack,
+ ] = useLocalStorage(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY, true);
const documentElements = useMemo(() => {
const array: Array = [
@@ -117,12 +134,18 @@ function SettingsContextController({
}
}, [browserTheme, theme, documentElements]);
+ useEffect(() => {
+ bridge.send('updateAppendComponentStack', appendComponentStack);
+ }, [bridge, appendComponentStack]);
+
const value = useMemo(
() => ({
displayDensity,
setDisplayDensity,
theme,
setTheme,
+ appendComponentStack,
+ setAppendComponentStack,
lineHeight:
displayDensity === 'compact'
? compactLineHeight
@@ -134,6 +157,8 @@ function SettingsContextController({
displayDensity,
setDisplayDensity,
setTheme,
+ appendComponentStack,
+ setAppendComponentStack,
theme,
]
);
diff --git a/src/hook.js b/src/hook.js
index 168aa3591a43..3e9e55d035b0 100644
--- a/src/hook.js
+++ b/src/hook.js
@@ -7,6 +7,11 @@
* @flow
*/
+import {
+ patch as patchConsole,
+ registerRenderer as registerRendererWithConsole,
+} from './backend/console';
+
import type { DevToolsHook } from 'src/backend/types';
declare var window: any;
@@ -155,6 +160,32 @@ export function installHook(target: any): DevToolsHook | null {
? 'deadcode'
: detectReactBuildType(renderer);
+ // Patching the console enables DevTools to do a few useful things:
+ // * Append component stacks to warnings and error messages
+ // * Disable logging during re-renders to inspect hooks (see inspectHooksOfFiber)
+ //
+ // For React Native, we intentionally patch early (during injection).
+ // This provides React Native developers with components stacks even if they don't run DevTools.
+ // This won't work for DOM though, since this entire file is eval'ed and inserted as a script tag.
+ // In that case, we'll patch later (when the frontend attaches).
+ //
+ // Don't patch in test environments because we don't want to interfere with Jest's own console overrides.
+ if (process.env.NODE_ENV !== 'test') {
+ try {
+ // The installHook() function is injected by being stringified in the browser,
+ // so imports outside of this function do not get included.
+ //
+ // Normally we could check "typeof patchConsole === 'function'",
+ // 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 (window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false) {
+ registerRendererWithConsole(renderer);
+ patchConsole();
+ }
+ } catch (error) {}
+ }
+
// If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
// Otherwise the renderer won't yet exist and we can skip this step.
const attach = target.__REACT_DEVTOOLS_ATTACH__;
diff --git a/src/utils.js b/src/utils.js
index 3f73a7967173..f655c7729a01 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -8,7 +8,10 @@ import {
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
} from './constants';
import { ElementTypeRoot } from 'src/types';
-import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
+import {
+ LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
+ LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
+} from './constants';
import { ComponentFilterElementType, ElementTypeHostComponent } from './types';
import {
ElementTypeClass,
@@ -198,6 +201,23 @@ export function saveComponentFilters(
);
}
+export function getAppendComponentStack(): boolean {
+ try {
+ const raw = localStorageGetItem(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY);
+ if (raw != null) {
+ return JSON.parse(raw);
+ }
+ } catch (error) {}
+ return true;
+}
+
+export function setAppendComponentStack(value: boolean): void {
+ localStorageSetItem(
+ LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
+ JSON.stringify(value)
+ );
+}
+
export function separateDisplayNameAndHOCs(
displayName: string | null,
type: ElementType
diff --git a/yarn.lock b/yarn.lock
index 80d36901ff0a..616e6e264520 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9944,20 +9944,20 @@ react-color@^2.11.7:
object-assign "^4.1.0"
prop-types "^15.5.10"
-react-dom@^0.0.0-50b50c26f:
- version "0.0.0-50b50c26f"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-50b50c26f.tgz#3cd8da0f2276ed4b7a926e1807d2675b2eb40227"
- integrity sha512-da9qleWDdBdAguEIDvvpFE0iuS8hfcCSGgZTYKRQMlSh5A94Ktr1otL4rgDTFH+bNsOwz3XrvEBYRA6WaE9xzQ==
+react-dom@^0.0.0-424099da6:
+ version "0.0.0-424099da6"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-424099da6.tgz#2a6392d5730fd7688d7ec5a56b2f9f8e3627d271"
+ integrity sha512-B6x2YWaw06xJ0br9wsny6a9frqmaNAw+vGrm08W7JZp7WfiBYKhM6Nt9seijaAVzajp49fe18orNMgL12Lafsg==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
- scheduler "0.0.0-50b50c26f"
+ scheduler "0.0.0-424099da6"
-react-is@0.0.0-50b50c26f, react-is@^0.0.0-50b50c26f:
- version "0.0.0-50b50c26f"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-0.0.0-50b50c26f.tgz#c4003ffffef9bd2b287979f9041a23d12a607bf2"
- integrity sha512-9Y6ZvdOVmOxXs9mGuFy6eXHBww8RJCtJAh94b1hkbjhnW8Mb5ADScDoxJBVxcNuX9hvDkhENspC96ZQK1NIv3g==
+react-is@0.0.0-424099da6:
+ version "0.0.0-424099da6"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-0.0.0-424099da6.tgz#a8b1322bbb1ef014b33ee3bff30d3be9a4729baa"
+ integrity sha512-VMFvIdqNV0eB8YxmE9katf3XM4qbdKGhLYANfohwktTryrWWOUOoVRX6IHm4iN06LgHwWLBOBP/YARc1qzuF2w==
react-is@^16.8.1:
version "16.8.3"
@@ -9969,15 +9969,15 @@ react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
-react-test-renderer@^0.0.0-50b50c26f:
- version "0.0.0-50b50c26f"
- resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-0.0.0-50b50c26f.tgz#1a85cf9073ef5a932d03bee36fcfd9bf15aeae2c"
- integrity sha512-gWc4L+mFIUCjvBpafR88n4/i/oaKHD6rzVyZY+XBou9MNtr2rRkjePOhBVsiYlCwkj+zZi6klV9b05TMzftosA==
+react-test-renderer@^0.0.0-424099da6:
+ version "0.0.0-424099da6"
+ resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-0.0.0-424099da6.tgz#75272c39e0b45e99dbd674977a4afc32a2d37f81"
+ integrity sha512-tF9NutO52Js52L390poDUvnN43j77SekXyIt0KzQa+HdgY7uvvzKH9ouqCz3M5f52eDZDff4OG4cZuZ+lNLIfQ==
dependencies:
object-assign "^4.1.1"
prop-types "^15.6.2"
- react-is "0.0.0-50b50c26f"
- scheduler "0.0.0-50b50c26f"
+ react-is "0.0.0-424099da6"
+ scheduler "0.0.0-424099da6"
react-virtualized-auto-sizer@^1.0.2:
version "1.0.2"
@@ -9990,10 +9990,10 @@ react-window@./vendor/react-window:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
-react@^0.0.0-50b50c26f:
- version "0.0.0-50b50c26f"
- resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-50b50c26f.tgz#b782b579ce1f5d8bd696c5e45c744714ebecb111"
- integrity sha512-jUAzS4DeWTdUZ/3kqm2T6C9OIpiAf2qdwVamCts0qzwYVni1/gUTOWK1ui0J+eaRzKxrIEzVvmCMxFd35lP/pA==
+react@^0.0.0-424099da6:
+ version "0.0.0-424099da6"
+ resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-424099da6.tgz#bf9155a10bb09783cfdc9e79438062af4b249861"
+ integrity sha512-z/brDYS4RaX3+zknH8nIV7i9B7We3hFBdD0QWhDKKgEHInFLF1Y/+2GsdedDI69GWw8Hv6mw1iilycHjHRaCZA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
@@ -10658,10 +10658,10 @@ sax@>=0.6.0, sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-scheduler@0.0.0-50b50c26f, scheduler@^0.0.0-50b50c26f:
- version "0.0.0-50b50c26f"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-50b50c26f.tgz#09bedde1c64d7a042b557bee2dbf5faf5fd58a50"
- integrity sha512-LBN3zrP8iBdILOoYxybFtkU7j+ldZTHORKyYyVLwXuIwGQ8/Xhs5VZjNQ5R2Xru2zv3GGVpJSbd47EpDuD2EHw==
+scheduler@0.0.0-424099da6, scheduler@^0.0.0-424099da6:
+ version "0.0.0-424099da6"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-424099da6.tgz#5311edfc2716479475517fdcbc24909948026bdb"
+ integrity sha512-eDsz8sdikcel1lKRDJhUZ17K22rOdmJAV07coMgIvdX0MoHh9cVNQ+iryU7O9Gi/c7ySE3DaX1M9xBqAqyXA3g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"