Files
react/packages/react-devtools-inline/src/frontend.js
Brian Vaughn 911f92a44d DevTools: Support mulitple DevTools instances per page (#22949)
This is being done so that we can embed DevTools within the new React (beta) docs.

The primary changes here are to `react-devtools-inline/backend`:
* Add a new `createBridge` API
* Add an option to the `activate` method to support passing in the custom bridge object.

The `react-devtools-inline` README has been updated to include these new methods.

To verify these changes, this commit also updates the test shell to add a new entry-point for multiple DevTools.

This commit also replaces two direct calls to `window.postMessage()` with `bridge.send()` (and adds the related Flow types).
2021-12-14 12:16:16 -05:00

106 lines
3.1 KiB
JavaScript

/** @flow */
import * as React from 'react';
import {forwardRef} from 'react';
import Bridge from 'react-devtools-shared/src/bridge';
import Store from 'react-devtools-shared/src/devtools/store';
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
import {
getAppendComponentStack,
getBreakOnConsoleErrors,
getSavedComponentFilters,
getShowInlineWarningsAndErrors,
getHideConsoleLogsInStrictMode,
} from 'react-devtools-shared/src/utils';
import type {Wall} from 'react-devtools-shared/src/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Props} from 'react-devtools-shared/src/devtools/views/DevTools';
type Config = {|
supportsNativeInspection?: boolean,
|};
export function createStore(bridge: FrontendBridge, config?: Config): Store {
return new Store(bridge, {
checkBridgeProtocolCompatibility: true,
supportsTraceUpdates: true,
supportsTimeline: true,
supportsNativeInspection: config?.supportsNativeInspection !== false,
});
}
export function createBridge(
contentWindow: window,
wall?: Wall,
): FrontendBridge {
if (wall == null) {
wall = {
listen(fn) {
const onMessage = ({data}) => {
fn(data);
};
window.addEventListener('message', onMessage);
return () => {
window.removeEventListener('message', onMessage);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
contentWindow.postMessage({event, payload}, '*', transferable);
},
};
}
return (new Bridge(wall): FrontendBridge);
}
export function initialize(
contentWindow: window,
{
bridge,
store,
}: {|
bridge?: FrontendBridge,
store?: Store,
|} = {},
): React.AbstractComponent<Props, mixed> {
if (bridge == null) {
bridge = createBridge(contentWindow);
}
// Type refinement.
const frontendBridge = ((bridge: any): FrontendBridge);
if (store == null) {
store = createStore(frontendBridge);
}
const onGetSavedPreferences = () => {
// This is the only message we're listening for,
// so it's safe to cleanup after we've received it.
frontendBridge.removeListener('getSavedPreferences', onGetSavedPreferences);
const data = {
appendComponentStack: getAppendComponentStack(),
breakOnConsoleErrors: getBreakOnConsoleErrors(),
componentFilters: getSavedComponentFilters(),
showInlineWarningsAndErrors: getShowInlineWarningsAndErrors(),
hideConsoleLogsInStrictMode: getHideConsoleLogsInStrictMode(),
};
// The renderer interface can't read saved preferences directly,
// because they are stored in localStorage within the context of the extension.
// Instead it relies on the extension to pass them through.
frontendBridge.send('savedPreferences', data);
};
frontendBridge.addListener('getSavedPreferences', onGetSavedPreferences);
const ForwardRef = forwardRef<Props, mixed>((props, ref) => (
<DevTools ref={ref} bridge={frontendBridge} store={store} {...props} />
));
ForwardRef.displayName = 'DevTools';
return ForwardRef;
}