@samitouri / QOS-React / commits / f8024b0686

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

Edmond Chui committed Sep 26, 2024 at 12:39 UTC f8024b0686c87634b233262e8a05e4a37a292e87
8 files changed +57 -60
packages/react-devtools-core/src/backend.js
+15 -3
@@ -13,7 +13,10 @@ import {installHook} from 'react-devtools-shared/src/hook';
13 import {initBackend} from 'react-devtools-shared/src/backend';
14 import {__DEBUG__} from 'react-devtools-shared/src/constants';
15 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';
20
21 import type {BackendBridge} from 'react-devtools-shared/src/bridge';
22 import type {
@@ -36,6 +39,7 @@ type ConnectOptions = {
39 isAppActive?: () => boolean,
40 websocket?: ?WebSocket,
41 onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
42 + isReloadAndProfileSupported?: boolean,
43 };
44
45 let savedComponentFilters: Array<ComponentFilter> =
@@ -77,6 +81,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
81 retryConnectionDelay = 2000,
82 isAppActive = () => true,
83 onSettingsUpdated,
84 + isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
85 } = options || {};
86
87 const protocol = useHttps ? 'wss' : 'ws';
@@ -184,7 +189,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
189 hook.emit('shutdown');
190 });
191
187 - initBackend(hook, agent, window);
192 + initBackend(hook, agent, window, isReloadAndProfileSupported);
193
194 // Setup React Native style editor if the environment supports it.
195 if (resolveRNStyle != null || hook.resolveRNStyle != null) {
@@ -309,6 +314,7 @@ type ConnectWithCustomMessagingOptions = {
314 nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
315 resolveRNStyle?: ResolveNativeStyle,
316 onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
317 + isReloadAndProfileSupported?: boolean,
318 };
319
320 export function connectWithCustomMessagingProtocol({
@@ -318,6 +324,7 @@ export function connectWithCustomMessagingProtocol({
324 nativeStyleEditorValidAttributes,
325 resolveRNStyle,
326 onSettingsUpdated,
327 + isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
328 }: ConnectWithCustomMessagingOptions): Function {
329 const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
330 if (hook == null) {
@@ -368,7 +375,12 @@ export function connectWithCustomMessagingProtocol({
375 hook.emit('shutdown');
376 });
377
371 - const unsubscribeBackend = initBackend(hook, agent, window);
378 + const unsubscribeBackend = initBackend(
379 + hook,
380 + agent,
381 + window,
382 + isReloadAndProfileSupported,
383 + );
384
385 const nativeStyleResolver: ResolveNativeStyle | void =
386 resolveRNStyle || hook.resolveRNStyle;
packages/react-devtools-extensions/src/contentScripts/backendManager.js
+2 -1
@@ -13,6 +13,7 @@ import type {
13 } from 'react-devtools-shared/src/backend/types';
14 import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils';
15 import {COMPACT_VERSION_NAME} from 'react-devtools-extensions/src/utils';
16 +import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
17
18 let welcomeHasInitialized = false;
19
@@ -140,7 +141,7 @@ function activateBackend(version: string, hook: DevToolsHook) {
141 hook.emit('shutdown');
142 });
143
143 - initBackend(hook, agent, window);
144 + initBackend(hook, agent, window, getIsReloadAndProfileSupported());
145
146 // Setup React Native style editor if a renderer like react-native-web has injected it.
147 if (typeof setupNativeStyleEditor === 'function' && hook.resolveRNStyle) {
packages/react-devtools-inline/src/backend.js
+2 -1
@@ -8,6 +8,7 @@ import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyl
8
9 import type {BackendBridge} from 'react-devtools-shared/src/bridge';
10 import type {Wall} from 'react-devtools-shared/src/frontend/types';
11 +import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
12
13 function startActivation(contentWindow: any, bridge: BackendBridge) {
14 const onSavedPreferences = (data: $FlowFixMe) => {
@@ -66,7 +67,7 @@ function finishActivation(contentWindow: any, bridge: BackendBridge) {
67
68 const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
69 if (hook) {
69 - initBackend(hook, agent, contentWindow);
70 + initBackend(hook, agent, contentWindow, getIsReloadAndProfileSupported());
71
72 // Setup React Native style editor if a renderer like react-native-web has injected it.
73 if (hook.resolveRNStyle) {
packages/react-devtools-shared/src/backend/agent.js
+5 -11
@@ -38,7 +38,7 @@ import type {
38 DevToolsHookSettings,
39 } from './types';
40 import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
41 -import {isSynchronousXHRSupported, isReactNativeEnvironment} from './utils';
41 +import {isReactNativeEnvironment} from './utils';
42
43 const debug = (methodName: string, ...args: Array<string>) => {
44 if (__DEBUG__) {
@@ -242,16 +242,6 @@ export default class Agent extends EventEmitter<{
242 if (this._isProfiling) {
243 bridge.send('profilingStatus', true);
244 }
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());
245 }
246
247 get rendererInterfaces(): {[key: RendererID]: RendererInterface, ...} {
@@ -675,6 +665,10 @@ export default class Agent extends EventEmitter<{
665 }
666 };
667
668 + onReloadAndProfileSupportedByHost: () => void = () => {
669 + this._bridge.send('isReloadAndProfileSupportedByBackend', true);
670 + };
671 +
672 reloadAndProfile: (recordChangeDescriptions: boolean) => void =
673 recordChangeDescriptions => {
674 sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
packages/react-devtools-shared/src/backend/index.js
+5
@@ -17,6 +17,7 @@ export function initBackend(
17 hook: DevToolsHook,
18 agent: Agent,
19 global: Object,
20 + isReloadAndProfileSupported: boolean,
21 ): () => void {
22 if (hook == null) {
23 // DevTools didn't get injected into this page (maybe b'c of the contentType).
@@ -94,6 +95,10 @@ export function initBackend(
95 }
96 });
97
98 + if (isReloadAndProfileSupported) {
99 + agent.onReloadAndProfileSupportedByHost();
100 + }
101 +
102 return () => {
103 subs.forEach(fn => fn());
104 };
packages/react-devtools-shared/src/bridge.js
+1 -2
@@ -181,8 +181,7 @@ export type BackendEvents = {
181 fastRefreshScheduled: [],
182 getSavedPreferences: [],
183 inspectedElement: [InspectedElementPayload],
184 - isBackendStorageAPISupported: [boolean],
185 - isSynchronousXHRSupported: [boolean],
184 + isReloadAndProfileSupportedByBackend: [boolean],
185 operations: [Array<number>],
186 ownersList: [OwnersList],
187 overrideComponentFilters: [Array<ComponentFilter>],
packages/react-devtools-shared/src/devtools/store.js
+14 -42
@@ -138,16 +138,6 @@ export default class Store extends EventEmitter<{
138 // Should the React Native style editor panel be shown?
139 _isNativeStyleEditorSupported: boolean = false;
140
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 -
141 _nativeStyleEditorValidAttributes: $ReadOnlyArray<string> | null = null;
142
143 // Older backends don't support an explicit bridge protocol,
@@ -178,10 +168,12 @@ export default class Store extends EventEmitter<{
168 // These options may be initially set by a configuration option when constructing the Store.
169 _supportsInspectMatchingDOMElement: boolean = false;
170 _supportsClickToInspect: boolean = false;
181 - _supportsReloadAndProfile: boolean = false;
171 _supportsTimeline: boolean = false;
172 _supportsTraceUpdates: boolean = false;
173
174 + _isReloadAndProfileFrontendSupported: boolean = false;
175 + _isReloadAndProfileBackendSupported: boolean = false;
176 +
177 // These options default to false but may be updated as roots are added and removed.
178 _rootSupportsBasicProfiling: boolean = false;
179 _rootSupportsTimelineProfiling: boolean = false;
@@ -234,7 +226,7 @@ export default class Store extends EventEmitter<{
226 this._supportsClickToInspect = true;
227 }
228 if (supportsReloadAndProfile) {
237 - this._supportsReloadAndProfile = true;
229 + this._isReloadAndProfileFrontendSupported = true;
230 }
231 if (supportsTimeline) {
232 this._supportsTimeline = true;
@@ -255,17 +247,13 @@ export default class Store extends EventEmitter<{
247 );
248 bridge.addListener('shutdown', this.onBridgeShutdown);
249 bridge.addListener(
258 - 'isBackendStorageAPISupported',
259 - this.onBackendStorageAPISupported,
250 + 'isReloadAndProfileSupportedByBackend',
251 + this.onBackendReloadAndProfileSupported,
252 );
253 bridge.addListener(
254 'isNativeStyleEditorSupported',
255 this.onBridgeNativeStyleEditorSupported,
256 );
265 - bridge.addListener(
266 - 'isSynchronousXHRSupported',
267 - this.onBridgeSynchronousXHRSupported,
268 - );
257 bridge.addListener(
258 'unsupportedRendererVersion',
259 this.onBridgeUnsupportedRendererVersion,
@@ -469,13 +457,9 @@ export default class Store extends EventEmitter<{
457 }
458
459 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.
460 return (
476 - this._supportsReloadAndProfile &&
477 - this._isBackendStorageAPISupported &&
478 - this._isSynchronousXHRSupported
461 + this._isReloadAndProfileFrontendSupported &&
462 + this._isReloadAndProfileBackendSupported
463 );
464 }
465
@@ -1433,17 +1417,13 @@ export default class Store extends EventEmitter<{
1417 );
1418 bridge.removeListener('shutdown', this.onBridgeShutdown);
1419 bridge.removeListener(
1436 - 'isBackendStorageAPISupported',
1437 - this.onBackendStorageAPISupported,
1420 + 'isReloadAndProfileSupportedByBackend',
1421 + this.onBackendReloadAndProfileSupported,
1422 );
1423 bridge.removeListener(
1424 'isNativeStyleEditorSupported',
1425 this.onBridgeNativeStyleEditorSupported,
1426 );
1443 - bridge.removeListener(
1444 - 'isSynchronousXHRSupported',
1445 - this.onBridgeSynchronousXHRSupported,
1446 - );
1427 bridge.removeListener(
1428 'unsupportedRendererVersion',
1429 this.onBridgeUnsupportedRendererVersion,
@@ -1458,18 +1438,10 @@ export default class Store extends EventEmitter<{
1438 }
1439 };
1440
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;
1445
1446 this.emit('supportsReloadAndProfile');
1447 };
packages/react-devtools-shared/src/utils.js
+13
@@ -61,6 +61,7 @@ import type {
61 LRUCache,
62 } from 'react-devtools-shared/src/frontend/types';
63 import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types';
64 +import {isSynchronousXHRSupported} from './backend/utils';
65
66 // $FlowFixMe[method-unbinding]
67 const hasOwnProperty = Object.prototype.hasOwnProperty;
@@ -965,3 +966,15 @@ export function backendToFrontendSerializedElementMapper(
966 export function normalizeUrl(url: string): string {
967 return url.replace('/./', '/');
968 }
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 +}