import type {
DevToolsHook,
ReactRenderer,
} from 'react-devtools-shared/src/backend/types';
import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils';
import {COMPACT_VERSION_NAME} from 'react-devtools-extensions/src/utils';
import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
import {
getIfReloadedAndProfiling,
onReloadAndProfile,
onReloadAndProfileFlagsReset,
} from 'react-devtools-shared/src/utils';
let welcomeHasInitialized = false;
const requiredBackends = new Set<string>();
function welcome(event: $FlowFixMe) {
if (
event.source !== window ||
event.data.source !== 'react-devtools-content-script'
) {
return;
}
if (welcomeHasInitialized) {
console.warn(
'React DevTools detected duplicate welcome "message" events from the content script.',
);
return;
}
welcomeHasInitialized = true;
window.removeEventListener('message', welcome);
setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
}
function setup(hook: ?DevToolsHook) {
if (hook == null) {
return;
}
hook.renderers.forEach(renderer => {
registerRenderer(renderer, hook);
});
hook.backends.forEach((_, backendVersion) => {
requiredBackends.delete(backendVersion);
activateBackend(backendVersion, hook);
});
updateRequiredBackends();
const unsubscribeRendererListener = hook.sub('renderer', ({renderer}) => {
registerRenderer(renderer, hook);
updateRequiredBackends();
});
const unsubscribeBackendInstallationListener = hook.sub(
'devtools-backend-installed',
version => {
activateBackend(version, hook);
updateRequiredBackends();
},
);
const unsubscribeShutdownListener: () => void = hook.sub('shutdown', () => {
unsubscribeRendererListener();
unsubscribeBackendInstallationListener();
unsubscribeShutdownListener();
});
}
function registerRenderer(renderer: ReactRenderer, hook: DevToolsHook) {
let version = renderer.reconcilerVersion || renderer.version;
if (!hasAssignedBackend(version)) {
version = COMPACT_VERSION_NAME;
}
if (!hook.backends.has(version)) {
requiredBackends.add(version);
}
}
function activateBackend(version: string, hook: DevToolsHook) {
const backend = hook.backends.get(version);
if (!backend) {
throw new Error(`Could not find backend for version "${version}"`);
}
const {Agent, Bridge, initBackend, setupNativeStyleEditor} = backend;
const bridge = new Bridge({
listen(fn) {
const listener = (event: $FlowFixMe) => {
if (
event.source !== window ||
!event.data ||
event.data.source !== 'react-devtools-content-script' ||
!event.data.payload
) {
return;
}
fn(event.data.payload);
};
window.addEventListener('message', listener);
return () => {
window.removeEventListener('message', listener);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
window.postMessage(
{
source: 'react-devtools-bridge',
payload: {event, payload},
},
'*',
transferable,
);
},
});
const agent = new Agent(
bridge,
getIfReloadedAndProfiling(),
onReloadAndProfile,
);
onReloadAndProfileFlagsReset();
agent.addListener('shutdown', () => {
hook.emit('shutdown');
delete window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__;
});
initBackend(hook, agent, window, getIsReloadAndProfileSupported());
if (typeof setupNativeStyleEditor === 'function' && hook.resolveRNStyle) {
setupNativeStyleEditor(
bridge,
agent,
hook.resolveRNStyle,
hook.nativeStyleEditorValidAttributes,
);
}
bridge.send('extensionBackendInitialized');
requiredBackends.delete(version);
}
function updateRequiredBackends() {
if (requiredBackends.size === 0) {
return;
}
window.postMessage(
{
source: 'react-devtools-backend-manager',
payload: {
type: 'require-backends',
versions: Array.from(requiredBackends),
},
},
'*',
);
}
if (!window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__) {
window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__ = true;
window.addEventListener('message', welcome);
}