Skip to content

Commit

Permalink
refactor: allow custom impl of backend realod-to-profile support check (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
EdmondChuiHW authored Sep 26, 2024
1 parent d66fa02 commit f8024b0
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 60 deletions.
18 changes: 15 additions & 3 deletions packages/react-devtools-core/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,6 +39,7 @@ type ConnectOptions = {
isAppActive?: () => boolean,
websocket?: ?WebSocket,
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
isReloadAndProfileSupported?: boolean,
};

let savedComponentFilters: Array<ComponentFilter> =
Expand Down Expand Up @@ -77,6 +81,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
retryConnectionDelay = 2000,
isAppActive = () => true,
onSettingsUpdated,
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
} = options || {};

const protocol = useHttps ? 'wss' : 'ws';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -309,6 +314,7 @@ type ConnectWithCustomMessagingOptions = {
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
resolveRNStyle?: ResolveNativeStyle,
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
isReloadAndProfileSupported?: boolean,
};

export function connectWithCustomMessagingProtocol({
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion packages/react-devtools-inline/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) {
Expand Down
16 changes: 5 additions & 11 deletions packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__) {
Expand Down Expand Up @@ -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, ...} {
Expand Down Expand Up @@ -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');
Expand Down
5 changes: 5 additions & 0 deletions packages/react-devtools-shared/src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -94,6 +95,10 @@ export function initBackend(
}
});

if (isReloadAndProfileSupported) {
agent.onReloadAndProfileSupportedByHost();
}

return () => {
subs.forEach(fn => fn());
};
Expand Down
3 changes: 1 addition & 2 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>],
Expand Down
56 changes: 14 additions & 42 deletions packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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
);
}

Expand Down Expand Up @@ -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,
Expand All @@ -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');
};
Expand Down
13 changes: 13 additions & 0 deletions packages/react-devtools-shared/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

0 comments on commit f8024b0

Please sign in to comment.