@@ -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