@@ -15,166 +15,183 @@ import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils';
1515import  { COMPACT_VERSION_NAME }  from  'react-devtools-extensions/src/utils' ; 
1616import  { getIsReloadAndProfileSupported }  from  'react-devtools-shared/src/utils' ; 
1717
18- let  welcomeHasInitialized  =  false ; 
19- 
20- function  welcome ( event : $FlowFixMe )  { 
21-   if  ( 
22-     event . source  !==  window  || 
23-     event . data . source  !==  'react-devtools-content-script' 
24-   )  { 
25-     return ; 
26-   } 
27- 
28-   // In some circumstances, this method is called more than once for a single welcome message. 
29-   // The exact circumstances of this are unclear, though it seems related to 3rd party event batching code. 
30-   // 
31-   // Regardless, call this method multiple times can cause DevTools to add duplicate elements to the Store 
32-   // (and throw an error) or worse yet, choke up entirely and freeze the browser. 
33-   // 
34-   // The simplest solution is to ignore the duplicate events. 
35-   // To be clear, this SHOULD NOT BE NECESSARY, since we remove the event handler below. 
36-   // 
37-   // See https://github.com/facebook/react/issues/24162 
38-   if  ( welcomeHasInitialized )  { 
39-     console . warn ( 
40-       'React DevTools detected duplicate welcome "message" events from the content script.' , 
41-     ) ; 
42-     return ; 
43-   } 
44- 
45-   welcomeHasInitialized  =  true ; 
46- 
47-   window . removeEventListener ( 'message' ,  welcome ) ; 
18+ /* 
19+  * Make sure this is executed only once in case Frontend is reloaded multiple times while Backend is initializing 
20+  * We can't use `reactDevToolsAgent` field on a global Hook object, because it only cleaned up after both Frontend and Backend initialized 
21+  */ 
22+ if  ( ! window . __REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__ )  { 
23+   window . __REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__  =  true ; 
24+ 
25+   let  welcomeHasInitialized  =  false ; 
26+   function  welcome ( event : $FlowFixMe )  { 
27+     if  ( 
28+       event . source  !==  window  || 
29+       event . data . source  !==  'react-devtools-content-script' 
30+     )  { 
31+       return ; 
32+     } 
33+ 
34+     // In some circumstances, this method is called more than once for a single welcome message. 
35+     // The exact circumstances of this are unclear, though it seems related to 3rd party event batching code. 
36+     // 
37+     // Regardless, call this method multiple times can cause DevTools to add duplicate elements to the Store 
38+     // (and throw an error) or worse yet, choke up entirely and freeze the browser. 
39+     // 
40+     // The simplest solution is to ignore the duplicate events. 
41+     // To be clear, this SHOULD NOT BE NECESSARY, since we remove the event handler below. 
42+     // 
43+     // See https://github.com/facebook/react/issues/24162 
44+     if  ( welcomeHasInitialized )  { 
45+       console . warn ( 
46+         'React DevTools detected duplicate welcome "message" events from the content script.' , 
47+       ) ; 
48+       return ; 
49+     } 
4850
49-   setup ( window . __REACT_DEVTOOLS_GLOBAL_HOOK__ ) ; 
50- } 
51+     welcomeHasInitialized  =  true ; 
5152
52- window . addEventListener ( 'message' ,  welcome ) ; 
53+      window . removeEventListener ( 'message' ,  welcome ) ; 
5354
54- function  setup ( hook : ?DevToolsHook )  { 
55-   // this should not happen, but Chrome can be weird sometimes 
56-   if  ( hook  ==  null )  { 
57-     return ; 
55+     setup ( window . __REACT_DEVTOOLS_GLOBAL_HOOK__ ) ; 
5856  } 
5957
60-   // register renderers that have already injected themselves. 
61-   hook . renderers . forEach ( renderer  =>  { 
62-     registerRenderer ( renderer ,  hook ) ; 
63-   } ) ; 
58+   window . addEventListener ( 'message' ,  welcome ) ; 
6459
65-   // Activate and remove from required all present backends, registered within the hook 
66-   hook . backends . forEach ( ( _ ,   backendVersion )   =>   { 
67-     requiredBackends . delete ( backendVersion ) ; 
68-     activateBackend ( backendVersion ,   hook ) ; 
69-   } ) ; 
60+   function   setup ( hook : ? DevToolsHook )   { 
61+      // this should not happen, but Chrome can be weird sometimes 
62+     if   ( hook   ==   null )   { 
63+        return ; 
64+      } 
7065
71-   updateRequiredBackends ( ) ; 
66+     // register renderers that have already injected themselves. 
67+     hook . renderers . forEach ( renderer  =>  { 
68+       registerRenderer ( renderer ,  hook ) ; 
69+     } ) ; 
7270
73-   // register renderers that inject themselves later.  
74-   hook . sub ( 'renderer' ,   ( { renderer } )  =>  { 
75-     registerRenderer ( renderer ,   hook ) ; 
76-     updateRequiredBackends ( ) ; 
77-   } ) ; 
71+      // Activate and remove from required all present backends, registered within the hook  
72+      hook . backends . forEach ( ( _ ,   backendVersion )  =>  { 
73+        requiredBackends . delete ( backendVersion ) ; 
74+        activateBackend ( backendVersion ,   hook ) ; 
75+      } ) ; 
7876
79-   // listen for backend installations. 
80-   hook . sub ( 'devtools-backend-installed' ,  version  =>  { 
81-     activateBackend ( version ,  hook ) ; 
8277    updateRequiredBackends ( ) ; 
83-   } ) ; 
84- } 
8578
86- const  requiredBackends  =  new  Set < string > ( ) ; 
79+     // register renderers that inject themselves later. 
80+     const  unsubscribeRendererListener  =  hook . sub ( 'renderer' ,  ( { renderer} )  =>  { 
81+       registerRenderer ( renderer ,  hook ) ; 
82+       updateRequiredBackends ( ) ; 
83+     } ) ; 
84+ 
85+     // listen for backend installations. 
86+     const  unsubscribeBackendInstallationListener  =  hook . sub ( 
87+       'devtools-backend-installed' , 
88+       version  =>  { 
89+         activateBackend ( version ,  hook ) ; 
90+         updateRequiredBackends ( ) ; 
91+       } , 
92+     ) ; 
8793
88- function  registerRenderer ( renderer : ReactRenderer ,  hook : DevToolsHook )  { 
89-   let  version  =  renderer . reconcilerVersion  ||  renderer . version ; 
90-   if  ( ! hasAssignedBackend ( version ) )  { 
91-     version  =  COMPACT_VERSION_NAME ; 
94+     const  unsubscribeShutdownListener  =  hook . sub ( 'shutdown' ,  ( )  =>  { 
95+       unsubscribeRendererListener ( ) ; 
96+       unsubscribeBackendInstallationListener ( ) ; 
97+       unsubscribeShutdownListener ( ) ; 
98+     } ) ; 
9299  } 
93100
94-   // Check if required backend is already activated, no need to require again 
95-   if  ( ! hook . backends . has ( version ) )  { 
96-     requiredBackends . add ( version ) ; 
97-   } 
98- } 
101+   const  requiredBackends  =  new  Set < string > ( ) ; 
99102
100- function  activateBackend ( version :  string ,  hook : DevToolsHook )  { 
101-   const   backend   =   hook . backends . get ( version ) ; 
102-   if  ( ! backend )  { 
103-     throw   new   Error ( `Could not find backend for  version " ${ version } "` ) ; 
104-   } 
103+    function  registerRenderer ( renderer :  ReactRenderer ,  hook : DevToolsHook )  { 
104+      let   version   =   renderer . reconcilerVersion   ||   renderer . version ; 
105+      if  ( ! hasAssignedBackend ( version ) )  { 
106+        version  =   COMPACT_VERSION_NAME ; 
107+      } 
105108
106-   const  { Agent,  Bridge,  initBackend,  setupNativeStyleEditor}  =  backend ; 
107-   const  bridge  =  new  Bridge ( { 
108-     listen ( fn )  { 
109-       const  listener  =  ( event : $FlowFixMe )  =>  { 
110-         if  ( 
111-           event . source  !==  window  || 
112-           ! event . data  || 
113-           event . data . source  !==  'react-devtools-content-script'  || 
114-           ! event . data . payload 
115-         )  { 
116-           return ; 
117-         } 
118-         fn ( event . data . payload ) ; 
119-       } ; 
120-       window . addEventListener ( 'message' ,  listener ) ; 
121-       return  ( )  =>  { 
122-         window . removeEventListener ( 'message' ,  listener ) ; 
123-       } ; 
124-     } , 
125-     send ( event : string ,  payload : any ,  transferable ?: Array < any > )  { 
126-       window. postMessage ( 
127-         { 
128-           source : 'react-devtools-bridge' , 
129-           payload : { event,  payload} , 
130-         } , 
131-         '*' , 
132-         transferable , 
133-       ) ; 
134-     } , 
135-   } ) ; 
136- 
137-   const  agent  =  new  Agent ( bridge ) ; 
138-   agent . addListener ( 'shutdown' ,  ( )  =>  { 
139-     // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down, 
140-     // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here. 
141-     hook . emit ( 'shutdown' ) ; 
142-   } ) ; 
143- 
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 )  { 
148-     setupNativeStyleEditor ( 
149-       bridge , 
150-       agent , 
151-       hook . resolveRNStyle , 
152-       hook . nativeStyleEditorValidAttributes , 
153-     ) ; 
109+     // Check if required backend is already activated, no need to require again 
110+     if  ( ! hook . backends . has ( version ) )  { 
111+       requiredBackends . add ( version ) ; 
112+     } 
154113  } 
155114
156-   // Let the frontend know that the backend has attached listeners and is ready for messages. 
157-   // This covers the case of syncing saved values after reloading/navigating while DevTools remain open. 
158-   bridge . send ( 'extensionBackendInitialized' ) ; 
115+   function  activateBackend ( version : string ,  hook : DevToolsHook )  { 
116+     const  backend  =  hook . backends . get ( version ) ; 
117+     if  ( ! backend )  { 
118+       throw  new  Error ( `Could not find backend for version "${ version }  ) ; 
119+     } 
120+ 
121+     const  { Agent,  Bridge,  initBackend,  setupNativeStyleEditor}  =  backend ; 
122+     const  bridge  =  new  Bridge ( { 
123+       listen ( fn )  { 
124+         const  listener  =  ( event : $FlowFixMe )  =>  { 
125+           if  ( 
126+             event . source  !==  window  || 
127+             ! event . data  || 
128+             event . data . source  !==  'react-devtools-content-script'  || 
129+             ! event . data . payload 
130+           )  { 
131+             return ; 
132+           } 
133+           fn ( event . data . payload ) ; 
134+         } ; 
135+         window . addEventListener ( 'message' ,  listener ) ; 
136+         return  ( )  =>  { 
137+           window . removeEventListener ( 'message' ,  listener ) ; 
138+         } ; 
139+       } , 
140+       send ( event : string ,  payload : any ,  transferable ?: Array < any > )  { 
141+         window. postMessage ( 
142+           { 
143+             source : 'react-devtools-bridge' , 
144+             payload : { event,  payload} , 
145+           } , 
146+           '*' , 
147+           transferable , 
148+         ) ; 
149+       } , 
150+     } ) ; 
151+ 
152+     const  agent  =  new  Agent ( bridge ) ; 
153+     agent . addListener ( 'shutdown' ,  ( )  =>  { 
154+       // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down, 
155+       // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here. 
156+       hook . emit ( 'shutdown' ) ; 
157+       delete  window . __REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__ ; 
158+     } ) ; 
159+ 
160+     initBackend ( hook ,  agent ,  window ,  getIsReloadAndProfileSupported ( ) ) ; 
161+ 
162+     // Setup React Native style editor if a renderer like react-native-web has injected it. 
163+     if  ( typeof  setupNativeStyleEditor  ===  'function'  &&  hook . resolveRNStyle )  { 
164+       setupNativeStyleEditor ( 
165+         bridge , 
166+         agent , 
167+         hook . resolveRNStyle , 
168+         hook . nativeStyleEditorValidAttributes , 
169+       ) ; 
170+     } 
159171
160-   // this  backend is activated  
161-   requiredBackends . delete ( version ) ; 
162- } 
172+      // Let the frontend know that the  backend has attached listeners and  is ready for messages.  
173+      // This covers the case of syncing saved values after reloading/navigating while DevTools remain open. 
174+      bridge . send ( 'extensionBackendInitialized' ) ; 
163175
164- // tell the service worker which versions of backends are needed for the current page 
165- function  updateRequiredBackends ( )  { 
166-   if  ( requiredBackends . size  ===  0 )  { 
167-     return ; 
176+     // this backend is activated 
177+     requiredBackends . delete ( version ) ; 
168178  } 
169179
170-   window . postMessage ( 
171-     { 
172-       source : 'react-devtools-backend-manager' , 
173-       payload : { 
174-         type : 'require-backends' , 
175-         versions : Array . from ( requiredBackends ) , 
180+   // tell the service worker which versions of backends are needed for the current page 
181+   function  updateRequiredBackends ( )  { 
182+     if  ( requiredBackends . size  ===  0 )  { 
183+       return ; 
184+     } 
185+ 
186+     window . postMessage ( 
187+       { 
188+         source : 'react-devtools-backend-manager' , 
189+         payload : { 
190+           type : 'require-backends' , 
191+           versions : Array . from ( requiredBackends ) , 
192+         } , 
176193      } , 
177-     } , 
178-     '*' , 
179-   ) ; 
194+        '*' , 
195+     ) ; 
196+   } 
180197} 
0 commit comments