DevTools: Add break-on-warn feature (#19048)

This commit adds a new tab to the Settings modal: Debugging

This new tab has the append component stacks feature and a new one: break on warn

This new feature adds a debugger statement into the console override
This commit is contained in:
Brian Vaughn
2020-05-29 14:34:43 -07:00
committed by GitHub
parent 89edb0eae3
commit 2efe63d99c
19 changed files with 280 additions and 78 deletions
+11 -5
View File
@@ -161,8 +161,8 @@ export default class Agent extends EventEmitter<{|
);
bridge.addListener('shutdown', this.shutdown);
bridge.addListener(
'updateAppendComponentStack',
this.updateAppendComponentStack,
'updateConsolePatchSettings',
this.updateConsolePatchSettings,
);
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
bridge.addListener('viewAttributeSource', this.viewAttributeSource);
@@ -443,13 +443,19 @@ export default class Agent extends EventEmitter<{|
}
};
updateAppendComponentStack = (appendComponentStack: boolean) => {
updateConsolePatchSettings = ({
appendComponentStack,
breakOnConsoleErrors,
}: {|
appendComponentStack: boolean,
breakOnConsoleErrors: boolean,
|}) => {
// If the frontend preference has change,
// or in the case of React Native- if the backend is just finding out the preference-
// then install or uninstall the console overrides.
// It's safe to call these methods multiple times, so we don't need to worry about that.
if (appendComponentStack) {
patchConsole();
if (appendComponentStack || breakOnConsoleErrors) {
patchConsole({appendComponentStack, breakOnConsoleErrors});
} else {
unpatchConsole();
}
+62 -30
View File
@@ -80,9 +80,25 @@ export function registerRenderer(renderer: ReactRenderer): void {
}
}
const consoleSettingsRef = {
appendComponentStack: false,
breakOnConsoleErrors: false,
};
// Patches whitelisted console methods to append component stack for the current fiber.
// Call unpatch() to remove the injected behavior.
export function patch(): void {
export function patch({
appendComponentStack,
breakOnConsoleErrors,
}: {
appendComponentStack: boolean,
breakOnConsoleErrors: boolean,
}): void {
// Settings may change after we've patched the console.
// Using a shared ref allows the patch function to read the latest values.
consoleSettingsRef.appendComponentStack = appendComponentStack;
consoleSettingsRef.breakOnConsoleErrors = breakOnConsoleErrors;
if (unpatchFn !== null) {
// Don't patch twice.
return;
@@ -105,40 +121,56 @@ export function patch(): void {
targetConsole[method]);
const overrideMethod = (...args) => {
try {
// 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.
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const alreadyHasComponentStack =
lastArg !== null &&
(PREFIX_REGEX.test(lastArg) ||
ROW_COLUMN_NUMBER_REGEX.test(lastArg));
const latestAppendComponentStack =
consoleSettingsRef.appendComponentStack;
const latestBreakOnConsoleErrors =
consoleSettingsRef.breakOnConsoleErrors;
if (!alreadyHasComponentStack) {
// If there's a component stack for at least one of the injected renderers, append it.
// 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,
workTagMap,
} of injectedRenderers.values()) {
const current: ?Fiber = getCurrentFiber();
if (current != null) {
const componentStack = getStackByFiberInDevAndProd(
workTagMap,
current,
currentDispatcherRef,
);
if (componentStack !== '') {
args.push(componentStack);
if (latestAppendComponentStack) {
try {
// 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.
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const alreadyHasComponentStack =
lastArg !== null &&
(PREFIX_REGEX.test(lastArg) ||
ROW_COLUMN_NUMBER_REGEX.test(lastArg));
if (!alreadyHasComponentStack) {
// If there's a component stack for at least one of the injected renderers, append it.
// 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,
workTagMap,
} of injectedRenderers.values()) {
const current: ?Fiber = getCurrentFiber();
if (current != null) {
const componentStack = getStackByFiberInDevAndProd(
workTagMap,
current,
currentDispatcherRef,
);
if (componentStack !== '') {
args.push(componentStack);
}
break;
}
break;
}
}
} catch (error) {
// Don't let a DevTools or React internal error interfere with logging.
}
} catch (error) {
// Don't let a DevTools or React internal error interfere with logging.
}
if (latestBreakOnConsoleErrors) {
// --- 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);
+10 -3
View File
@@ -430,11 +430,18 @@ export function attach(
if (process.env.NODE_ENV !== 'test') {
registerRendererWithConsole(renderer);
// The renderer interface can't read this preference directly,
// The renderer interface can't read these preferences directly,
// because it is stored in localStorage within the context of the extension.
// It relies on the extension to pass the preference through via the global.
if (window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false) {
patchConsole();
const appendComponentStack =
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false;
const breakOnConsoleErrors =
window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ === true;
if (appendComponentStack || breakOnConsoleErrors) {
patchConsole({
appendComponentStack,
breakOnConsoleErrors,
});
}
}