mirror of
https://github.com/facebook/react.git
synced 2026-02-22 03:42:05 +00:00
refactor: allow custom impl of backend realod-to-profile support check (#31048)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary In preparation to support reload-to-profile in Fusebox (#31021), we need a way to check capability of different backends, e.g. web vs React Native. ## How did you test this change? <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> * Default, e.g. existing web impl = no-op * Custom impl: is called
This commit is contained in:
18
packages/react-devtools-core/src/backend.js
vendored
18
packages/react-devtools-core/src/backend.js
vendored
@@ -13,7 +13,10 @@ import {installHook} from 'react-devtools-shared/src/hook';
|
||||
import {initBackend} from 'react-devtools-shared/src/backend';
|
||||
import {__DEBUG__} from 'react-devtools-shared/src/constants';
|
||||
import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
|
||||
import {getDefaultComponentFilters} from 'react-devtools-shared/src/utils';
|
||||
import {
|
||||
getDefaultComponentFilters,
|
||||
getIsReloadAndProfileSupported,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
|
||||
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {
|
||||
@@ -36,6 +39,7 @@ type ConnectOptions = {
|
||||
isAppActive?: () => boolean,
|
||||
websocket?: ?WebSocket,
|
||||
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
|
||||
isReloadAndProfileSupported?: boolean,
|
||||
};
|
||||
|
||||
let savedComponentFilters: Array<ComponentFilter> =
|
||||
@@ -77,6 +81,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
retryConnectionDelay = 2000,
|
||||
isAppActive = () => true,
|
||||
onSettingsUpdated,
|
||||
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
|
||||
} = options || {};
|
||||
|
||||
const protocol = useHttps ? 'wss' : 'ws';
|
||||
@@ -184,7 +189,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
hook.emit('shutdown');
|
||||
});
|
||||
|
||||
initBackend(hook, agent, window);
|
||||
initBackend(hook, agent, window, isReloadAndProfileSupported);
|
||||
|
||||
// Setup React Native style editor if the environment supports it.
|
||||
if (resolveRNStyle != null || hook.resolveRNStyle != null) {
|
||||
@@ -309,6 +314,7 @@ type ConnectWithCustomMessagingOptions = {
|
||||
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
|
||||
resolveRNStyle?: ResolveNativeStyle,
|
||||
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
|
||||
isReloadAndProfileSupported?: boolean,
|
||||
};
|
||||
|
||||
export function connectWithCustomMessagingProtocol({
|
||||
@@ -318,6 +324,7 @@ export function connectWithCustomMessagingProtocol({
|
||||
nativeStyleEditorValidAttributes,
|
||||
resolveRNStyle,
|
||||
onSettingsUpdated,
|
||||
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
|
||||
}: ConnectWithCustomMessagingOptions): Function {
|
||||
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (hook == null) {
|
||||
@@ -368,7 +375,12 @@ export function connectWithCustomMessagingProtocol({
|
||||
hook.emit('shutdown');
|
||||
});
|
||||
|
||||
const unsubscribeBackend = initBackend(hook, agent, window);
|
||||
const unsubscribeBackend = initBackend(
|
||||
hook,
|
||||
agent,
|
||||
window,
|
||||
isReloadAndProfileSupported,
|
||||
);
|
||||
|
||||
const nativeStyleResolver: ResolveNativeStyle | void =
|
||||
resolveRNStyle || hook.resolveRNStyle;
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
} 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';
|
||||
|
||||
let welcomeHasInitialized = false;
|
||||
|
||||
@@ -140,7 +141,7 @@ function activateBackend(version: string, hook: DevToolsHook) {
|
||||
hook.emit('shutdown');
|
||||
});
|
||||
|
||||
initBackend(hook, agent, window);
|
||||
initBackend(hook, agent, window, getIsReloadAndProfileSupported());
|
||||
|
||||
// Setup React Native style editor if a renderer like react-native-web has injected it.
|
||||
if (typeof setupNativeStyleEditor === 'function' && hook.resolveRNStyle) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyl
|
||||
|
||||
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {Wall} from 'react-devtools-shared/src/frontend/types';
|
||||
import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
|
||||
|
||||
function startActivation(contentWindow: any, bridge: BackendBridge) {
|
||||
const onSavedPreferences = (data: $FlowFixMe) => {
|
||||
@@ -66,7 +67,7 @@ function finishActivation(contentWindow: any, bridge: BackendBridge) {
|
||||
|
||||
const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (hook) {
|
||||
initBackend(hook, agent, contentWindow);
|
||||
initBackend(hook, agent, contentWindow, getIsReloadAndProfileSupported());
|
||||
|
||||
// Setup React Native style editor if a renderer like react-native-web has injected it.
|
||||
if (hook.resolveRNStyle) {
|
||||
|
||||
@@ -38,7 +38,7 @@ import type {
|
||||
DevToolsHookSettings,
|
||||
} from './types';
|
||||
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
|
||||
import {isSynchronousXHRSupported, isReactNativeEnvironment} from './utils';
|
||||
import {isReactNativeEnvironment} from './utils';
|
||||
|
||||
const debug = (methodName: string, ...args: Array<string>) => {
|
||||
if (__DEBUG__) {
|
||||
@@ -242,16 +242,6 @@ export default class Agent extends EventEmitter<{
|
||||
if (this._isProfiling) {
|
||||
bridge.send('profilingStatus', true);
|
||||
}
|
||||
|
||||
// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
|
||||
// If not, features like reload-and-profile will not work correctly and must be disabled.
|
||||
let isBackendStorageAPISupported = false;
|
||||
try {
|
||||
localStorage.getItem('test');
|
||||
isBackendStorageAPISupported = true;
|
||||
} catch (error) {}
|
||||
bridge.send('isBackendStorageAPISupported', isBackendStorageAPISupported);
|
||||
bridge.send('isSynchronousXHRSupported', isSynchronousXHRSupported());
|
||||
}
|
||||
|
||||
get rendererInterfaces(): {[key: RendererID]: RendererInterface, ...} {
|
||||
@@ -675,6 +665,10 @@ export default class Agent extends EventEmitter<{
|
||||
}
|
||||
};
|
||||
|
||||
onReloadAndProfileSupportedByHost: () => void = () => {
|
||||
this._bridge.send('isReloadAndProfileSupportedByBackend', true);
|
||||
};
|
||||
|
||||
reloadAndProfile: (recordChangeDescriptions: boolean) => void =
|
||||
recordChangeDescriptions => {
|
||||
sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
|
||||
|
||||
@@ -17,6 +17,7 @@ export function initBackend(
|
||||
hook: DevToolsHook,
|
||||
agent: Agent,
|
||||
global: Object,
|
||||
isReloadAndProfileSupported: boolean,
|
||||
): () => void {
|
||||
if (hook == null) {
|
||||
// DevTools didn't get injected into this page (maybe b'c of the contentType).
|
||||
@@ -94,6 +95,10 @@ export function initBackend(
|
||||
}
|
||||
});
|
||||
|
||||
if (isReloadAndProfileSupported) {
|
||||
agent.onReloadAndProfileSupportedByHost();
|
||||
}
|
||||
|
||||
return () => {
|
||||
subs.forEach(fn => fn());
|
||||
};
|
||||
|
||||
3
packages/react-devtools-shared/src/bridge.js
vendored
3
packages/react-devtools-shared/src/bridge.js
vendored
@@ -181,8 +181,7 @@ export type BackendEvents = {
|
||||
fastRefreshScheduled: [],
|
||||
getSavedPreferences: [],
|
||||
inspectedElement: [InspectedElementPayload],
|
||||
isBackendStorageAPISupported: [boolean],
|
||||
isSynchronousXHRSupported: [boolean],
|
||||
isReloadAndProfileSupportedByBackend: [boolean],
|
||||
operations: [Array<number>],
|
||||
ownersList: [OwnersList],
|
||||
overrideComponentFilters: [Array<ComponentFilter>],
|
||||
|
||||
@@ -138,16 +138,6 @@ export default class Store extends EventEmitter<{
|
||||
// Should the React Native style editor panel be shown?
|
||||
_isNativeStyleEditorSupported: boolean = false;
|
||||
|
||||
// Can the backend use the Storage API (e.g. localStorage)?
|
||||
// If not, features like reload-and-profile will not work correctly and must be disabled.
|
||||
_isBackendStorageAPISupported: boolean = false;
|
||||
|
||||
// Can DevTools use sync XHR requests?
|
||||
// If not, features like reload-and-profile will not work correctly and must be disabled.
|
||||
// This current limitation applies only to web extension builds
|
||||
// and will need to be reconsidered in the future if we add support for reload to React Native.
|
||||
_isSynchronousXHRSupported: boolean = false;
|
||||
|
||||
_nativeStyleEditorValidAttributes: $ReadOnlyArray<string> | null = null;
|
||||
|
||||
// Older backends don't support an explicit bridge protocol,
|
||||
@@ -178,10 +168,12 @@ export default class Store extends EventEmitter<{
|
||||
// These options may be initially set by a configuration option when constructing the Store.
|
||||
_supportsInspectMatchingDOMElement: boolean = false;
|
||||
_supportsClickToInspect: boolean = false;
|
||||
_supportsReloadAndProfile: boolean = false;
|
||||
_supportsTimeline: boolean = false;
|
||||
_supportsTraceUpdates: boolean = false;
|
||||
|
||||
_isReloadAndProfileFrontendSupported: boolean = false;
|
||||
_isReloadAndProfileBackendSupported: boolean = false;
|
||||
|
||||
// These options default to false but may be updated as roots are added and removed.
|
||||
_rootSupportsBasicProfiling: boolean = false;
|
||||
_rootSupportsTimelineProfiling: boolean = false;
|
||||
@@ -234,7 +226,7 @@ export default class Store extends EventEmitter<{
|
||||
this._supportsClickToInspect = true;
|
||||
}
|
||||
if (supportsReloadAndProfile) {
|
||||
this._supportsReloadAndProfile = true;
|
||||
this._isReloadAndProfileFrontendSupported = true;
|
||||
}
|
||||
if (supportsTimeline) {
|
||||
this._supportsTimeline = true;
|
||||
@@ -255,17 +247,13 @@ export default class Store extends EventEmitter<{
|
||||
);
|
||||
bridge.addListener('shutdown', this.onBridgeShutdown);
|
||||
bridge.addListener(
|
||||
'isBackendStorageAPISupported',
|
||||
this.onBackendStorageAPISupported,
|
||||
'isReloadAndProfileSupportedByBackend',
|
||||
this.onBackendReloadAndProfileSupported,
|
||||
);
|
||||
bridge.addListener(
|
||||
'isNativeStyleEditorSupported',
|
||||
this.onBridgeNativeStyleEditorSupported,
|
||||
);
|
||||
bridge.addListener(
|
||||
'isSynchronousXHRSupported',
|
||||
this.onBridgeSynchronousXHRSupported,
|
||||
);
|
||||
bridge.addListener(
|
||||
'unsupportedRendererVersion',
|
||||
this.onBridgeUnsupportedRendererVersion,
|
||||
@@ -469,13 +457,9 @@ export default class Store extends EventEmitter<{
|
||||
}
|
||||
|
||||
get supportsReloadAndProfile(): boolean {
|
||||
// Does the DevTools shell support reloading and eagerly injecting the renderer interface?
|
||||
// And if so, can the backend use the localStorage API and sync XHR?
|
||||
// All of these are currently required for the reload-and-profile feature to work.
|
||||
return (
|
||||
this._supportsReloadAndProfile &&
|
||||
this._isBackendStorageAPISupported &&
|
||||
this._isSynchronousXHRSupported
|
||||
this._isReloadAndProfileFrontendSupported &&
|
||||
this._isReloadAndProfileBackendSupported
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1433,17 +1417,13 @@ export default class Store extends EventEmitter<{
|
||||
);
|
||||
bridge.removeListener('shutdown', this.onBridgeShutdown);
|
||||
bridge.removeListener(
|
||||
'isBackendStorageAPISupported',
|
||||
this.onBackendStorageAPISupported,
|
||||
'isReloadAndProfileSupportedByBackend',
|
||||
this.onBackendReloadAndProfileSupported,
|
||||
);
|
||||
bridge.removeListener(
|
||||
'isNativeStyleEditorSupported',
|
||||
this.onBridgeNativeStyleEditorSupported,
|
||||
);
|
||||
bridge.removeListener(
|
||||
'isSynchronousXHRSupported',
|
||||
this.onBridgeSynchronousXHRSupported,
|
||||
);
|
||||
bridge.removeListener(
|
||||
'unsupportedRendererVersion',
|
||||
this.onBridgeUnsupportedRendererVersion,
|
||||
@@ -1458,18 +1438,10 @@ export default class Store extends EventEmitter<{
|
||||
}
|
||||
};
|
||||
|
||||
onBackendStorageAPISupported: (
|
||||
isBackendStorageAPISupported: boolean,
|
||||
) => void = isBackendStorageAPISupported => {
|
||||
this._isBackendStorageAPISupported = isBackendStorageAPISupported;
|
||||
|
||||
this.emit('supportsReloadAndProfile');
|
||||
};
|
||||
|
||||
onBridgeSynchronousXHRSupported: (
|
||||
isSynchronousXHRSupported: boolean,
|
||||
) => void = isSynchronousXHRSupported => {
|
||||
this._isSynchronousXHRSupported = isSynchronousXHRSupported;
|
||||
onBackendReloadAndProfileSupported: (
|
||||
isReloadAndProfileSupported: boolean,
|
||||
) => void = isReloadAndProfileSupported => {
|
||||
this._isReloadAndProfileBackendSupported = isReloadAndProfileSupported;
|
||||
|
||||
this.emit('supportsReloadAndProfile');
|
||||
};
|
||||
|
||||
13
packages/react-devtools-shared/src/utils.js
vendored
13
packages/react-devtools-shared/src/utils.js
vendored
@@ -61,6 +61,7 @@ import type {
|
||||
LRUCache,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types';
|
||||
import {isSynchronousXHRSupported} from './backend/utils';
|
||||
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
@@ -965,3 +966,15 @@ export function backendToFrontendSerializedElementMapper(
|
||||
export function normalizeUrl(url: string): string {
|
||||
return url.replace('/./', '/');
|
||||
}
|
||||
|
||||
export function getIsReloadAndProfileSupported(): boolean {
|
||||
// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
|
||||
// If not, features like reload-and-profile will not work correctly and must be disabled.
|
||||
let isBackendStorageAPISupported = false;
|
||||
try {
|
||||
localStorage.getItem('test');
|
||||
isBackendStorageAPISupported = true;
|
||||
} catch (error) {}
|
||||
|
||||
return isBackendStorageAPISupported && isSynchronousXHRSupported();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user