fix[devtools/extension]: added a workaround for proxy content script injection in firefox (#27375)
Changes: 1. [Firefox-only] For some reason, Firefox might try to inject dynamically registered content script in pages like `about:blank`. I couldn't find a way to change this behaviour, `about:` is not a valid scheme, so we can't exclude it and `match_about_blank` flag is not supported in `chrome.scripting.registerContentScripts`. 2. While navigating the history in Firefox, some content scripts might not be re-injected and still be alive. To handle this, we are now patching `window` with `__REACT_DEVTOOLS_PROXY_INJECTED__` flag, to make sure that proxy is injected and only once. This flag is cleared on `pagehide` event.
Ruslan Lesiutin committed
Sep 22, 2023 at 19:36 UTC
f9d75e32ba85b3c6a2a5d20fa27ba7e23d5529b2
2 files changed
+35
-13
packages/react-devtools-extensions/src/background/index.js
+6
@@ -60,6 +60,12 @@ function isNumeric(str: string): boolean {
60
61
chrome.runtime.onConnect.addListener(port => {
62
if (port.name === 'proxy') {
63
+ // Might not be present for restricted pages in Firefox
64
+ if (port.sender?.tab?.id == null) {
65
+ // Not disconnecting it, so it would not reconnect
66
+ return;
67
+ }
68
+
69
// Proxy content script is executed in tab, so it should have it specified.
70
const tabId = port.sender.tab.id;
71
packages/react-devtools-extensions/src/contentScripts/proxy.js
+29
-13
@@ -2,22 +2,38 @@
2
3
'use strict';
4
5
-let port = null;
6
-let backendInitialized: boolean = false;
7
-
8
-connectPort();
9
-sayHelloToBackendManager();
5
+window.addEventListener('pageshow', function ({target}) {
6
+ // Firefox's behaviour for injecting this content script can be unpredictable
7
+ // While navigating the history, some content scripts might not be re-injected and still be alive
8
+ if (!window.__REACT_DEVTOOLS_PROXY_INJECTED__) {
9
+ window.__REACT_DEVTOOLS_PROXY_INJECTED__ = true;
10
11
-// The backend waits to install the global hook until notified by the content script.
12
-// In the event of a page reload, the content script might be loaded before the backend manager is injected.
13
-// Because of this we need to poll the backend manager until it has been initialized.
14
-const intervalID = setInterval(() => {
15
- if (backendInitialized) {
16
- clearInterval(intervalID);
17
- } else {
11
+ connectPort();
12
sayHelloToBackendManager();
13
+
14
+ // The backend waits to install the global hook until notified by the content script.
15
+ // In the event of a page reload, the content script might be loaded before the backend manager is injected.
16
+ // Because of this we need to poll the backend manager until it has been initialized.
17
+ const intervalID = setInterval(() => {
18
+ if (backendInitialized) {
19
+ clearInterval(intervalID);
20
+ } else {
21
+ sayHelloToBackendManager();
22
+ }
23
+ }, 500);
24
+ }
25
+});
26
+
27
+window.addEventListener('pagehide', function ({target}) {
28
+ if (target !== window.document) {
29
+ return;
30
}
20
-}, 500);
31
+
32
+ delete window.__REACT_DEVTOOLS_PROXY_INJECTED__;
33
+});
34
+
35
+let port = null;
36
+let backendInitialized: boolean = false;
37
38
function sayHelloToBackendManager() {
39
window.postMessage(