diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 11684dfa33ad2..24971303544ec 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -23,6 +23,8 @@ import type { import type { DevToolsHook, DevToolsHookSettings, + ReloadAndProfileConfig, + ReloadAndProfileConfigPersistence, } from 'react-devtools-shared/src/backend/types'; import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; @@ -37,6 +39,7 @@ type ConnectOptions = { websocket?: ?WebSocket, onSettingsUpdated?: (settings: $ReadOnly) => void, getIsReloadAndProfileSupported?: () => boolean, + reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence, }; let savedComponentFilters: Array = @@ -57,6 +60,7 @@ export function initialize( maybeSettingsOrSettingsPromise?: | DevToolsHookSettings | Promise, + reloadAndProfileConfig: ReloadAndProfileConfig, ) { installHook(window, maybeSettingsOrSettingsPromise); } @@ -79,6 +83,7 @@ export function connectToDevTools(options: ?ConnectOptions) { isAppActive = () => true, onSettingsUpdated, getIsReloadAndProfileSupported, + reloadAndProfileConfigPersistence, } = options || {}; const protocol = useHttps ? 'wss' : 'ws'; @@ -172,7 +177,7 @@ export function connectToDevTools(options: ?ConnectOptions) { // TODO (npm-packages) Warn if "isBackendStorageAPISupported" // $FlowFixMe[incompatible-call] found when upgrading Flow - const agent = new Agent(bridge); + const agent = new Agent(bridge, reloadAndProfileConfigPersistence); if (onSettingsUpdated != null) { agent.addListener('updateHookSettings', onSettingsUpdated); } @@ -312,6 +317,7 @@ type ConnectWithCustomMessagingOptions = { resolveRNStyle?: ResolveNativeStyle, onSettingsUpdated?: (settings: $ReadOnly) => void, getIsReloadAndProfileSupported?: () => boolean, + reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence, }; export function connectWithCustomMessagingProtocol({ @@ -322,6 +328,7 @@ export function connectWithCustomMessagingProtocol({ resolveRNStyle, onSettingsUpdated, getIsReloadAndProfileSupported, + reloadAndProfileConfigPersistence, }: ConnectWithCustomMessagingOptions): Function { const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (hook == null) { @@ -358,7 +365,7 @@ export function connectWithCustomMessagingProtocol({ bridge.send('overrideComponentFilters', savedComponentFilters); } - const agent = new Agent(bridge); + const agent = new Agent(bridge, reloadAndProfileConfigPersistence); if (onSettingsUpdated != null) { agent.addListener('updateHookSettings', onSettingsUpdated); } diff --git a/packages/react-devtools-fusebox/src/frontend.d.ts b/packages/react-devtools-fusebox/src/frontend.d.ts index 8a62ad54e504c..74a88c36a85f8 100644 --- a/packages/react-devtools-fusebox/src/frontend.d.ts +++ b/packages/react-devtools-fusebox/src/frontend.d.ts @@ -19,9 +19,12 @@ export type Bridge = { }; export type Store = Object; export type BrowserTheme = 'dark' | 'light'; +export type Config = { + supportsReloadAndProfile?: boolean, +}; export function createBridge(wall: Wall): Bridge; -export function createStore(bridge: Bridge): Store; +export function createStore(bridge: Bridge, config?: Config): Store; export type Source = { sourceURL: string, diff --git a/packages/react-devtools-shared/src/attachRenderer.js b/packages/react-devtools-shared/src/attachRenderer.js index 3138f00cad615..2273d752aa336 100644 --- a/packages/react-devtools-shared/src/attachRenderer.js +++ b/packages/react-devtools-shared/src/attachRenderer.js @@ -18,6 +18,7 @@ import {attach as attachFlight} from 'react-devtools-shared/src/backend/flight/r import {attach as attachFiber} from 'react-devtools-shared/src/backend/fiber/renderer'; import {attach as attachLegacy} from 'react-devtools-shared/src/backend/legacy/renderer'; import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils'; +import type {ReloadAndProfileConfig} from './backend/types'; // this is the backend that is compatible with all older React versions function isMatchingRender(version: string): boolean { @@ -29,6 +30,7 @@ export default function attachRenderer( id: RendererID, renderer: ReactRenderer, global: Object, + reloadAndProfileConfig?: ReloadAndProfileConfig, ): RendererInterface | void { // only attach if the renderer is compatible with the current version of the backend if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) { @@ -48,7 +50,13 @@ export default function attachRenderer( renderer.currentDispatcherRef != null ) { // react-reconciler v16+ - rendererInterface = attachFiber(hook, id, renderer, global); + rendererInterface = attachFiber( + hook, + id, + renderer, + global, + reloadAndProfileConfig, + ); } else if (renderer.ComponentTree) { // react-dom v15 rendererInterface = attachLegacy(hook, id, renderer, global); diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index a1e96bfcdeb39..d4c475c427935 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -36,6 +36,8 @@ import type { RendererID, RendererInterface, DevToolsHookSettings, + ReloadAndProfileConfigPersistence, + ReloadAndProfileConfig, } from './types'; import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types'; import {isReactNativeEnvironment} from './utils'; @@ -159,21 +161,27 @@ export default class Agent extends EventEmitter<{ _persistedSelection: PersistedSelection | null = null; _persistedSelectionMatch: PathMatch | null = null; _traceUpdatesEnabled: boolean = false; + _reloadAndProfileConfigPersistence: ReloadAndProfileConfigPersistence; - constructor(bridge: BackendBridge) { + constructor( + bridge: BackendBridge, + reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence = defaultReloadAndProfileConfigSetters, + ) { super(); - if ( - sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true' - ) { + this._reloadAndProfileConfigPersistence = reloadAndProfileConfigPersistence; + const {getReloadAndProfileConfig, setReloadAndProfileConfig} = + reloadAndProfileConfigPersistence; + const reloadAndProfileConfig = getReloadAndProfileConfig(); + if (reloadAndProfileConfig.shouldReloadAndProfile) { this._recordChangeDescriptions = - sessionStorageGetItem( - SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, - ) === 'true'; + reloadAndProfileConfig.recordChangeDescriptions; this._isProfiling = true; - sessionStorageRemoveItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY); - sessionStorageRemoveItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY); + setReloadAndProfileConfig({ + shouldReloadAndProfile: false, + recordChangeDescriptions: false, + }); } const persistedSelectionString = sessionStorageGetItem( @@ -671,11 +679,10 @@ export default class Agent extends EventEmitter<{ reloadAndProfile: (recordChangeDescriptions: boolean) => void = recordChangeDescriptions => { - sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true'); - sessionStorageSetItem( - SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, - recordChangeDescriptions ? 'true' : 'false', - ); + this._reloadAndProfileConfigPersistence.setReloadAndProfileConfig({ + shouldReloadAndProfile: true, + recordChangeDescriptions, + }); // This code path should only be hit if the shell has explicitly told the Store that it supports profiling. // In that case, the shell must also listen for this specific message to know when it needs to reload the app. @@ -956,3 +963,35 @@ export default class Agent extends EventEmitter<{ } }; } + +const defaultReloadAndProfileConfigSetters: ReloadAndProfileConfigPersistence = + { + setReloadAndProfileConfig({ + shouldReloadAndProfile, + recordChangeDescriptions, + }): void { + if (shouldReloadAndProfile != null) { + sessionStorageSetItem( + SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, + shouldReloadAndProfile ? 'true' : 'false', + ); + } + if (recordChangeDescriptions != null) { + sessionStorageSetItem( + SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, + recordChangeDescriptions ? 'true' : 'false', + ); + } + }, + getReloadAndProfileConfig(): ReloadAndProfileConfig { + return { + shouldReloadAndProfile: + sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === + 'true', + recordChangeDescriptions: + sessionStorageGetItem( + SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, + ) === 'true', + }; + }, + }; diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index d93e713911561..f342b68078fe0 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -42,7 +42,6 @@ import { utfEncodeString, filterOutLocationComponentFilters, } from 'react-devtools-shared/src/utils'; -import {sessionStorageGetItem} from 'react-devtools-shared/src/storage'; import { formatConsoleArgumentsToSingleString, gt, @@ -61,8 +60,6 @@ import { __DEBUG__, PROFILING_FLAG_BASIC_SUPPORT, PROFILING_FLAG_TIMELINE_SUPPORT, - SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, - SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, TREE_OPERATION_ADD, TREE_OPERATION_REMOVE, TREE_OPERATION_REORDER_CHILDREN, @@ -106,6 +103,7 @@ import { supportsOwnerStacks, supportsConsoleTasks, } from './DevToolsFiberComponentStack'; +import type {ReloadAndProfileConfig} from '../types'; // $FlowFixMe[method-unbinding] const toString = Object.prototype.toString; @@ -851,6 +849,7 @@ export function attach( rendererID: number, renderer: ReactRenderer, global: Object, + reloadAndProfileConfig?: ReloadAndProfileConfig, ): RendererInterface { // Newer versions of the reconciler package also specific reconciler version. // If that version number is present, use it. @@ -5199,13 +5198,10 @@ export function attach( } // Automatically start profiling so that we don't miss timing info from initial "mount". - if ( - sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true' - ) { - startProfiling( - sessionStorageGetItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY) === - 'true', - ); + if (reloadAndProfileConfig?.shouldReloadAndProfile) { + const shouldRecordChangeDescriptions = + reloadAndProfileConfig.recordChangeDescriptions; + startProfiling(shouldRecordChangeDescriptions); } function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index c3110dc517fdc..c6f743546e49e 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -485,6 +485,20 @@ export type DevToolsBackend = { setupNativeStyleEditor?: SetupNativeStyleEditor, }; +export type ReloadAndProfileConfig = { + shouldReloadAndProfile: boolean, + recordChangeDescriptions: boolean, +}; + +// Linter doesn't speak Flow's `Partial` type +// eslint-disable-next-line no-undef +type PartialReloadAndProfileConfig = Partial; + +export type ReloadAndProfileConfigPersistence = { + setReloadAndProfileConfig: (config: PartialReloadAndProfileConfig) => void, + getReloadAndProfileConfig: () => ReloadAndProfileConfig, +}; + export type DevToolsHook = { listeners: {[key: string]: Array, ...}, rendererInterfaces: Map, diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 1916a8c93822c..2981f1a6fcc10 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -16,6 +16,7 @@ import type { RendererInterface, DevToolsBackend, DevToolsHookSettings, + ReloadAndProfileConfig, } from './backend/types'; import { @@ -54,6 +55,7 @@ export function installHook( maybeSettingsOrSettingsPromise?: | DevToolsHookSettings | Promise, + reloadAndProfileConfig?: ReloadAndProfileConfig, ): DevToolsHook | null { if (target.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) { return null; @@ -207,7 +209,13 @@ export function installHook( reactBuildType, }); - const rendererInterface = attachRenderer(hook, id, renderer, target); + const rendererInterface = attachRenderer( + hook, + id, + renderer, + target, + reloadAndProfileConfig, + ); if (rendererInterface != null) { hook.rendererInterfaces.set(id, rendererInterface); hook.emit('renderer-attached', {id, rendererInterface});