Add: reload to profile for Fusebox (#31021)
## Summary Add reload to profile for Fusebox Stacked on #31048. See https://github.com/facebook/react/pull/31021/commits/6be1977112596581f7ce4cfade572f43320ab06f ## How did you test this change? Test E2E in [D63233256](https://www.internalfb.com/diff/D63233256)
Edmond Chui committed
Sep 26, 2024 at 16:39 UTC
204a551eae466ab74ba23870f61dc2b5c71d5ab2
8 files changed
+124
-42
packages/react-devtools-core/src/backend.js
+10
-3
@@ -26,6 +26,8 @@ import type {
26
import type {
27
DevToolsHook,
28
DevToolsHookSettings,
29
+ ReloadAndProfileConfig,
30
+ ReloadAndProfileConfigPersistence,
31
} from 'react-devtools-shared/src/backend/types';
32
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
33
@@ -40,6 +42,7 @@ type ConnectOptions = {
42
websocket?: ?WebSocket,
43
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
44
isReloadAndProfileSupported?: boolean,
45
+ reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence,
46
};
47
48
let savedComponentFilters: Array<ComponentFilter> =
@@ -60,8 +63,9 @@ export function initialize(
63
maybeSettingsOrSettingsPromise?:
64
| DevToolsHookSettings
65
| Promise<DevToolsHookSettings>,
66
+ reloadAndProfileConfig?: ReloadAndProfileConfig,
67
) {
64
- installHook(window, maybeSettingsOrSettingsPromise);
68
+ installHook(window, maybeSettingsOrSettingsPromise, reloadAndProfileConfig);
69
}
70
71
export function connectToDevTools(options: ?ConnectOptions) {
@@ -82,6 +86,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
86
isAppActive = () => true,
87
onSettingsUpdated,
88
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
89
+ reloadAndProfileConfigPersistence,
90
} = options || {};
91
92
const protocol = useHttps ? 'wss' : 'ws';
@@ -175,7 +180,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
180
181
// TODO (npm-packages) Warn if "isBackendStorageAPISupported"
182
// $FlowFixMe[incompatible-call] found when upgrading Flow
178
- const agent = new Agent(bridge);
183
+ const agent = new Agent(bridge, reloadAndProfileConfigPersistence);
184
if (onSettingsUpdated != null) {
185
agent.addListener('updateHookSettings', onSettingsUpdated);
186
}
@@ -315,6 +320,7 @@ type ConnectWithCustomMessagingOptions = {
320
resolveRNStyle?: ResolveNativeStyle,
321
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
322
isReloadAndProfileSupported?: boolean,
323
+ reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence,
324
};
325
326
export function connectWithCustomMessagingProtocol({
@@ -325,6 +331,7 @@ export function connectWithCustomMessagingProtocol({
331
resolveRNStyle,
332
onSettingsUpdated,
333
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
334
+ reloadAndProfileConfigPersistence,
335
}: ConnectWithCustomMessagingOptions): Function {
336
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
337
if (hook == null) {
@@ -361,7 +368,7 @@ export function connectWithCustomMessagingProtocol({
368
bridge.send('overrideComponentFilters', savedComponentFilters);
369
}
370
364
- const agent = new Agent(bridge);
371
+ const agent = new Agent(bridge, reloadAndProfileConfigPersistence);
372
if (onSettingsUpdated != null) {
373
agent.addListener('updateHookSettings', onSettingsUpdated);
374
}
packages/react-devtools-fusebox/src/frontend.d.ts
+4
-1
@@ -19,9 +19,12 @@ export type Bridge = {
19
};
20
export type Store = Object;
21
export type BrowserTheme = 'dark' | 'light';
22
+export type Config = {
23
+ supportsReloadAndProfile?: boolean,
24
+};
25
26
export function createBridge(wall: Wall): Bridge;
24
-export function createStore(bridge: Bridge): Store;
27
+export function createStore(bridge: Bridge, config?: Config): Store;
28
29
export type Source = {
30
sourceURL: string,
packages/react-devtools-shared/src/attachRenderer.js
+9
-1
@@ -13,6 +13,7 @@ import type {
13
DevToolsHook,
14
RendererID,
15
} from 'react-devtools-shared/src/backend/types';
16
+import type {ReloadAndProfileConfig} from './backend/types';
17
18
import {attach as attachFlight} from 'react-devtools-shared/src/backend/flight/renderer';
19
import {attach as attachFiber} from 'react-devtools-shared/src/backend/fiber/renderer';
@@ -29,6 +30,7 @@ export default function attachRenderer(
30
id: RendererID,
31
renderer: ReactRenderer,
32
global: Object,
33
+ reloadAndProfileConfig: ReloadAndProfileConfig,
34
): RendererInterface | void {
35
// only attach if the renderer is compatible with the current version of the backend
36
if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) {
@@ -48,7 +50,13 @@ export default function attachRenderer(
50
renderer.currentDispatcherRef != null
51
) {
52
// react-reconciler v16+
51
- rendererInterface = attachFiber(hook, id, renderer, global);
53
+ rendererInterface = attachFiber(
54
+ hook,
55
+ id,
56
+ renderer,
57
+ global,
58
+ reloadAndProfileConfig,
59
+ );
60
} else if (renderer.ComponentTree) {
61
// react-dom v15
62
rendererInterface = attachLegacy(hook, id, renderer, global);
packages/react-devtools-shared/src/backend/agent.js
+27
-25
@@ -8,17 +8,7 @@
8
*/
9
10
import EventEmitter from '../events';
11
-import {
12
- SESSION_STORAGE_LAST_SELECTION_KEY,
13
- SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
14
- SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
15
- __DEBUG__,
16
-} from '../constants';
17
-import {
18
- sessionStorageGetItem,
19
- sessionStorageRemoveItem,
20
- sessionStorageSetItem,
21
-} from 'react-devtools-shared/src/storage';
11
+import {SESSION_STORAGE_LAST_SELECTION_KEY, __DEBUG__} from '../constants';
12
import setupHighlighter from './views/Highlighter';
13
import {
14
initialize as setupTraceUpdates,
@@ -36,9 +26,16 @@ import type {
26
RendererID,
27
RendererInterface,
28
DevToolsHookSettings,
29
+ ReloadAndProfileConfigPersistence,
30
} from './types';
31
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
32
import {isReactNativeEnvironment} from './utils';
33
+import {defaultReloadAndProfileConfigPersistence} from '../utils';
34
+import {
35
+ sessionStorageGetItem,
36
+ sessionStorageRemoveItem,
37
+ sessionStorageSetItem,
38
+} from '../storage';
39
40
const debug = (methodName: string, ...args: Array<string>) => {
41
if (__DEBUG__) {
@@ -159,21 +156,27 @@ export default class Agent extends EventEmitter<{
156
_persistedSelection: PersistedSelection | null = null;
157
_persistedSelectionMatch: PathMatch | null = null;
158
_traceUpdatesEnabled: boolean = false;
159
+ _reloadAndProfileConfigPersistence: ReloadAndProfileConfigPersistence;
160
163
- constructor(bridge: BackendBridge) {
161
+ constructor(
162
+ bridge: BackendBridge,
163
+ reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence = defaultReloadAndProfileConfigPersistence,
164
+ ) {
165
super();
166
166
- if (
167
- sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true'
168
- ) {
167
+ this._reloadAndProfileConfigPersistence = reloadAndProfileConfigPersistence;
168
+ const {getReloadAndProfileConfig, setReloadAndProfileConfig} =
169
+ reloadAndProfileConfigPersistence;
170
+ const reloadAndProfileConfig = getReloadAndProfileConfig();
171
+ if (reloadAndProfileConfig.shouldReloadAndProfile) {
172
this._recordChangeDescriptions =
170
- sessionStorageGetItem(
171
- SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
172
- ) === 'true';
173
+ reloadAndProfileConfig.recordChangeDescriptions;
174
this._isProfiling = true;
175
175
- sessionStorageRemoveItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY);
176
- sessionStorageRemoveItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY);
176
+ setReloadAndProfileConfig({
177
+ shouldReloadAndProfile: false,
178
+ recordChangeDescriptions: false,
179
+ });
180
}
181
182
const persistedSelectionString = sessionStorageGetItem(
@@ -671,11 +674,10 @@ export default class Agent extends EventEmitter<{
674
675
reloadAndProfile: (recordChangeDescriptions: boolean) => void =
676
recordChangeDescriptions => {
674
- sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
675
- sessionStorageSetItem(
676
- SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
677
- recordChangeDescriptions ? 'true' : 'false',
678
- );
677
+ this._reloadAndProfileConfigPersistence.setReloadAndProfileConfig({
678
+ shouldReloadAndProfile: true,
679
+ recordChangeDescriptions,
680
+ });
681
682
// This code path should only be hit if the shell has explicitly told the Store that it supports profiling.
683
// In that case, the shell must also listen for this specific message to know when it needs to reload the app.
packages/react-devtools-shared/src/backend/fiber/renderer.js
+6
-10
@@ -42,7 +42,6 @@ import {
42
utfEncodeString,
43
filterOutLocationComponentFilters,
44
} from 'react-devtools-shared/src/utils';
45
-import {sessionStorageGetItem} from 'react-devtools-shared/src/storage';
45
import {
46
formatConsoleArgumentsToSingleString,
47
gt,
@@ -61,8 +60,6 @@ import {
60
__DEBUG__,
61
PROFILING_FLAG_BASIC_SUPPORT,
62
PROFILING_FLAG_TIMELINE_SUPPORT,
64
- SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
65
- SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
63
TREE_OPERATION_ADD,
64
TREE_OPERATION_REMOVE,
65
TREE_OPERATION_REORDER_CHILDREN,
@@ -106,6 +103,7 @@ import {
103
supportsOwnerStacks,
104
supportsConsoleTasks,
105
} from './DevToolsFiberComponentStack';
106
+import type {ReloadAndProfileConfig} from '../types';
107
108
// $FlowFixMe[method-unbinding]
109
const toString = Object.prototype.toString;
@@ -865,6 +863,7 @@ export function attach(
863
rendererID: number,
864
renderer: ReactRenderer,
865
global: Object,
866
+ reloadAndProfileConfig: ReloadAndProfileConfig,
867
): RendererInterface {
868
// Newer versions of the reconciler package also specific reconciler version.
869
// If that version number is present, use it.
@@ -5213,13 +5212,10 @@ export function attach(
5212
}
5213
5214
// Automatically start profiling so that we don't miss timing info from initial "mount".
5216
- if (
5217
- sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true'
5218
- ) {
5219
- startProfiling(
5220
- sessionStorageGetItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY) ===
5221
- 'true',
5222
- );
5215
+ if (reloadAndProfileConfig.shouldReloadAndProfile) {
5216
+ const shouldRecordChangeDescriptions =
5217
+ reloadAndProfileConfig.recordChangeDescriptions;
5218
+ startProfiling(shouldRecordChangeDescriptions);
5219
}
5220
5221
function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber {
packages/react-devtools-shared/src/backend/types.js
+14
@@ -485,6 +485,20 @@ export type DevToolsBackend = {
485
setupNativeStyleEditor?: SetupNativeStyleEditor,
486
};
487
488
+export type ReloadAndProfileConfig = {
489
+ shouldReloadAndProfile: boolean,
490
+ recordChangeDescriptions: boolean,
491
+};
492
+
493
+// Linter doesn't speak Flow's `Partial` type
494
+// eslint-disable-next-line no-undef
495
+type PartialReloadAndProfileConfig = Partial<ReloadAndProfileConfig>;
496
+
497
+export type ReloadAndProfileConfigPersistence = {
498
+ setReloadAndProfileConfig: (config: PartialReloadAndProfileConfig) => void,
499
+ getReloadAndProfileConfig: () => ReloadAndProfileConfig,
500
+};
501
+
502
export type DevToolsHook = {
503
listeners: {[key: string]: Array<Handler>, ...},
504
rendererInterfaces: Map<RendererID, RendererInterface>,
packages/react-devtools-shared/src/hook.js
+10
-1
@@ -16,6 +16,7 @@ import type {
16
RendererInterface,
17
DevToolsBackend,
18
DevToolsHookSettings,
19
+ ReloadAndProfileConfig,
20
} from './backend/types';
21
22
import {
@@ -26,6 +27,7 @@ import {
27
import attachRenderer from './attachRenderer';
28
import formatConsoleArguments from 'react-devtools-shared/src/backend/utils/formatConsoleArguments';
29
import formatWithStyles from 'react-devtools-shared/src/backend/utils/formatWithStyles';
30
+import {defaultReloadAndProfileConfigPersistence} from './utils';
31
32
// React's custom built component stack strings match "\s{4}in"
33
// Chrome's prefix matches "\s{4}at"
@@ -54,6 +56,7 @@ export function installHook(
56
maybeSettingsOrSettingsPromise?:
57
| DevToolsHookSettings
58
| Promise<DevToolsHookSettings>,
59
+ reloadAndProfileConfig?: ReloadAndProfileConfig = defaultReloadAndProfileConfigPersistence.getReloadAndProfileConfig(),
60
): DevToolsHook | null {
61
if (target.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
62
return null;
@@ -207,7 +210,13 @@ export function installHook(
210
reactBuildType,
211
});
212
210
- const rendererInterface = attachRenderer(hook, id, renderer, target);
213
+ const rendererInterface = attachRenderer(
214
+ hook,
215
+ id,
216
+ renderer,
217
+ target,
218
+ reloadAndProfileConfig,
219
+ );
220
if (rendererInterface != null) {
221
hook.rendererInterfaces.set(id, rendererInterface);
222
hook.emit('renderer-attached', {id, rendererInterface});
packages/react-devtools-shared/src/utils.js
+44
-1
@@ -36,6 +36,8 @@ import {
36
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
37
LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY,
38
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
39
+ SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
40
+ SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
41
} from './constants';
42
import {
43
ComponentFilterElementType,
@@ -50,7 +52,12 @@ import {
52
ElementTypeMemo,
53
ElementTypeVirtual,
54
} from 'react-devtools-shared/src/frontend/types';
53
-import {localStorageGetItem, localStorageSetItem} from './storage';
55
+import {
56
+ localStorageGetItem,
57
+ localStorageSetItem,
58
+ sessionStorageGetItem,
59
+ sessionStorageSetItem,
60
+} from './storage';
61
import {meta} from './hydration';
62
import isArray from './isArray';
63
@@ -62,6 +69,10 @@ import type {
69
} from 'react-devtools-shared/src/frontend/types';
70
import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types';
71
import {isSynchronousXHRSupported} from './backend/utils';
72
+import type {
73
+ ReloadAndProfileConfig,
74
+ ReloadAndProfileConfigPersistence,
75
+} from './backend/types';
76
77
// $FlowFixMe[method-unbinding]
78
const hasOwnProperty = Object.prototype.hasOwnProperty;
@@ -978,3 +989,35 @@ export function getIsReloadAndProfileSupported(): boolean {
989
990
return isBackendStorageAPISupported && isSynchronousXHRSupported();
991
}
992
+
993
+export const defaultReloadAndProfileConfigPersistence: ReloadAndProfileConfigPersistence =
994
+ {
995
+ setReloadAndProfileConfig({
996
+ shouldReloadAndProfile,
997
+ recordChangeDescriptions,
998
+ }): void {
999
+ if (shouldReloadAndProfile != null) {
1000
+ sessionStorageSetItem(
1001
+ SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
1002
+ shouldReloadAndProfile ? 'true' : 'false',
1003
+ );
1004
+ }
1005
+ if (recordChangeDescriptions != null) {
1006
+ sessionStorageSetItem(
1007
+ SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
1008
+ recordChangeDescriptions ? 'true' : 'false',
1009
+ );
1010
+ }
1011
+ },
1012
+ getReloadAndProfileConfig(): ReloadAndProfileConfig {
1013
+ return {
1014
+ shouldReloadAndProfile:
1015
+ sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) ===
1016
+ 'true',
1017
+ recordChangeDescriptions:
1018
+ sessionStorageGetItem(
1019
+ SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
1020
+ ) === 'true',
1021
+ };
1022
+ },
1023
+ };