import type {
DevToolsHook,
WorkTagMap,
CurrentDispatcherRef,
} from 'react-devtools-shared/src/backend/types';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
getDisplayNameForFiberType,
ReactPriorityLevelsType,
} from 'react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants';
import {getInternalReactConstants} from 'react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants';
export {createTools} from './DevToolsFacadeTools';
export type {Tools} from './DevToolsFacadeTools';
export type RendererInternals = {
getDisplayNameForFiber: getDisplayNameForFiberType,
ReactTypeOfWork: WorkTagMap,
ReactPriorityLevels: ReactPriorityLevelsType,
currentDispatcherRef: CurrentDispatcherRef,
};
export type ProfilingState = {
isActive: boolean,
currentTraceName: string | null,
traces: Map<string, any>,
onCommit:
| ((
rendererID: number,
root: FiberRoot,
schedulerPriority: number | void,
) => void)
| null,
onPostCommit: ((root: FiberRoot) => void) | null,
};
export type Facade = {
hook: DevToolsHook,
fiberRoots: Map<number, Set<FiberRoot>>,
rendererInternals: Map<number, RendererInternals>,
profilingState: ProfilingState,
};
function initializeRendererInternals(
rendererInternals: Map<number, RendererInternals>,
id: number,
renderer: any,
): void {
const version = renderer.reconcilerVersion || renderer.version;
if (version == null) {
console.error(
'react-devtools-facade: Renderer %s has no version, internals not initialized.',
id,
);
return;
}
const {getDisplayNameForFiber, ReactTypeOfWork, ReactPriorityLevels} =
getInternalReactConstants(version);
rendererInternals.set(id, {
getDisplayNameForFiber,
ReactTypeOfWork,
ReactPriorityLevels,
currentDispatcherRef: renderer.currentDispatcherRef,
});
}
function recordCommitFiberRoot(
fiberRoots: Map<number, Set<FiberRoot>>,
profilingState: ProfilingState,
rendererID: number,
root: any,
schedulerPriority?: number,
): void {
let mountedRoots = fiberRoots.get(rendererID);
if (mountedRoots == null) {
mountedRoots = new Set();
fiberRoots.set(rendererID, mountedRoots);
}
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);
}
if (profilingState.isActive && profilingState.onCommit != null) {
profilingState.onCommit(rendererID, root, schedulerPriority);
}
}
function attachToExistingHook(
hook: any,
fiberRoots: Map<number, Set<FiberRoot>>,
rendererInternals: Map<number, RendererInternals>,
profilingState: ProfilingState,
): void {
if (hook.renderers instanceof Map) {
hook.renderers.forEach((renderer: any, id: number) => {
if (!rendererInternals.has(id)) {
initializeRendererInternals(rendererInternals, id, renderer);
}
if (typeof hook.getFiberRoots === 'function') {
let roots = fiberRoots.get(id);
if (roots == null) {
roots = new Set();
fiberRoots.set(id, roots);
}
const mountedRoots = roots;
hook.getFiberRoots(id).forEach((root: FiberRoot) => {
mountedRoots.add(root);
});
}
});
}
const originalInject = hook.inject;
hook.inject = function inject(renderer: any, ...rest: Array<mixed>): number {
const id = originalInject.call(hook, renderer, ...rest);
if (typeof id === 'number') {
initializeRendererInternals(rendererInternals, id, renderer);
}
return id;
};
const originalOnCommitFiberRoot = hook.onCommitFiberRoot;
hook.onCommitFiberRoot = function onCommitFiberRoot(
rendererID: number,
root: any,
schedulerPriority?: number,
...rest: Array<mixed>
) {
if (typeof originalOnCommitFiberRoot === 'function') {
originalOnCommitFiberRoot.call(
hook,
rendererID,
root,
schedulerPriority,
...rest,
);
}
recordCommitFiberRoot(
fiberRoots,
profilingState,
rendererID,
root,
schedulerPriority,
);
};
const originalOnPostCommitFiberRoot = hook.onPostCommitFiberRoot;
hook.onPostCommitFiberRoot = function onPostCommitFiberRoot(
rendererID: number,
root: any,
...rest: Array<mixed>
) {
if (typeof originalOnPostCommitFiberRoot === 'function') {
originalOnPostCommitFiberRoot.call(hook, rendererID, root, ...rest);
}
if (profilingState.isActive && profilingState.onPostCommit != null) {
profilingState.onPostCommit(root);
}
};
}
export function installFacade(target?: any = globalThis): Facade {
const fiberRoots: Map<number, Set<FiberRoot>> = new Map();
const rendererInternals: Map<number, RendererInternals> = new Map();
const profilingState: ProfilingState = {
isActive: false,
currentTraceName: null,
traces: new Map(),
onCommit: null,
onPostCommit: null,
};
const existingHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (existingHook != null) {
attachToExistingHook(
existingHook,
fiberRoots,
rendererInternals,
profilingState,
);
return {hook: existingHook, fiberRoots, rendererInternals, profilingState};
}
let registeredRenderersCount = 0;
const hook: DevToolsHook = {
listeners: {},
rendererInterfaces: new Map(),
renderers: new Map(),
hasUnsupportedRendererAttached: false,
backends: new Map(),
emit() {},
getFiberRoots(rendererID: number) {
let roots = fiberRoots.get(rendererID);
if (roots == null) {
roots = new Set();
fiberRoots.set(rendererID, roots);
}
return roots;
},
inject(renderer: any): number {
const id = registeredRenderersCount++;
hook.renderers.set(id, renderer);
initializeRendererInternals(rendererInternals, id, renderer);
return id;
},
on() {},
off() {},
sub() {
return () => {};
},
supportsFiber: true,
supportsFlight: true,
checkDCE() {},
onCommitFiberRoot(
rendererID: number,
root: any,
schedulerPriority?: number,
) {
recordCommitFiberRoot(
fiberRoots,
profilingState,
rendererID,
root,
schedulerPriority,
);
},
onCommitFiberUnmount() {},
onPostCommitFiberRoot(rendererID: number, root: any) {
if (profilingState.isActive && profilingState.onPostCommit != null) {
profilingState.onPostCommit(root);
}
},
getInternalModuleRanges(): Array<[string, string]> {
return [];
},
registerInternalModuleStart() {},
registerInternalModuleStop() {},
};
Object.defineProperty(target, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {
configurable: __DEV__,
enumerable: false,
get() {
return hook;
},
});
return {hook, fiberRoots, rendererInternals, profilingState};
}