@@ -11,6 +11,7 @@ import EventEmitter from '../events';
1111import { prepareProfilingDataFrontendFromBackendAndStore } from './views/Profiler/utils' ;
1212import ProfilingCache from './ProfilingCache' ;
1313import Store from './store' ;
14+ import { logEvent } from 'react-devtools-shared/src/Logger' ;
1415
1516import type { FrontendBridge } from 'react-devtools-shared/src/bridge' ;
1617import type { ProfilingDataBackend } from 'react-devtools-shared/src/backend/types' ;
@@ -67,7 +68,12 @@ export default class ProfilerStore extends EventEmitter<{
6768
6869 // The backend is currently profiling.
6970 // When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
70- _isProfiling : boolean = false ;
71+ _isBackendProfiling : boolean = false ;
72+
73+ // Mainly used for optimistic UI.
74+ // This could be false, but at the same time _isBackendProfiling could be true
75+ // for cases when Backend is busy serializing a chunky payload.
76+ _isProfilingBasedOnUserInput : boolean = false ;
7177
7278 // Tracks whether a specific renderer logged any profiling data during the most recent session.
7379 _rendererIDsThatReportedProfilingData : Set < number > = new Set ( ) ;
@@ -86,7 +92,8 @@ export default class ProfilerStore extends EventEmitter<{
8692 super ( ) ;
8793
8894 this . _bridge = bridge ;
89- this . _isProfiling = defaultIsProfiling ;
95+ this . _isBackendProfiling = defaultIsProfiling ;
96+ this . _isProfilingBasedOnUserInput = defaultIsProfiling ;
9097 this . _store = store ;
9198
9299 bridge . addListener ( 'operations' , this . onBridgeOperations ) ;
@@ -139,8 +146,8 @@ export default class ProfilerStore extends EventEmitter<{
139146 return this . _rendererQueue . size > 0 || this . _dataBackends . length > 0 ;
140147 }
141148
142- get isProfiling ( ) : boolean {
143- return this . _isProfiling ;
149+ get isProfilingBasedOnUserInput ( ) : boolean {
150+ return this . _isProfilingBasedOnUserInput ;
144151 }
145152
146153 get profilingCache ( ) : ProfilingCache {
@@ -151,7 +158,7 @@ export default class ProfilerStore extends EventEmitter<{
151158 return this . _dataFrontend ;
152159 }
153160 set profilingData ( value : ProfilingDataFrontend | null ) : void {
154- if ( this . _isProfiling ) {
161+ if ( this . _isBackendProfiling ) {
155162 console . warn (
156163 'Profiling data cannot be updated while profiling is in progress.' ,
157164 ) ;
@@ -186,6 +193,9 @@ export default class ProfilerStore extends EventEmitter<{
186193 startProfiling ( ) : void {
187194 this. _bridge . send ( 'startProfiling' , this . _store . recordChangeDescriptions ) ;
188195
196+ this . _isProfilingBasedOnUserInput = true ;
197+ this . emit ( 'isProfiling' ) ;
198+
189199 // Don't actually update the local profiling boolean yet!
190200 // Wait for onProfilingStatus() to confirm the status has changed.
191201 // This ensures the frontend and backend are in sync wrt which commits were profiled.
@@ -195,8 +205,12 @@ export default class ProfilerStore extends EventEmitter<{
195205 stopProfiling ( ) : void {
196206 this. _bridge . send ( 'stopProfiling' ) ;
197207
198- // Don't actually update the local profiling boolean yet!
199- // Wait for onProfilingStatus() to confirm the status has changed.
208+ // Backend might be busy serializing the payload, so we are going to display
209+ // optimistic UI to the user that profiling is stopping.
210+ this . _isProfilingBasedOnUserInput = false ;
211+ this . emit ( 'isProfiling' ) ;
212+
213+ // Wait for onProfilingStatus() to confirm the status has changed, this will update _isBackendProfiling.
200214 // This ensures the frontend and backend are in sync wrt which commits were profiled.
201215 // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
202216 }
@@ -229,7 +243,7 @@ export default class ProfilerStore extends EventEmitter<{
229243 const rendererID = operations [ 0 ] ;
230244 const rootID = operations [ 1 ] ;
231245
232- if ( this . _isProfiling ) {
246+ if ( this . _isBackendProfiling ) {
233247 let profilingOperations = this . _inProgressOperationsByRootID . get ( rootID ) ;
234248 if ( profilingOperations == null ) {
235249 profilingOperations = [ operations ] ;
@@ -252,8 +266,8 @@ export default class ProfilerStore extends EventEmitter<{
252266
253267 onBridgeProfilingData: ( dataBackend : ProfilingDataBackend ) => void =
254268 dataBackend => {
255- if ( this . _isProfiling ) {
256- // This should never happen, but if it does- ignore previous profiling data.
269+ if ( this . _isBackendProfiling ) {
270+ // This should never happen, but if it does, then ignore previous profiling data.
257271 return ;
258272 }
259273
@@ -289,7 +303,7 @@ export default class ProfilerStore extends EventEmitter<{
289303 } ;
290304
291305 onProfilingStatus: ( isProfiling : boolean ) => void = isProfiling => {
292- if ( this . _isProfiling === isProfiling ) {
306+ if ( this . _isBackendProfiling === isProfiling ) {
293307 return ;
294308 }
295309
@@ -319,15 +333,25 @@ export default class ProfilerStore extends EventEmitter<{
319333 } ) ;
320334 }
321335
322- this . _isProfiling = isProfiling ;
336+ this . _isBackendProfiling = isProfiling ;
337+ // _isProfilingBasedOnUserInput should already be updated from startProfiling, stopProfiling, or constructor.
338+ if ( this . _isProfilingBasedOnUserInput !== isProfiling ) {
339+ logEvent ( {
340+ event_name : 'error' ,
341+ error_message : `Unexpected profiling status. Expected ${ this . _isProfilingBasedOnUserInput . toString ( ) } , but received ${ isProfiling . toString ( ) } .` ,
342+ error_stack : new Error ( ) . stack ,
343+ error_component_stack : null ,
344+ } ) ;
345+
346+ // If happened, fallback to displaying the value from Backend
347+ this . _isProfilingBasedOnUserInput = isProfiling ;
348+ }
323349
324350 // Invalidate suspense cache if profiling data is being (re-)recorded.
325351 // Note that we clear again, in case any views read from the cache while profiling.
326352 // (That would have resolved a now-stale value without any profiling data.)
327353 this . _cache . invalidate ( ) ;
328354
329- this . emit ( 'isProfiling' ) ;
330-
331355 // If we've just finished a profiling session, we need to fetch data stored in each renderer interface
332356 // and re-assemble it on the front-end into a format (ProfilingDataFrontend) that can power the Profiler UI.
333357 // During this time, DevTools UI should probably not be interactive.
0 commit comments