Skip to content

Commit 0e7f122

Browse files
committed
refactor[devtools/extension]: refactored messaging logic across different parts of the extension
1 parent 69728fd commit 0e7f122

File tree

14 files changed

+389
-291
lines changed

14 files changed

+389
-291
lines changed

packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ const contentScriptsToInject = IS_FIREFOX
1313
persistAcrossSessions: true,
1414
runAt: 'document_end',
1515
},
16+
{
17+
id: '@react-devtools/file-fetcher',
18+
js: ['build/fileFetcher.js'],
19+
matches: ['<all_urls>'],
20+
persistAcrossSessions: true,
21+
runAt: 'document_end',
22+
},
1623
]
1724
: [
1825
{
@@ -23,6 +30,14 @@ const contentScriptsToInject = IS_FIREFOX
2330
runAt: 'document_end',
2431
world: chrome.scripting.ExecutionWorld.ISOLATED,
2532
},
33+
{
34+
id: '@react-devtools/file-fetcher',
35+
js: ['build/fileFetcher.js'],
36+
matches: ['<all_urls>'],
37+
persistAcrossSessions: true,
38+
runAt: 'document_end',
39+
world: chrome.scripting.ExecutionWorld.ISOLATED,
40+
},
2641
{
2742
id: '@react-devtools/hook',
2843
js: ['build/installHook.js'],
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* global chrome */
2+
3+
import {IS_FIREFOX} from '../utils';
4+
5+
// Firefox doesn't support ExecutionWorld.MAIN yet
6+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
7+
function executeScriptForFirefoxInMainWorld({target, files}) {
8+
return chrome.scripting.executeScript({
9+
target,
10+
func: fileNames => {
11+
function injectScriptSync(src) {
12+
let code = '';
13+
const request = new XMLHttpRequest();
14+
request.addEventListener('load', function () {
15+
code = this.responseText;
16+
});
17+
request.open('GET', src, false);
18+
request.send();
19+
20+
const script = document.createElement('script');
21+
script.textContent = code;
22+
23+
// This script runs before the <head> element is created,
24+
// so we add the script to <html> instead.
25+
if (document.documentElement) {
26+
document.documentElement.appendChild(script);
27+
}
28+
29+
if (script.parentNode) {
30+
script.parentNode.removeChild(script);
31+
}
32+
}
33+
34+
fileNames.forEach(file => injectScriptSync(chrome.runtime.getURL(file)));
35+
},
36+
args: [files],
37+
});
38+
}
39+
40+
export function executeScriptInIsolatedWorld({target, files}) {
41+
return chrome.scripting.executeScript({
42+
target,
43+
files,
44+
world: chrome.scripting.ExecutionWorld.ISOLATED,
45+
});
46+
}
47+
48+
export function executeScriptInMainWorld({target, files}) {
49+
if (IS_FIREFOX) {
50+
return executeScriptForFirefoxInMainWorld({target, files});
51+
}
52+
53+
return chrome.scripting.executeScript({
54+
target,
55+
files,
56+
world: chrome.scripting.ExecutionWorld.MAIN,
57+
});
58+
}

packages/react-devtools-extensions/src/background/index.js

Lines changed: 21 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
'use strict';
44

5-
import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from '../utils';
6-
75
import './dynamicallyInjectContentScripts';
86
import './tabsManager';
9-
import setExtensionIconAndPopup from './setExtensionIconAndPopup';
7+
8+
import {
9+
handleDevToolsPageMessage,
10+
handleBackendManagerMessage,
11+
handleReactDevToolsHookMessage,
12+
handleFetchResourceContentScriptMessage,
13+
} from './messageHandlers';
1014

1115
/*
1216
{
@@ -173,67 +177,21 @@ function connectExtensionAndProxyPorts(extensionPort, proxyPort, tabId) {
173177
}
174178

175179
chrome.runtime.onMessage.addListener((message, sender) => {
176-
const tab = sender.tab;
177-
// sender.tab.id from content script points to the tab that injected the content script
178-
if (tab) {
179-
const id = tab.id;
180-
// This is sent from the hook content script.
181-
// It tells us a renderer has attached.
182-
if (message.hasDetectedReact) {
183-
setExtensionIconAndPopup(message.reactBuildType, id);
184-
} else {
185-
const extensionPort = ports[id]?.extension;
186-
187-
switch (message.payload?.type) {
188-
case 'fetch-file-with-cache-complete':
189-
case 'fetch-file-with-cache-error':
190-
// Forward the result of fetch-in-page requests back to the extension.
191-
extensionPort?.postMessage(message);
192-
break;
193-
// This is sent from the backend manager running on a page
194-
case 'react-devtools-required-backends':
195-
const backendsToDownload = [];
196-
message.payload.versions.forEach(version => {
197-
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
198-
if (!IS_FIREFOX) {
199-
// equivalent logic for Firefox is in prepareInjection.js
200-
chrome.scripting.executeScript({
201-
target: {tabId: id},
202-
files: [`/build/react_devtools_backend_${version}.js`],
203-
world: chrome.scripting.ExecutionWorld.MAIN,
204-
});
205-
}
206-
} else {
207-
backendsToDownload.push(version);
208-
}
209-
});
210-
211-
// Request the necessary backends in the extension DevTools UI
212-
// TODO: handle this message in index.js to build the UI
213-
extensionPort?.postMessage({
214-
payload: {
215-
type: 'react-devtools-additional-backends',
216-
versions: backendsToDownload,
217-
},
218-
});
219-
break;
220-
}
180+
switch (message?.source) {
181+
case 'devtools-page': {
182+
handleDevToolsPageMessage(message);
183+
break;
221184
}
222-
}
223-
224-
// This is sent from the devtools page when it is ready for injecting the backend
225-
if (message?.payload?.type === 'react-devtools-inject-backend-manager') {
226-
// sender.tab.id from devtools page may not exist, or point to the undocked devtools window
227-
// so we use the payload to get the tab id
228-
const tabId = message.payload.tabId;
229-
230-
if (tabId && !IS_FIREFOX) {
231-
// equivalent logic for Firefox is in prepareInjection.js
232-
chrome.scripting.executeScript({
233-
target: {tabId},
234-
files: ['/build/backendManager.js'],
235-
world: chrome.scripting.ExecutionWorld.MAIN,
236-
});
185+
case 'react-devtools-fetch-resource-content-script': {
186+
handleFetchResourceContentScriptMessage(message);
187+
break;
188+
}
189+
case 'react-devtools-backend-manager': {
190+
handleBackendManagerMessage(message, sender);
191+
break;
192+
}
193+
case 'react-devtools-hook': {
194+
handleReactDevToolsHookMessage(message, sender);
237195
}
238196
}
239197
});

packages/react-devtools-extensions/src/background/injectProxy.js

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* global chrome */
2+
3+
import setExtensionIconAndPopup from './setExtensionIconAndPopup';
4+
import {executeScriptInMainWorld} from './executeScript';
5+
6+
import {EXTENSION_CONTAINED_VERSIONS} from '../utils';
7+
8+
export function handleReactDevToolsHookMessage(message, sender) {
9+
const {payload} = message;
10+
11+
switch (payload?.type) {
12+
case 'react-renderer-attached': {
13+
setExtensionIconAndPopup(payload.reactBuildType, sender.tab.id);
14+
}
15+
}
16+
}
17+
18+
export function handleBackendManagerMessage(message, sender) {
19+
const {payload} = message;
20+
21+
switch (payload?.type) {
22+
case 'require-backends': {
23+
payload.versions.forEach(version => {
24+
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
25+
executeScriptInMainWorld({
26+
target: {tabId: sender.tab.id},
27+
files: [`/build/react_devtools_backend_${version}.js`],
28+
});
29+
}
30+
});
31+
32+
break;
33+
}
34+
}
35+
}
36+
37+
export function handleDevToolsPageMessage(message) {
38+
const {payload} = message;
39+
40+
switch (payload?.type) {
41+
// Proxy this message from DevTools page to content script via chrome.tabs.sendMessage
42+
case 'fetch-file-with-cache': {
43+
const {
44+
payload: {tabId, url},
45+
} = message;
46+
47+
if (!tabId) {
48+
throw new Error("Couldn't fetch file sources: tabId not specified");
49+
}
50+
51+
if (!url) {
52+
throw new Error("Couldn't fetch file sources: url not specified");
53+
}
54+
55+
chrome.tabs.sendMessage(tabId, {
56+
source: 'devtools-page',
57+
payload: {
58+
type: 'fetch-file-with-cache',
59+
url,
60+
},
61+
});
62+
63+
break;
64+
}
65+
66+
case 'inject-backend-manager': {
67+
const {
68+
payload: {tabId},
69+
} = message;
70+
71+
if (!tabId) {
72+
throw new Error("Couldn't inject backend manager: tabId not specified");
73+
}
74+
75+
executeScriptInMainWorld({
76+
target: {tabId},
77+
files: ['/build/backendManager.js'],
78+
});
79+
80+
break;
81+
}
82+
}
83+
}
84+
85+
export function handleFetchResourceContentScriptMessage(message) {
86+
const {payload} = message;
87+
88+
switch (payload?.type) {
89+
case 'fetch-file-with-cache-complete':
90+
case 'fetch-file-with-cache-error':
91+
// Forward the result of fetch-in-page requests back to the DevTools page.
92+
// We switch the source here because of inconsistency between Firefox and Chrome
93+
// In Chromium this message will be propagated from content script to DevTools page
94+
// For Firefox, only background script will get this message, so we need to forward it to DevTools page
95+
chrome.runtime.sendMessage({
96+
source: 'react-devtools-background',
97+
payload,
98+
});
99+
break;
100+
}
101+
}

packages/react-devtools-extensions/src/contentScripts/backendManager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ function updateRequiredBackends() {
170170
{
171171
source: 'react-devtools-backend-manager',
172172
payload: {
173-
type: 'react-devtools-required-backends',
173+
type: 'require-backends',
174174
versions: Array.from(requiredBackends),
175175
},
176176
},
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* global chrome */
2+
3+
function fetchResource(url) {
4+
const reject = value => {
5+
console.log('fetch reject');
6+
chrome.runtime.sendMessage({
7+
source: 'react-devtools-fetch-resource-content-script',
8+
payload: {
9+
type: 'fetch-file-with-cache-error',
10+
url,
11+
value,
12+
},
13+
});
14+
};
15+
16+
const resolve = value => {
17+
console.log('fetch resolve');
18+
chrome.runtime.sendMessage({
19+
source: 'react-devtools-fetch-resource-content-script',
20+
payload: {
21+
type: 'fetch-file-with-cache-complete',
22+
url,
23+
value,
24+
},
25+
});
26+
};
27+
28+
fetch(url, {cache: 'force-cache'}).then(
29+
response => {
30+
if (response.ok) {
31+
response
32+
.text()
33+
.then(text => resolve(text))
34+
.catch(error => reject(null));
35+
} else {
36+
reject(null);
37+
}
38+
},
39+
error => reject(null),
40+
);
41+
}
42+
43+
chrome.runtime.onMessage.addListener(message => {
44+
if (
45+
message?.source === 'devtools-page' &&
46+
message?.payload?.type === 'fetch-file-with-cache'
47+
) {
48+
console.log('fetch start');
49+
fetchResource(message.payload.url);
50+
}
51+
});

packages/react-devtools-extensions/src/contentScripts/installHook.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
1010
function ({reactBuildType}) {
1111
window.postMessage(
1212
{
13-
source: 'react-devtools-detector',
14-
reactBuildType,
13+
source: 'react-devtools-hook',
14+
payload: {
15+
type: 'react-renderer-attached',
16+
reactBuildType,
17+
},
1518
},
1619
'*',
1720
);

0 commit comments

Comments
 (0)