Skip to content

Commit f8024b0

Browse files
authored
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
1 parent d66fa02 commit f8024b0

File tree

8 files changed

+57
-60
lines changed

8 files changed

+57
-60
lines changed

packages/react-devtools-core/src/backend.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {installHook} from 'react-devtools-shared/src/hook';
1313
import {initBackend} from 'react-devtools-shared/src/backend';
1414
import {__DEBUG__} from 'react-devtools-shared/src/constants';
1515
import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
16-
import {getDefaultComponentFilters} from 'react-devtools-shared/src/utils';
16+
import {
17+
getDefaultComponentFilters,
18+
getIsReloadAndProfileSupported,
19+
} from 'react-devtools-shared/src/utils';
1720

1821
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
1922
import type {
@@ -36,6 +39,7 @@ type ConnectOptions = {
3639
isAppActive?: () => boolean,
3740
websocket?: ?WebSocket,
3841
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
42+
isReloadAndProfileSupported?: boolean,
3943
};
4044

4145
let savedComponentFilters: Array<ComponentFilter> =
@@ -77,6 +81,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
7781
retryConnectionDelay = 2000,
7882
isAppActive = () => true,
7983
onSettingsUpdated,
84+
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
8085
} = options || {};
8186

8287
const protocol = useHttps ? 'wss' : 'ws';
@@ -184,7 +189,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
184189
hook.emit('shutdown');
185190
});
186191

187-
initBackend(hook, agent, window);
192+
initBackend(hook, agent, window, isReloadAndProfileSupported);
188193

