react/packages/react-devtools-shared/src/hook.js

545 lines
18 KiB
JavaScript
Raw Normal View History

2019-01-23 03:04:37 +08:00
/**
* Install the hook on window, which is an event emitter.
* Note because Chrome content scripts cannot directly modify the window object,
* we are evaling this function by inserting a script tag.
* That's why we have to inline the whole event emitter implementation,
* the string format implementation, and part of the console implementation here.
2019-01-23 03:04:37 +08:00
*
* @flow
*/
import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools';
import {
patch as patchConsole,
registerRenderer as registerRendererWithConsole,
} from './backend/console';
2019-08-14 08:58:03 +08:00
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
2019-01-23 03:04:37 +08:00
declare var window: any;
export function installHook(target: any): DevToolsHook | null {
2019-08-14 12:59:07 +08:00
if (target.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
return null;
}
2019-01-23 03:04:37 +08:00
let targetConsole: Object = console;
let targetConsoleMethods = {};
for (const method in console) {
targetConsoleMethods[method] = console[method];
}
function dangerous_setTargetConsoleForTesting(
targetConsoleForTesting: Object,
): void {
targetConsole = targetConsoleForTesting;
targetConsoleMethods = {};
for (const method in targetConsole) {
targetConsoleMethods[method] = console[method];
}
}
2019-01-23 03:04:37 +08:00
function detectReactBuildType(renderer) {
try {
if (typeof renderer.version === 'string') {
// React DOM Fiber (16+)
2019-01-23 03:04:37 +08:00
if (renderer.bundleType > 0) {
// This is not a production build.
// We are currently only using 0 (PROD) and 1 (DEV)
// but might add 2 (PROFILE) in the future.
return 'development';
}
// React 16 uses flat bundles. If we report the bundle as production
// version, it means we also minified and envified it ourselves.
return 'production';
// Note: There is still a risk that the CommonJS entry point has not
// been envified or uglified. In this case the user would have *both*
// development and production bundle, but only the prod one would run.
// This would be really bad. We have a separate check for this because
// it happens *outside* of the renderer injection. See `checkDCE` below.
}
const toString = Function.prototype.toString;
if (renderer.Mount && renderer.Mount._renderNewRootComponent) {
// React DOM Stack
const renderRootCode = toString.call(
2019-08-14 08:58:03 +08:00
renderer.Mount._renderNewRootComponent,
);
// Filter out bad results (if that is even possible):
if (renderRootCode.indexOf('function') !== 0) {
// Hope for the best if we're not sure.
return 'production';
}
// Check for React DOM Stack < 15.1.0 in development.
// If it contains "storedMeasure" call, it's wrapped in ReactPerf (DEV only).
// This would be true even if it's minified, as method name still matches.
if (renderRootCode.indexOf('storedMeasure') !== -1) {
return 'development';
}
// For other versions (and configurations) it's not so easy.
// Let's quickly exclude proper production builds.
// If it contains a warning message, it's either a DEV build,
// or an PROD build without proper dead code elimination.
if (renderRootCode.indexOf('should be a pure function') !== -1) {
// Now how do we tell a DEV build from a bad PROD build?
// If we see NODE_ENV, we're going to assume this is a dev build
// because most likely it is referring to an empty shim.
if (renderRootCode.indexOf('NODE_ENV') !== -1) {
return 'development';
}
// If we see "development", we're dealing with an envified DEV build
// (such as the official React DEV UMD).
if (renderRootCode.indexOf('development') !== -1) {
return 'development';
}
// I've seen process.env.NODE_ENV !== 'production' being smartly
// replaced by `true` in DEV by Webpack. I don't know how that
// works but we can safely guard against it because `true` was
// never used in the function source since it was written.
if (renderRootCode.indexOf('true') !== -1) {
return 'development';
}
// By now either it is a production build that has not been minified,
// or (worse) this is a minified development build using non-standard
// environment (e.g. "staging"). We're going to look at whether
// the function argument name is mangled:
if (
// 0.13 to 15
renderRootCode.indexOf('nextElement') !== -1 ||
// 0.12
renderRootCode.indexOf('nextComponent') !== -1
) {
// We can't be certain whether this is a development build or not,
// but it is definitely unminified.
return 'unminified';
} else {
// This is likely a minified development build.
return 'development';
}
}
// By now we know that it's envified and dead code elimination worked,
// but what if it's still not minified? (Is this even possible?)
// Let's check matches for the first argument name.
if (
// 0.13 to 15
renderRootCode.indexOf('nextElement') !== -1 ||
// 0.12
renderRootCode.indexOf('nextComponent') !== -1
) {
return 'unminified';
}
// Seems like we're using the production version.
// However, the branch above is Stack-only so this is 15 or earlier.
return 'outdated';
}
} catch (err) {
// Weird environments may exist.
// This code needs a higher fault tolerance
// because it runs even with closed DevTools.
// TODO: should we catch errors in all injected code, and not just this part?
}
2019-01-23 03:04:37 +08:00
return 'production';
}
function checkDCE(fn: Function) {
// This runs for production versions of React.
// Needs to be super safe.
try {
const toString = Function.prototype.toString;
const code = toString.call(fn);
// This is a string embedded in the passed function under DEV-only
// condition. However the function executes only in PROD. Therefore,
// if we see it, dead code elimination did not work.
if (code.indexOf('^_^') > -1) {
// Remember to report during next injection.
hasDetectedBadDCE = true;
// Bonus: throw an exception hoping that it gets picked up by a reporting system.
// Not synchronously so that it doesn't break the calling code.
setTimeout(function() {
throw new Error(
'React is running in production mode, but dead code ' +
'elimination has not been applied. Read how to correctly ' +
'configure React for production: ' +
'https://reactjs.org/link/perf-use-production-build',
2019-01-23 03:04:37 +08:00
);
});
}
} catch (err) {}
}
// NOTE: KEEP IN SYNC with src/backend/utils.js
function format(
maybeMessage: any,
...inputArgs: $ReadOnlyArray<any>
): string {
const args = inputArgs.slice();
// Symbols cannot be concatenated with Strings.
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
let formatted = String(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;
});
}
}
// Arguments that remain after formatting.
if (args.length) {
for (let i = 0; i < args.length; i++) {
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
formatted += ' ' + String(args[i]);
}
}
// Update escaped %% values.
formatted = formatted.replace(/%{2,2}/g, '%');
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
return String(formatted);
}
let unpatchFn = null;
// NOTE: KEEP IN SYNC with src/backend/console.js:patchForStrictMode
// This function hides or dims console logs during the initial double renderer
// in Strict Mode. We need this function because during initial render,
// React and DevTools are connecting and the renderer interface isn't avaiable
// and we want to be able to have consistent logging behavior for double logs
// during the initial renderer.
function patchConsoleForInitialRenderInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
}: {
hideConsoleLogsInStrictMode: boolean,
browserTheme: BrowserTheme,
}) {
const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
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:unpatchForStrictMode
function unpatchConsoleForInitialRenderInStrictMode() {
if (unpatchFn !== null) {
unpatchFn();
unpatchFn = null;
}
}
let uidCounter = 0;
2019-01-23 03:04:37 +08:00
function inject(renderer) {
const id = ++uidCounter;
renderers.set(id, renderer);
2019-01-23 03:04:37 +08:00
2019-01-24 00:45:19 +08:00
const reactBuildType = hasDetectedBadDCE
? 'deadcode'
: detectReactBuildType(renderer);
2019-01-23 03:04:37 +08:00
// Patching the console enables DevTools to do a few useful things:
// * Append component stacks to warnings and error messages
// * Disabling or marking logs during a double render in Strict Mode
// * 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 only patch parts of the console that are needed during the first render
// and patch everything else later (when the frontend attaches).
//
// Don't patch in test environments because we don't want to interfere with Jest's own console overrides.
//
// 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__ && !__EXTENSION__) {
try {
const appendComponentStack =
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false;
const breakOnConsoleErrors =
window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ === true;
const showInlineWarningsAndErrors =
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ !== false;
const hideConsoleLogsInStrictMode =
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true;
const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__;
// 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.
registerRendererWithConsole(renderer);
patchConsole({
appendComponentStack,
breakOnConsoleErrors,
showInlineWarningsAndErrors,
hideConsoleLogsInStrictMode,
browserTheme,
});
} 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__;
if (typeof attach === 'function') {
const rendererInterface = attach(hook, id, renderer, target);
hook.rendererInterfaces.set(id, rendererInterface);
}
hook.emit('renderer', {
id,
renderer,
reactBuildType,
});
2019-01-23 03:04:37 +08:00
return id;
}
let hasDetectedBadDCE = false;
function sub(event, fn) {
hook.on(event, fn);
return () => hook.off(event, fn);
}
function on(event, fn) {
if (!listeners[event]) {
listeners[event] = [];
}
listeners[event].push(fn);
}
function off(event, fn) {
if (!listeners[event]) {
return;
}
const index = listeners[event].indexOf(fn);
if (index !== -1) {
listeners[event].splice(index, 1);
}
if (!listeners[event].length) {
delete listeners[event];
}
}
function emit(event, data) {
if (listeners[event]) {
listeners[event].map(fn => fn(data));
}
}
function getFiberRoots(rendererID) {
const roots = fiberRoots;
if (!roots[rendererID]) {
roots[rendererID] = new Set();
}
return roots[rendererID];
}
function onCommitFiberUnmount(rendererID, fiber) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
rendererInterface.handleCommitFiberUnmount(fiber);
2019-01-23 03:04:37 +08:00
}
}
function onCommitFiberRoot(rendererID, root, priorityLevel) {
2019-01-23 03:04:37 +08:00
const mountedRoots = hook.getFiberRoots(rendererID);
const current = root.current;
const isKnownRoot = mountedRoots.has(root);
2019-01-24 00:45:19 +08:00
const isUnmounting =
current.memoizedState == null || current.memoizedState.element == null;
2019-01-23 03:04:37 +08:00
// Keep track of mounted roots so we can hydrate when DevTools connect.
if (!isKnownRoot && !isUnmounting) {
mountedRoots.add(root);
} else if (isKnownRoot && isUnmounting) {
mountedRoots.delete(root);
}
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
rendererInterface.handleCommitFiberRoot(root, priorityLevel);
2019-01-23 03:04:37 +08:00
}
}
function onPostCommitFiberRoot(rendererID, root) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
rendererInterface.handlePostCommitFiberRoot(root);
}
}
function setStrictMode(rendererID, isStrictMode) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
if (isStrictMode) {
rendererInterface.patchConsoleForStrictMode();
} else {
rendererInterface.unpatchConsoleForStrictMode();
}
} else {
// This should only happen during initial render in the extension before DevTools
// finishes its handshake with the injected renderer
if (isStrictMode) {
const hideConsoleLogsInStrictMode =
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true;
const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__;
patchConsoleForInitialRenderInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
});
} else {
unpatchConsoleForInitialRenderInStrictMode();
}
}
}
2019-01-23 03:04:37 +08:00
// TODO: More meaningful names for "rendererInterfaces" and "renderers".
const fiberRoots = {};
const rendererInterfaces = new Map();
2019-01-23 03:04:37 +08:00
const listeners = {};
const renderers = new Map();
2019-01-23 03:04:37 +08:00
const hook: DevToolsHook = {
rendererInterfaces,
listeners,
// Fast Refresh for web relies on this.
renderers,
emit,
getFiberRoots,
inject,
on,
off,
sub,
// This is a legacy flag.
// React v16 checks the hook for this to ensure DevTools is new enough.
supportsFiber: true,
// React calls these methods.
checkDCE,
onCommitFiberUnmount,
onCommitFiberRoot,
onPostCommitFiberRoot,
setStrictMode,
};
if (__TEST__) {
hook.dangerous_setTargetConsoleForTesting = dangerous_setTargetConsoleForTesting;
}
2019-01-24 00:45:19 +08:00
Object.defineProperty(
target,
'__REACT_DEVTOOLS_GLOBAL_HOOK__',
({
2019-04-12 00:07:40 +08:00
// This property needs to be configurable for the test environment,
2021-07-15 08:42:54 +08:00
// else we won't be able to delete and recreate it between tests.
2019-04-12 00:07:40 +08:00
configurable: __DEV__,
2019-01-24 00:45:19 +08:00
enumerable: false,
get() {
return hook;
},
2019-08-14 08:58:03 +08:00
}: Object),
2019-01-24 00:45:19 +08:00
);
2019-01-23 03:04:37 +08:00
return hook;
2019-01-24 00:45:19 +08:00
}