mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
655affa302
Co-authored-by: shengxinjing <316783812@qq.com>
194 lines
6.3 KiB
JavaScript
194 lines
6.3 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
|
import type {CurrentDispatcherRef, ReactRenderer, WorkTagMap} from './types';
|
|
|
|
import {getInternalReactConstants} from './renderer';
|
|
import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
|
|
|
|
const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn'];
|
|
|
|
// React's custom built component stack strings match "\s{4}in"
|
|
// Chrome's prefix matches "\s{4}at"
|
|
const PREFIX_REGEX = /\s{4}(in|at)\s{1}/;
|
|
// Firefox and Safari have no prefix ("")
|
|
// but we can fallback to looking for location info (e.g. "foo.js:12:345")
|
|
const ROW_COLUMN_NUMBER_REGEX = /:\d+:\d+(\n|$)/;
|
|
|
|
const injectedRenderers: Map<
|
|
ReactRenderer,
|
|
{|
|
|
currentDispatcherRef: CurrentDispatcherRef,
|
|
getCurrentFiber: () => Fiber | null,
|
|
workTagMap: WorkTagMap,
|
|
|},
|
|
> = new Map();
|
|
|
|
let targetConsole: Object = console;
|
|
let targetConsoleMethods = {};
|
|
for (const method in console) {
|
|
targetConsoleMethods[method] = console[method];
|
|
}
|
|
|
|
let unpatchFn: null | (() => void) = null;
|
|
|
|
// Enables e.g. Jest tests to inject a mock console object.
|
|
export function dangerous_setTargetConsoleForTesting(
|
|
targetConsoleForTesting: Object,
|
|
): void {
|
|
targetConsole = targetConsoleForTesting;
|
|
|
|
targetConsoleMethods = {};
|
|
for (const method in targetConsole) {
|
|
targetConsoleMethods[method] = console[method];
|
|
}
|
|
}
|
|
|
|
// v16 renderers should use this method to inject internals necessary to generate a component stack.
|
|
// These internals will be used if the console is patched.
|
|
// Injecting them separately allows the console to easily be patched or un-patched later (at runtime).
|
|
export function registerRenderer(renderer: ReactRenderer): void {
|
|
const {
|
|
currentDispatcherRef,
|
|
getCurrentFiber,
|
|
findFiberByHostInstance,
|
|
version,
|
|
} = renderer;
|
|
|
|
// Ignore React v15 and older because they don't expose a component stack anyway.
|
|
if (typeof findFiberByHostInstance !== 'function') {
|
|
return;
|
|
}
|
|
|
|
// currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
|
|
// getCurrentFiber gets injected for v16.9+.
|
|
if (currentDispatcherRef != null && typeof getCurrentFiber === 'function') {
|
|
const {ReactTypeOfWork} = getInternalReactConstants(version);
|
|
|
|
injectedRenderers.set(renderer, {
|
|
currentDispatcherRef,
|
|
getCurrentFiber,
|
|
workTagMap: ReactTypeOfWork,
|
|
});
|
|
}
|
|
}
|
|
|
|
const consoleSettingsRef = {
|
|
appendComponentStack: false,
|
|
breakOnConsoleErrors: false,
|
|
};
|
|
|
|
// Patches console methods to append component stack for the current fiber.
|
|
// Call unpatch() to remove the injected behavior.
|
|
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;
|
|
}
|
|
|
|
const originalConsoleMethods = {};
|
|
|
|
unpatchFn = () => {
|
|
for (const method in originalConsoleMethods) {
|
|
try {
|
|
// $FlowFixMe property error|warn is not writable.
|
|
targetConsole[method] = originalConsoleMethods[method];
|
|
} catch (error) {}
|
|
}
|
|
};
|
|
|
|
APPEND_STACK_TO_METHODS.forEach(method => {
|
|
try {
|
|
const originalMethod = (originalConsoleMethods[method] =
|
|
targetConsole[method]);
|
|
|
|
const overrideMethod = (...args) => {
|
|
const latestAppendComponentStack =
|
|
consoleSettingsRef.appendComponentStack;
|
|
const latestBreakOnConsoleErrors =
|
|
consoleSettingsRef.breakOnConsoleErrors;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} 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);
|
|
};
|
|
|
|
overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod;
|
|
|
|
// $FlowFixMe property error|warn is not writable.
|
|
targetConsole[method] = overrideMethod;
|
|
} catch (error) {}
|
|
});
|
|
}
|
|
|
|
// Removed component stack patch from console methods.
|
|
export function unpatch(): void {
|
|
if (unpatchFn !== null) {
|
|
unpatchFn();
|
|
unpatchFn = null;
|
|
}
|
|
}
|