189194
// Setup React Native style editor if the environment supports it.
190195
if (resolveRNStyle != null || hook.resolveRNStyle != null) {
@@ -309,6 +314,7 @@ type ConnectWithCustomMessagingOptions = {
309314
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
310315
resolveRNStyle?: ResolveNativeStyle,
311316
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
317+
isReloadAndProfileSupported?: boolean,
312318
};
313319

314320
export function connectWithCustomMessagingProtocol({
@@ -318,6 +324,7 @@ export function connectWithCustomMessagingProtocol({
318324
nativeStyleEditorValidAttributes,
319325
resolveRNStyle,
320326
onSettingsUpdated,
327+
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
321328
}: ConnectWithCustomMessagingOptions): Function {
322329
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
323330
if (hook == null) {
@@ -368,7 +375,12 @@ export function connectWithCustomMessagingProtocol({
368375
hook.emit('shutdown');
369376
});
370377

371-
const unsubscribeBackend = initBackend(hook, agent, window);
378+
const unsubscribeBackend = initBackend(
379+
hook,
380+
agent,
381+
window,
382+
isReloadAndProfileSupported,
383+
);
372384

373385
const nativeStyleResolver: ResolveNativeStyle | void =
374386
resolveRNStyle || hook.resolveRNStyle;

packages/react-devtools-extensions/src/contentScripts/backendManager.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from 'react-devtools-shared/src/backend/types';
1414
import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils';
1515
import {COMPACT_VERSION_NAME} from 'react-devtools-extensions/src/utils';
16+
import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
1617

1718
let welcomeHasInitialized = false;
1819

@@ -140,7 +141,7 @@ function activateBackend(version: string, hook: DevToolsHook) {
140141
hook.emit('shutdown');
141142
});
142143

143-
initBackend(hook, agent, window);
144+
initBackend(hook, agent, window, getIsReloadAndProfileSupported());
144145

145146
// Setup React Native style editor if a renderer like react-native-web has injected it.
146147
if (typeof setupNativeStyleEditor === 'function' && hook.resolveRNStyle) {

packages/react-devtools-inline/src/backend.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyl
88

99
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
1010
import type {Wall} from 'react-devtools-shared/src/frontend/types';
11+
import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
1112

1213
function startActivation(contentWindow: any, bridge: BackendBridge) {
1314
const onSavedPreferences = (data: $FlowFixMe) => {
@@ -66,7 +67,7 @@ function finishActivation(contentWindow: any, bridge: BackendBridge) {
6667

6768
const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
6869
if (hook) {
69-
initBackend(hook, agent, contentWindow);
70+
initBackend(hook, agent, contentWindow, getIsReloadAndProfileSupported());
7071

7172
// Setup React Native style editor if a renderer like react-native-web has injected it.
7273
if (hook.resolveRNStyle) {

packages/react-devtools-shared/src/backend/agent.js

+5-11
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import type {
3838
DevToolsHookSettings,
3939
} from './types';
4040
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
41-
import {isSynchronousXHRSupported, isReactNativeEnvironment} from './utils';
41+
import {isReactNativeEnvironment} from './utils';
4242

4343
const debug = (methodName: string, ...args: Array<string>) => {
4444
if (__DEBUG__) {
@@ -242,16 +242,6 @@ export default class Agent extends EventEmitter<{
242242
if (this._isProfiling) {
243243
bridge.send('profilingStatus', true);
244244
}
245-
246-
// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
247-
// If not, features like reload-and-profile will not work correctly and must be disabled.
248-
let isBackendStorageAPISupported = false;
249-
try {
250-
localStorage.getItem('test');
251-
isBackendStorageAPISupported = true;
252-
} catch (error) {}
253-
bridge.send('isBackendStorageAPISupported', isBackendStorageAPISupported);
254-
bridge.send('isSynchronousXHRSupported', isSynchronousXHRSupported());
255245
}
256246

257247
get rendererInterfaces(): {[key: RendererID]: RendererInterface, ...} {
@@ -675,6 +665,10 @@ export default class Agent extends EventEmitter<{
675665
}
676666
};
677667

668+
onReloadAndProfileSupportedByHost: () => void = () => {
669+
this._bridge.send('isReloadAndProfileSupportedByBackend', true);
670+
};
671+
678672
reloadAndProfile: (recordChangeDescriptions: boolean) => void =
679673
recordChangeDescriptions => {
680674
sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');

packages/react-devtools-shared/src/backend/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function initBackend(
1717
hook: DevToolsHook,
1818
agent: Agent,
1919
global: Object,
20+
isReloadAndProfileSupported: boolean,
2021
): () => void {
2122
if (hook == null) {
2223
// DevTools didn't get injected into this page (maybe b'c of the contentType).
@@ -94,6 +95,10 @@ export function initBackend(
9495
}
9596
});
9697

98+
if (isReloadAndProfileSupported) {
99+
agent.onReloadAndProfileSupportedByHost();
100+
}
101+
97102
return () => {
98103
subs.forEach(fn => fn());
99104
};

packages/react-devtools-shared/src/bridge.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ export type BackendEvents = {
181181
fastRefreshScheduled: [],
182182
getSavedPreferences: [],
183183
inspectedElement: [InspectedElementPayload],
184-
isBackendStorageAPISupported: [boolean],
185-
isSynchronousXHRSupported: [boolean],
184+
isReloadAndProfileSupportedByBackend: [boolean],
186185
operations: [Array<number>],
187186
ownersList: [OwnersList],
188187
overrideComponentFilters: [Array<ComponentFilter>],

packages/react-devtools-shared/src/devtools/store.js

+14-42
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,6 @@ export default class Store extends EventEmitter<{
138138
// Should the React Native style editor panel be shown?
139139
_isNativeStyleEditorSupported: boolean = false;
140140

141-
// Can the backend use the Storage API (e.g. localStorage)?
142-
// If not, features like reload-and-profile will not work correctly and must be disabled.
143-
_isBackendStorageAPISupported: boolean = false;
144-
145-
// Can DevTools use sync XHR requests?
146-
// If not, features like reload-and-profile will not work correctly and must be disabled.
147-
// This current limitation applies only to web extension builds
148-
// and will need to be reconsidered in the future if we add support for reload to React Native.
149-
_isSynchronousXHRSupported: boolean = false;
150-
151141
_nativeStyleEditorValidAttributes: $ReadOnlyArray<string> | null = null;
152142

153143
// Older backends don't support an explicit bridge protocol,
@@ -178,10 +168,12 @@ export default class Store extends EventEmitter<{
178168
// These options may be initially set by a configuration option when constructing the Store.
179169
_supportsInspectMatchingDOMElement: boolean = false;
180170
_supportsClickToInspect: boolean = false;
181-
_supportsReloadAndProfile: boolean = false;
182171
_supportsTimeline: boolean = false;
183172
_supportsTraceUpdates: boolean = false;
184173

174+
_isReloadAndProfileFrontendSupported: boolean = false;
175+
_isReloadAndProfileBackendSupported: boolean = false;
176+
185177
// These options default to false but may be updated as roots are added and removed.
186178
_rootSupportsBasicProfiling: boolean = false;
187179
_rootSupportsTimelineProfiling: boolean = false;
@@ -234,7 +226,7 @@ export default class Store extends EventEmitter<{
234226
this._supportsClickToInspect = true;
235227
}
236228
if (supportsReloadAndProfile) {
237-
this._supportsReloadAndProfile = true;
229+
this._isReloadAndProfileFrontendSupported = true;
238230
}
239231
if (supportsTimeline) {
240232
this._supportsTimeline = true;
@@ -255,17 +247,13 @@ export default class Store extends EventEmitter<{
255247
);
256248
bridge.addListener('shutdown', this.onBridgeShutdown);
257249
bridge.addListener(
258-
'isBackendStorageAPISupported',
259-
this.onBackendStorageAPISupported,
250+
'isReloadAndProfileSupportedByBackend',
251+
this.onBackendReloadAndProfileSupported,
260252
);
261253
bridge.addListener(
262254
'isNativeStyleEditorSupported',
263255
this.onBridgeNativeStyleEditorSupported,
264256
);
265-
bridge.addListener(
266-
'isSynchronousXHRSupported',
267-
this.onBridgeSynchronousXHRSupported,
268-
);
269257
bridge.addListener(
270258
'unsupportedRendererVersion',
271259
this.onBridgeUnsupportedRendererVersion,
@@ -469,13 +457,9 @@ export default class Store extends EventEmitter<{
469457
}
470458

471459
get supportsReloadAndProfile(): boolean {
472-
// Does the DevTools shell support reloading and eagerly injecting the renderer interface?
473-
// And if so, can the backend use the localStorage API and sync XHR?
474-
// All of these are currently required for the reload-and-profile feature to work.
475460
return (
476-
this._supportsReloadAndProfile &&
477-
this._isBackendStorageAPISupported &&
478-
this._isSynchronousXHRSupported
461+
this._isReloadAndProfileFrontendSupported &&
462+
this._isReloadAndProfileBackendSupported
479463
);
480464
}
481465

@@ -1433,17 +1417,13 @@ export default class Store extends EventEmitter<{
14331417
);
14341418
bridge.removeListener('shutdown', this.onBridgeShutdown);
14351419
bridge.removeListener(
1436-
'isBackendStorageAPISupported',
1437-
this.onBackendStorageAPISupported,
1420+
'isReloadAndProfileSupportedByBackend',
1421+
this.onBackendReloadAndProfileSupported,
14381422
);
14391423
bridge.removeListener(
14401424
'isNativeStyleEditorSupported',
14411425
this.onBridgeNativeStyleEditorSupported,
14421426
);
1443-
bridge.removeListener(
1444-
'isSynchronousXHRSupported',
1445-
this.onBridgeSynchronousXHRSupported,
1446-
);
14471427
bridge.removeListener(
14481428
'unsupportedRendererVersion',
14491429
this.onBridgeUnsupportedRendererVersion,
@@ -1458,18 +1438,10 @@ export default class Store extends EventEmitter<{
14581438
}
14591439
};
14601440

1461-
onBackendStorageAPISupported: (
1462-
isBackendStorageAPISupported: boolean,
1463-
) => void = isBackendStorageAPISupported => {
1464-
this._isBackendStorageAPISupported = isBackendStorageAPISupported;
1465-
1466-
this.emit('supportsReloadAndProfile');
1467-
};
1468-
1469-
onBridgeSynchronousXHRSupported: (
1470-
isSynchronousXHRSupported: boolean,
1471-
) => void = isSynchronousXHRSupported => {
1472-
this._isSynchronousXHRSupported = isSynchronousXHRSupported;
1441+
onBackendReloadAndProfileSupported: (
1442+
isReloadAndProfileSupported: boolean,
1443+
) => void = isReloadAndProfileSupported => {
1444+
this._isReloadAndProfileBackendSupported = isReloadAndProfileSupported;
14731445

14741446
this.emit('supportsReloadAndProfile');
14751447
};

packages/react-devtools-shared/src/utils.js

+13
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import type {
6161
LRUCache,
6262
} from 'react-devtools-shared/src/frontend/types';
6363
import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types';
64+
import {isSynchronousXHRSupported} from './backend/utils';
6465

6566
// $FlowFixMe[method-unbinding]
6667
const hasOwnProperty = Object.prototype.hasOwnProperty;
@@ -965,3 +966,15 @@ export function backendToFrontendSerializedElementMapper(
965966
export function normalizeUrl(url: string): string {
966967
return url.replace('/./', '/');
967968
}
969+
970+
export function getIsReloadAndProfileSupported(): boolean {
971+
// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
972+
// If not, features like reload-and-profile will not work correctly and must be disabled.
973+
let isBackendStorageAPISupported = false;
974+
try {
975+
localStorage.getItem('test');
976+
isBackendStorageAPISupported = true;
977+
} catch (error) {}
978+
979+
return isBackendStorageAPISupported && isSynchronousXHRSupported();
980+
}

0 commit comments

Comments
 (0)