27
import syncSavedPreferences from './syncSavedPreferences';
28
import registerEventsLogger from './registerEventsLogger';
29
import getProfilingFlags from './getProfilingFlags';
30
+import debounce from './debounce';
31
import './requestAnimationFramePolyfill';
32
33
// Try polling for at least 5 seconds, in case if it takes too long to load react
35
const REACT_POLLING_ATTEMPTS_THRESHOLD = 20;
36
37
let reactPollingTimeoutId = null;
37
-function clearReactPollingTimeout() {
38
+export function clearReactPollingTimeout() {
39
clearTimeout(reactPollingTimeoutId);
40
reactPollingTimeoutId = null;
41
}
42
42
-function executeIfReactHasLoaded(callback, attempt = 1) {
43
- reactPollingTimeoutId = null;
43
+export function executeIfReactHasLoaded(callback, attempt = 1) {
44
+ clearReactPollingTimeout();
45
46
if (attempt > REACT_POLLING_ATTEMPTS_THRESHOLD) {
47
return;
82
);
83
}
84
85
+let lastSubscribedBridgeListener = null;
86
+
87
function createBridge() {
88
bridge = new Bridge({
89
listen(fn) {
87
- const listener = message => fn(message);
90
+ const bridgeListener = message => fn(message);
91
// Store the reference so that we unsubscribe from the same object.
92
const portOnMessage = port.onMessage;
90
- portOnMessage.addListener(listener);
93
+ portOnMessage.addListener(bridgeListener);
94
+
95
+ lastSubscribedBridgeListener = bridgeListener;
96
97
return () => {
93
- portOnMessage.removeListener(listener);
98
+ port?.onMessage.removeListener(bridgeListener);
99
+ lastSubscribedBridgeListener = null;
100
};
101
},
102
103
send(event: string, payload: any, transferable?: Array<any>) {
98
- port.postMessage({event, payload}, transferable);
104
+ port?.postMessage({event, payload}, transferable);
105
},
106
});
107
475
bridge = null;
476
render = null;
477
root = null;
472
-
473
- port?.disconnect();
474
- port = null;
478
}
479
480
function performFullCleanup() {
502
}
503
504
function connectExtensionPort() {
505
+ if (port) {
506
+ throw new Error('DevTools port was already connected');
507
+ }
508
+
509
const tabId = chrome.devtools.inspectedWindow.tabId;
510
port = chrome.runtime.connect({
511
name: String(tabId),
512
});
513
514
+ // If DevTools port was reconnected and Bridge was already created
515
+ // We should subscribe bridge to this port events
516
+ // This could happen if service worker dies and all ports are disconnected,
517
+ // but later user continues the session and Chrome reconnects all ports
518
+ // Bridge object is still in-memory, though
519
+ if (lastSubscribedBridgeListener) {
520
+ port.onMessage.addListener(lastSubscribedBridgeListener);
521
+ }
522
+
523
// This port may be disconnected by Chrome at some point, this callback
524
// will be executed only if this port was disconnected from the other end
525
// so, when we call `port.disconnect()` from this script,
526
// this should not trigger this callback and port reconnection
511
- port.onDisconnect.addListener(connectExtensionPort);
527
+ port.onDisconnect.addListener(() => {
528
+ port = null;
529
+ connectExtensionPort();
530
+ });
531
}
532
533
function mountReactDevTools() {
534
registerEventsLogger();
535
517
- connectExtensionPort();
518
-
536
createBridgeAndStore();
537
538
setReactSelectionFromBrowser(bridge);
549
mountReactDevTools();
550
}
551
535
- executeIfReactHasLoaded(onReactReady);
552
+ executeIfReactHasLoaded(onReactReady, 1);
553
}
554
555
let bridge = null;
572
// since global values stored on window get reset in this case.
573
chrome.devtools.network.onNavigated.addListener(syncSavedPreferences);
574
558
-// Cleanup previous page state and remount everything
559
-chrome.devtools.network.onNavigated.addListener(() => {
575
+// In case when multiple navigation events emitted in a short period of time
576
+// This debounced callback primarily used to avoid mounting React DevTools multiple times, which results
577
+// into subscribing to the same events from Bridge and window multiple times
578
+// In this case, we will handle `operations` event twice or more and user will see
579
+// `Cannot add node "1" because a node with that id is already in the Store.`
580
+const debouncedOnNavigatedListener = debounce(() => {
581
performInTabNavigationCleanup();
582
mountReactDevToolsWhenReactHasLoaded();
562
-});
583
+}, 500);
584
+
585
+// Cleanup previous page state and remount everything
586
+chrome.devtools.network.onNavigated.addListener(debouncedOnNavigatedListener);
587
588
// Should be emitted when browser DevTools are closed
589
if (IS_FIREFOX) {
593
window.addEventListener('beforeunload', performFullCleanup);
594
}
595
596
+connectExtensionPort();
597
+
598
syncSavedPreferences();
599
mountReactDevToolsWhenReactHasLoaded();