diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 54a6b9b48a99d..217fcf8805fd2 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -26,8 +26,7 @@ import type { import type { DevToolsHook, DevToolsHookSettings, - ReloadAndProfileConfig, - ReloadAndProfileConfigPersistence, + ProfilingSettings, } from 'react-devtools-shared/src/backend/types'; import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; @@ -42,7 +41,9 @@ type ConnectOptions = { websocket?: ?WebSocket, onSettingsUpdated?: (settings: $ReadOnly) => void, isReloadAndProfileSupported?: boolean, - reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence, + isProfiling?: boolean, + onReloadAndProfile?: (recordChangeDescriptions: boolean) => void, + onReloadAndProfileFlagsReset?: () => void, }; let savedComponentFilters: Array = @@ -63,9 +64,15 @@ export function initialize( maybeSettingsOrSettingsPromise?: | DevToolsHookSettings | Promise, - reloadAndProfileConfig?: ReloadAndProfileConfig, + shouldStartProfilingRightNow: boolean = false, + profilingSettings?: ProfilingSettings, ) { - installHook(window, maybeSettingsOrSettingsPromise, reloadAndProfileConfig); + installHook( + window, + maybeSettingsOrSettingsPromise, + shouldStartProfilingRightNow, + profilingSettings, + ); } export function connectToDevTools(options: ?ConnectOptions) { @@ -86,7 +93,9 @@ export function connectToDevTools(options: ?ConnectOptions) { isAppActive = () => true, onSettingsUpdated, isReloadAndProfileSupported = getIsReloadAndProfileSupported(), - reloadAndProfileConfigPersistence, + isProfiling, + onReloadAndProfile, + onReloadAndProfileFlagsReset, } = options || {}; const protocol = useHttps ? 'wss' : 'ws'; @@ -180,7 +189,11 @@ export function connectToDevTools(options: ?ConnectOptions) { // TODO (npm-packages) Warn if "isBackendStorageAPISupported" // $FlowFixMe[incompatible-call] found when upgrading Flow - const agent = new Agent(bridge, reloadAndProfileConfigPersistence); + const agent = new Agent(bridge, isProfiling, onReloadAndProfile); + if (typeof onReloadAndProfileFlagsReset === 'function') { + onReloadAndProfileFlagsReset(); + } + if (onSettingsUpdated != null) { agent.addListener('updateHookSettings', onSettingsUpdated); } @@ -320,7 +333,9 @@ type ConnectWithCustomMessagingOptions = { resolveRNStyle?: ResolveNativeStyle, onSettingsUpdated?: (settings: $ReadOnly) => void, isReloadAndProfileSupported?: boolean, - reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence, + isProfiling?: boolean, + onReloadAndProfile?: (recordChangeDescriptions: boolean) => void, + onReloadAndProfileFlagsReset?: () => void, }; export function connectWithCustomMessagingProtocol({ @@ -331,7 +346,9 @@ export function connectWithCustomMessagingProtocol({ resolveRNStyle, onSettingsUpdated, isReloadAndProfileSupported = getIsReloadAndProfileSupported(), - reloadAndProfileConfigPersistence, + isProfiling, + onReloadAndProfile, + onReloadAndProfileFlagsReset, }: ConnectWithCustomMessagingOptions): Function { const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (hook == null) { @@ -368,7 +385,11 @@ export function connectWithCustomMessagingProtocol({ bridge.send('overrideComponentFilters', savedComponentFilters); } - const agent = new Agent(bridge, reloadAndProfileConfigPersistence); + const agent = new Agent(bridge, isProfiling, onReloadAndProfile); + if (typeof onReloadAndProfileFlagsReset === 'function') { + onReloadAndProfileFlagsReset(); + } + if (onSettingsUpdated != null) { agent.addListener('updateHookSettings', onSettingsUpdated); } diff --git a/packages/react-devtools-extensions/src/contentScripts/backendManager.js b/packages/react-devtools-extensions/src/contentScripts/backendManager.js index 9d3ec414330ed..32e2ef711ed4a 100644 --- a/packages/react-devtools-extensions/src/contentScripts/backendManager.js +++ b/packages/react-devtools-extensions/src/contentScripts/backendManager.js @@ -14,6 +14,11 @@ import type { 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'; +import { + getIfReloadedAndProfiling, + onReloadAndProfile, + onReloadAndProfileFlagsReset, +} from 'react-devtools-extensions/src/utils'; let welcomeHasInitialized = false; @@ -134,7 +139,15 @@ function activateBackend(version: string, hook: DevToolsHook) { }, }); - const agent = new Agent(bridge); + const agent = new Agent( + bridge, + getIfReloadedAndProfiling(), + onReloadAndProfile, + ); + // Agent read flags successfully, we can count it as successful launch + // Clean up flags, so that next reload won't start profiling + onReloadAndProfileFlagsReset(); + agent.addListener('shutdown', () => { // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down, // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here. diff --git a/packages/react-devtools-extensions/src/contentScripts/installHook.js b/packages/react-devtools-extensions/src/contentScripts/installHook.js index b7b96ed24714b..18ff7b14b4f06 100644 --- a/packages/react-devtools-extensions/src/contentScripts/installHook.js +++ b/packages/react-devtools-extensions/src/contentScripts/installHook.js @@ -1,4 +1,8 @@ import {installHook} from 'react-devtools-shared/src/hook'; +import { + getIfReloadedAndProfiling, + getProfilingSettings, +} from 'react-devtools-extensions/src/utils'; let resolveHookSettingsInjection; @@ -34,8 +38,15 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) { payload: {handshake: true}, }); + const shouldStartProfiling = getIfReloadedAndProfiling(); + const profilingSettings = getProfilingSettings(); // Can't delay hook installation, inject settings lazily - installHook(window, hookSettingsPromise); + installHook( + window, + hookSettingsPromise, + shouldStartProfiling, + profilingSettings, + ); // Detect React window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on( diff --git a/packages/react-devtools-extensions/src/utils.js b/packages/react-devtools-extensions/src/utils.js index 3219b58ebc9c1..7815f82c649d6 100644 --- a/packages/react-devtools-extensions/src/utils.js +++ b/packages/react-devtools-extensions/src/utils.js @@ -1,6 +1,16 @@ /* global chrome */ import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools'; +import { + sessionStorageGetItem, + sessionStorageRemoveItem, + sessionStorageSetItem, +} from 'react-devtools-shared/src/storage'; +import { + SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, + SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, +} from 'react-devtools-shared/src/constants'; +import type {ProfilingSettings} from 'react-devtools-shared/src/backend/types'; export function getBrowserTheme(): BrowserTheme { if (__IS_CHROME__) { @@ -23,3 +33,31 @@ export function getBrowserTheme(): BrowserTheme { export const COMPACT_VERSION_NAME = 'compact'; export const EXTENSION_CONTAINED_VERSIONS = [COMPACT_VERSION_NAME]; + +// Expected to be used only by browser extension +export function getIfReloadedAndProfiling(): boolean { + return ( + sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true' + ); +} + +export function getProfilingSettings(): ProfilingSettings { + return { + recordChangeDescriptions: + sessionStorageGetItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY) === + 'true', + }; +} + +export function onReloadAndProfile(recordChangeDescriptions: boolean): void { + sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true'); + sessionStorageSetItem( + SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, + recordChangeDescriptions ? 'true' : 'false', + ); +} + +export function onReloadAndProfileFlagsReset(): void { + sessionStorageRemoveItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY); + sessionStorageRemoveItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY); +} diff --git a/packages/react-devtools-inline/src/backend.js b/packages/react-devtools-inline/src/backend.js index 41af7809be0b1..c098c6ab2aa49 100644 --- a/packages/react-devtools-inline/src/backend.js +++ b/packages/react-devtools-inline/src/backend.js @@ -63,6 +63,7 @@ function startActivation(contentWindow: any, bridge: BackendBridge) { } function finishActivation(contentWindow: any, bridge: BackendBridge) { + // We don't pass `isProfiling` and `onReloadAndProfile`, because it is not supported by react-devtools-inline Frontend const agent = new Agent(bridge); const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/packages/react-devtools-inline/src/frontend.js b/packages/react-devtools-inline/src/frontend.js index f96d29ac50745..f95cd4f0890e5 100644 --- a/packages/react-devtools-inline/src/frontend.js +++ b/packages/react-devtools-inline/src/frontend.js @@ -16,7 +16,6 @@ export function createStore(bridge: FrontendBridge, config?: Config): Store { return new Store(bridge, { checkBridgeProtocolCompatibility: true, supportsTraceUpdates: true, - supportsTimeline: true, ...config, }); } diff --git a/packages/react-devtools-shared/src/attachRenderer.js b/packages/react-devtools-shared/src/attachRenderer.js index cd7a348b65d71..3bc6304eae4ae 100644 --- a/packages/react-devtools-shared/src/attachRenderer.js +++ b/packages/react-devtools-shared/src/attachRenderer.js @@ -12,8 +12,8 @@ import type { RendererInterface, DevToolsHook, RendererID, + ProfilingSettings, } from 'react-devtools-shared/src/backend/types'; -import type {ReloadAndProfileConfig} from './backend/types'; import {attach as attachFlight} from 'react-devtools-shared/src/backend/flight/renderer'; import {attach as attachFiber} from 'react-devtools-shared/src/backend/fiber/renderer'; @@ -30,7 +30,8 @@ export default function attachRenderer( id: RendererID, renderer: ReactRenderer, global: Object, - reloadAndProfileConfig: ReloadAndProfileConfig, + shouldStartProfilingRightNow: boolean, + profilingSettings: ProfilingSettings, ): RendererInterface | void { // only attach if the renderer is compatible with the current version of the backend if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) { @@ -55,7 +56,8 @@ export default function attachRenderer( id, renderer, global, - reloadAndProfileConfig, + shouldStartProfilingRightNow, + profilingSettings, ); } else if (renderer.ComponentTree) { // react-dom v15 diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 2a0e9feb3784a..5c4a510948a08 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -26,11 +26,9 @@ import type { RendererID, RendererInterface, DevToolsHookSettings, - ReloadAndProfileConfigPersistence, } from './types'; import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types'; import {isReactNativeEnvironment} from './utils'; -import {defaultReloadAndProfileConfigPersistence} from '../utils'; import { sessionStorageGetItem, sessionStorageRemoveItem, @@ -151,33 +149,21 @@ export default class Agent extends EventEmitter<{ }> { _bridge: BackendBridge; _isProfiling: boolean = false; - _recordChangeDescriptions: boolean = false; _rendererInterfaces: {[key: RendererID]: RendererInterface, ...} = {}; _persistedSelection: PersistedSelection | null = null; _persistedSelectionMatch: PathMatch | null = null; _traceUpdatesEnabled: boolean = false; - _reloadAndProfileConfigPersistence: ReloadAndProfileConfigPersistence; + _onReloadAndProfile: ((recordChangeDescriptions: boolean) => void) | void; constructor( bridge: BackendBridge, - reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence = defaultReloadAndProfileConfigPersistence, + isProfiling: boolean = false, + onReloadAndProfile?: (recordChangeDescriptions: boolean) => void, ) { super(); - this._reloadAndProfileConfigPersistence = reloadAndProfileConfigPersistence; - const {getReloadAndProfileConfig, setReloadAndProfileConfig} = - reloadAndProfileConfigPersistence; - const reloadAndProfileConfig = getReloadAndProfileConfig(); - if (reloadAndProfileConfig.shouldReloadAndProfile) { - this._recordChangeDescriptions = - reloadAndProfileConfig.recordChangeDescriptions; - this._isProfiling = true; - - setReloadAndProfileConfig({ - shouldReloadAndProfile: false, - recordChangeDescriptions: false, - }); - } + this._isProfiling = isProfiling; + this._onReloadAndProfile = onReloadAndProfile; const persistedSelectionString = sessionStorageGetItem( SESSION_STORAGE_LAST_SELECTION_KEY, @@ -674,10 +660,9 @@ export default class Agent extends EventEmitter<{ reloadAndProfile: (recordChangeDescriptions: boolean) => void = recordChangeDescriptions => { - this._reloadAndProfileConfigPersistence.setReloadAndProfileConfig({ - shouldReloadAndProfile: true, - recordChangeDescriptions, - }); + if (typeof this._onReloadAndProfile === 'function') { + this._onReloadAndProfile(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. @@ -754,7 +739,6 @@ export default class Agent extends EventEmitter<{ startProfiling: (recordChangeDescriptions: boolean) => void = recordChangeDescriptions => { - this._recordChangeDescriptions = recordChangeDescriptions; this._isProfiling = true; for (const rendererID in this._rendererInterfaces) { const renderer = ((this._rendererInterfaces[ @@ -767,7 +751,6 @@ export default class Agent extends EventEmitter<{ stopProfiling: () => void = () => { this._isProfiling = false; - this._recordChangeDescriptions = false; for (const rendererID in this._rendererInterfaces) { const renderer = ((this._rendererInterfaces[ (rendererID: any) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 8ce815c31f731..103c0b0d71327 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -104,7 +104,6 @@ import { supportsOwnerStacks, supportsConsoleTasks, } from './DevToolsFiberComponentStack'; -import type {ReloadAndProfileConfig} from '../types'; // $FlowFixMe[method-unbinding] const toString = Object.prototype.toString; @@ -136,6 +135,7 @@ import type { WorkTagMap, CurrentDispatcherRef, LegacyDispatcherRef, + ProfilingSettings, } from '../types'; import type { ComponentFilter, @@ -864,7 +864,8 @@ export function attach( rendererID: number, renderer: ReactRenderer, global: Object, - reloadAndProfileConfig: ReloadAndProfileConfig, + shouldStartProfilingRightNow: boolean, + profilingSettings: ProfilingSettings, ): RendererInterface { // Newer versions of the reconciler package also specific reconciler version. // If that version number is present, use it. @@ -5225,10 +5226,8 @@ export function attach( } // Automatically start profiling so that we don't miss timing info from initial "mount". - if (reloadAndProfileConfig.shouldReloadAndProfile) { - const shouldRecordChangeDescriptions = - reloadAndProfileConfig.recordChangeDescriptions; - startProfiling(shouldRecordChangeDescriptions); + if (shouldStartProfilingRightNow) { + startProfiling(profilingSettings.recordChangeDescriptions); } 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 c6f743546e49e..61546f2c289a6 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -485,20 +485,10 @@ export type DevToolsBackend = { setupNativeStyleEditor?: SetupNativeStyleEditor, }; -export type ReloadAndProfileConfig = { - shouldReloadAndProfile: boolean, +export type ProfilingSettings = { 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 3b45c7417d4e0..bca5d4142d29e 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -16,7 +16,7 @@ import type { RendererInterface, DevToolsBackend, DevToolsHookSettings, - ReloadAndProfileConfig, + ProfilingSettings, } from './backend/types'; import { @@ -27,7 +27,6 @@ import { import attachRenderer from './attachRenderer'; import formatConsoleArguments from 'react-devtools-shared/src/backend/utils/formatConsoleArguments'; import formatWithStyles from 'react-devtools-shared/src/backend/utils/formatWithStyles'; -import {defaultReloadAndProfileConfigPersistence} from './utils'; // React's custom built component stack strings match "\s{4}in" // Chrome's prefix matches "\s{4}at" @@ -51,12 +50,17 @@ function areStackTracesEqual(a: string, b: string): boolean { const targetConsole: Object = console; +const defaultProfilingSettings: ProfilingSettings = { + recordChangeDescriptions: false, +}; + export function installHook( target: any, maybeSettingsOrSettingsPromise?: | DevToolsHookSettings | Promise, - reloadAndProfileConfig?: ReloadAndProfileConfig = defaultReloadAndProfileConfigPersistence.getReloadAndProfileConfig(), + shouldStartProfilingRightNow: boolean = false, + profilingSettings: ProfilingSettings = defaultProfilingSettings, ): DevToolsHook | null { if (target.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) { return null; @@ -195,6 +199,8 @@ export function installHook( } catch (err) {} } + // TODO: isProfiling should be stateful, and we should update it once profiling is finished + const isProfiling = shouldStartProfilingRightNow; let uidCounter = 0; function inject(renderer: ReactRenderer): number { const id = ++uidCounter; @@ -215,7 +221,8 @@ export function installHook( id, renderer, target, - reloadAndProfileConfig, + isProfiling, + profilingSettings, ); if (rendererInterface != null) { hook.rendererInterfaces.set(id, rendererInterface); diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 715834334f360..a9ebeaaa129da 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -36,8 +36,6 @@ import { TREE_OPERATION_UPDATE_TREE_BASE_DURATION, LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY, LOCAL_STORAGE_OPEN_IN_EDITOR_URL, - SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, - SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY, } from './constants'; import { ComponentFilterElementType, @@ -52,12 +50,7 @@ import { ElementTypeMemo, ElementTypeVirtual, } from 'react-devtools-shared/src/frontend/types'; -import { - localStorageGetItem, - localStorageSetItem, - sessionStorageGetItem, - sessionStorageSetItem, -} from './storage'; +import {localStorageGetItem, localStorageSetItem} from './storage'; import {meta} from './hydration'; import isArray from './isArray'; @@ -69,10 +62,6 @@ import type { } from 'react-devtools-shared/src/frontend/types'; import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types'; import {isSynchronousXHRSupported} from './backend/utils'; -import type { - ReloadAndProfileConfig, - ReloadAndProfileConfigPersistence, -} from './backend/types'; // $FlowFixMe[method-unbinding] const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -989,35 +978,3 @@ export function getIsReloadAndProfileSupported(): boolean { return isBackendStorageAPISupported && isSynchronousXHRSupported(); } - -export const defaultReloadAndProfileConfigPersistence: 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', - }; - }, - };