import type {BrowserTheme} from './frontend/types';
import type {
DevToolsHook,
Handler,
ReactRenderer,
RendererID,
RendererInterface,
DevToolsBackend,
} from './backend/types';
declare var window: any;
export function installHook(target: any): DevToolsHook | null {
if (target.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
return null;
}
let targetConsole: Object = console;
let targetConsoleMethods: {[string]: $FlowFixMe} = {};
for (const method in console) {
targetConsoleMethods[method] = console[method];
}
function dangerous_setTargetConsoleForTesting(
targetConsoleForTesting: Object,
): void {
targetConsole = targetConsoleForTesting;
targetConsoleMethods = ({}: {[string]: $FlowFixMe});
for (const method in targetConsole) {
targetConsoleMethods[method] = console[method];
}
}
function detectReactBuildType(renderer: ReactRenderer) {
try {
if (typeof renderer.version === 'string') {
if (renderer.bundleType > 0) {
return 'development';
}
return 'production';
}
const toString = Function.prototype.toString;
if (renderer.Mount && renderer.Mount._renderNewRootComponent) {
const renderRootCode = toString.call(
renderer.Mount._renderNewRootComponent,
);
if (renderRootCode.indexOf('function') !== 0) {
return 'production';
}
if (renderRootCode.indexOf('storedMeasure') !== -1) {
return 'development';
}
if (renderRootCode.indexOf('should be a pure function') !== -1) {
if (renderRootCode.indexOf('NODE_ENV') !== -1) {
return 'development';
}
if (renderRootCode.indexOf('development') !== -1) {
return 'development';
}
if (renderRootCode.indexOf('true') !== -1) {
return 'development';
}
if (
renderRootCode.indexOf('nextElement') !== -1 ||
renderRootCode.indexOf('nextComponent') !== -1
) {
return 'unminified';
} else {
return 'development';
}
}
if (
renderRootCode.indexOf('nextElement') !== -1 ||
renderRootCode.indexOf('nextComponent') !== -1
) {
return 'unminified';
}
return 'outdated';
}
} catch (err) {
}
return 'production';
}
function checkDCE(fn: Function) {
try {
const toString = Function.prototype.toString;
const code = toString.call(fn);
if (code.indexOf('^_^') > -1) {
hasDetectedBadDCE = true;
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://react.dev/link/perf-use-production-build',
);
});
}
} catch (err) {}
}
function formatWithStyles(
inputArgs: $ReadOnlyArray<any>,
style?: string,
): $ReadOnlyArray<any> {
if (
inputArgs === undefined ||
inputArgs === null ||
inputArgs.length === 0 ||
(typeof inputArgs[0] === 'string' &&
inputArgs[0].match(/([^%]|^)(%c)/g)) ||
style === undefined
) {
return inputArgs;
}
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
} else {
const firstArg = inputArgs.reduce((formatStr, elem, i) => {
if (i > 0) {
formatStr += ' ';
}
switch (typeof elem) {
case 'string':
case 'boolean':
case 'symbol':
return (formatStr += '%s');
case 'number':
const formatting = Number.isInteger(elem) ? '%i' : '%f';
return (formatStr += formatting);
default:
return (formatStr += '%o');
}
}, '%c');
return [firstArg, style, ...inputArgs];
}
}
let unpatchFn = null;
function patchConsoleForInitialCommitInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
}: {
hideConsoleLogsInStrictMode: boolean,
browserTheme: BrowserTheme,
}) {
const overrideConsoleMethods = [
'error',
'group',
'groupCollapsed',
'info',
'log',
'trace',
'warn',
];
if (unpatchFn !== null) {
return;
}
const originalConsoleMethods: {[string]: $FlowFixMe} = {};
unpatchFn = () => {
for (const method in originalConsoleMethods) {
try {
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: $ReadOnlyArray<any>) => {
if (!hideConsoleLogsInStrictMode) {
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(...formatWithStyles(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;
targetConsole[method] = overrideMethod;
} catch (error) {}
});
}
function unpatchConsoleForInitialCommitInStrictMode() {
if (unpatchFn !== null) {
unpatchFn();
unpatchFn = null;
}
}
let uidCounter = 0;
function inject(renderer: ReactRenderer): number {
const id = ++uidCounter;
renderers.set(id, renderer);
const reactBuildType = hasDetectedBadDCE
? 'deadcode'
: detectReactBuildType(renderer);
if (target.hasOwnProperty('__REACT_DEVTOOLS_CONSOLE_FUNCTIONS__')) {
const {registerRendererWithConsole, patchConsoleUsingWindowValues} =
target.__REACT_DEVTOOLS_CONSOLE_FUNCTIONS__;
if (
typeof registerRendererWithConsole === 'function' &&
typeof patchConsoleUsingWindowValues === 'function'
) {
registerRendererWithConsole(renderer);
patchConsoleUsingWindowValues();
}
}
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,
});
return id;
}
let hasDetectedBadDCE = false;
function sub(event: string, fn: Handler) {
hook.on(event, fn);
return () => hook.off(event, fn);
}
function on(event: string, fn: Handler) {
if (!listeners[event]) {
listeners[event] = [];
}
listeners[event].push(fn);
}
function off(event: string, fn: Handler) {
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: string, data: any) {
if (listeners[event]) {
listeners[event].map(fn => fn(data));
}
}
function getFiberRoots(rendererID: RendererID) {
const roots = fiberRoots;
if (!roots[rendererID]) {
roots[rendererID] = new Set();
}
return roots[rendererID];
}
function onCommitFiberUnmount(rendererID: RendererID, fiber: any) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
rendererInterface.handleCommitFiberUnmount(fiber);
}
}
function onCommitFiberRoot(
rendererID: RendererID,
root: any,
priorityLevel: void | number,
) {
const mountedRoots = hook.getFiberRoots(rendererID);
const current = root.current;
const isKnownRoot = mountedRoots.has(root);
const isUnmounting =
current.memoizedState == null || current.memoizedState.element == null;
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);
}
}
function onPostCommitFiberRoot(rendererID: RendererID, root: any) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
rendererInterface.handlePostCommitFiberRoot(root);
}
}
function setStrictMode(rendererID: RendererID, isStrictMode: any) {
const rendererInterface = rendererInterfaces.get(rendererID);
if (rendererInterface != null) {
if (isStrictMode) {
rendererInterface.patchConsoleForStrictMode();
} else {
rendererInterface.unpatchConsoleForStrictMode();
}
} else {
if (isStrictMode) {
const hideConsoleLogsInStrictMode =
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true;
const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__;
patchConsoleForInitialCommitInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
});
} else {
unpatchConsoleForInitialCommitInStrictMode();
}
}
}
type StackFrameString = string;
const openModuleRangesStack: Array<StackFrameString> = [];
const moduleRanges: Array<[StackFrameString, StackFrameString]> = [];
function getTopStackFrameString(error: Error): StackFrameString | null {
const frames = error.stack.split('\n');
const frame = frames.length > 1 ? frames[1] : null;
return frame;
}
function getInternalModuleRanges(): Array<
[StackFrameString, StackFrameString],
> {
return moduleRanges;
}
function registerInternalModuleStart(error: Error) {
const startStackFrame = getTopStackFrameString(error);
if (startStackFrame !== null) {
openModuleRangesStack.push(startStackFrame);
}
}
function registerInternalModuleStop(error: Error) {
if (openModuleRangesStack.length > 0) {
const startStackFrame = openModuleRangesStack.pop();
const stopStackFrame = getTopStackFrameString(error);
if (stopStackFrame !== null) {
moduleRanges.push([startStackFrame, stopStackFrame]);
}
}
}
const fiberRoots: {[RendererID]: Set<mixed>} = {};
const rendererInterfaces = new Map<RendererID, RendererInterface>();
const listeners: {[string]: Array<Handler>} = {};
const renderers = new Map<RendererID, ReactRenderer>();
const backends = new Map<string, DevToolsBackend>();
const hook: DevToolsHook = {
rendererInterfaces,
listeners,
backends,
renderers,
emit,
getFiberRoots,
inject,
on,
off,
sub,
supportsFiber: true,
checkDCE,
onCommitFiberUnmount,
onCommitFiberRoot,
onPostCommitFiberRoot,
setStrictMode,
getInternalModuleRanges,
registerInternalModuleStart,
registerInternalModuleStop,
};
if (__TEST__) {
hook.dangerous_setTargetConsoleForTesting =
dangerous_setTargetConsoleForTesting;
}
Object.defineProperty(
target,
'__REACT_DEVTOOLS_GLOBAL_HOOK__',
({
configurable: __DEV__,
enumerable: false,
get() {
return hook;
},
}: Object),
);
return hook;
}