Skip to content

Commit 790f8b7

Browse files
Feat/upgrade to manifest v3 (#129)
* feat: upgrade to manifest v3 fields * feat: update manifest with service worker, csp * feat: install global hook without unsafe eval * fix: get panel loader working with executeScript * spike: no progress but placeholder before cursor help * feat: app content is loaded, need to get connected * fix: load backend script * feat: set up connection between backend and content script * chore: add some more debugging * feat: add some more bugs for backend * spike: more debuggin * fix: it finally works * chore: clean up console logs from debugging * feat: undo formatting changes in backend * chore: undo formatting in backend again * chore: restore backend - no changes needed here * fix: remove broken context menu option * chore: remove unused function
1 parent 14569d9 commit 790f8b7

File tree

9 files changed

+302
-186
lines changed

9 files changed

+302
-186
lines changed

src/shells/webextension/background.js

Lines changed: 57 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import debugConnection from '../../utils/debugConnection';
33
/*
44
* background.js
55
*
6-
* Runs all the time and serves as a central message hub for panels, contentScript, backend
6+
* Runs as a service worker serves as a central message hub for panels, contentScript, backend
77
*/
88

99
if (process.env.NODE_ENV === 'test') {
@@ -100,59 +100,27 @@ const installContentScript = tabId => {
100100
if (err) {
101101
handleInstallError(tabId, err);
102102
} else {
103-
chrome.tabs.executeScript(tabId, { file: '/contentScript.js' }, res => {
103+
chrome.scripting.executeScript({ target: { tabId }, files: ['/backend.js'] }, res => {
104104
const installError = chrome.runtime.lastError;
105105
if (err || !res) handleInstallError(tabId, installError);
106106
});
107107
}
108108
});
109109
};
110110

111-
function doublePipe(one, two) {
112-
if (!one.$i) {
113-
one.$i = Math.random().toString(32).slice(2);
114-
}
115-
if (!two.$i) {
116-
two.$i = Math.random().toString(32).slice(2);
117-
}
118-
119-
debugConnection(`BACKGORUND: connect ${one.name} <-> ${two.name} [${one.$i} <-> ${two.$i}]`);
120-
121-
function lOne(message) {
122-
debugConnection(`${one.name} -> BACKGORUND -> ${two.name} [${one.$i}-${two.$i}]`, message);
123-
try {
124-
two.postMessage(message);
125-
} catch (e) {
126-
if (__DEV__) console.error('Unexpected disconnect, error', e); // eslint-disable-line no-console
127-
shutdown(); // eslint-disable-line no-use-before-define
128-
}
129-
}
130-
function lTwo(message) {
131-
debugConnection(`${two.name} -> BACKGORUND -> ${one.name} [${two.$i}-${one.$i}]`, message);
132-
try {
133-
one.postMessage(message);
134-
} catch (e) {
135-
if (__DEV__) console.error('Unexpected disconnect, error', e); // eslint-disable-line no-console
136-
shutdown(); // eslint-disable-line no-use-before-define
137-
}
138-
}
139-
one.onMessage.addListener(lOne);
140-
two.onMessage.addListener(lTwo);
141-
function shutdown() {
142-
debugConnection(`SHUTDOWN ${one.name} <-> ${two.name} [${one.$i} <-> ${two.$i}]`);
143-
one.onMessage.removeListener(lOne);
144-
two.onMessage.removeListener(lTwo);
145-
one.disconnect();
146-
two.disconnect();
147-
}
148-
one.onDisconnect.addListener(shutdown);
149-
two.onDisconnect.addListener(shutdown);
150-
}
151-
152111
if (chrome.contextMenus) {
153-
// electron doesn't support this api
154-
chrome.contextMenus.onClicked.addListener((_, contentWindow) => {
155-
openWindow(contentWindow.id);
112+
chrome.contextMenus.onClicked.addListener((info, tab) => {
113+
console.log('Context menu clicked', info, tab);
114+
if (info.menuItemId === 'mobx-devtools') {
115+
try {
116+
console.log('Attempting to open window for tab', tab.id);
117+
window.contentTabId = tab.id;
118+
installContentScript(tab.id);
119+
openWindow(tab.id);
120+
} catch (err) {
121+
console.error('Error opening devtools window:', err);
122+
}
123+
}
156124
});
157125
}
158126

@@ -176,54 +144,57 @@ if (chrome.browserAction) {
176144
});
177145
}
178146

179-
chrome.runtime.onInstalled.addListener(() => {
180-
chrome.contextMenus.create({
181-
id: 'mobx-devtools',
182-
title: 'Open Mobx DevTools',
183-
contexts: ['all'],
184-
});
147+
// Keep service worker alive
148+
chrome.runtime.onConnect.addListener(port => {
149+
console.log('Service worker connected to port:', port.name);
185150
});
186151

152+
// Create a long-lived connection for the content script
153+
let contentScriptPorts = new Map();
154+
187155
chrome.runtime.onConnect.addListener(port => {
188-
let tab = null;
189-
let name = null;
190-
if (isNumeric(port.name)) {
191-
tab = port.name;
192-
name = 'devtools';
193-
installContentScript(+port.name);
194-
} else {
195-
tab = port.sender.tab.id;
196-
name = 'content-script';
197-
}
156+
if (port.name === 'content-script') {
157+
const tabId = port.sender.tab.id;
158+
contentScriptPorts.set(tabId, port);
198159

199-
if (!orphansByTabId[tab]) {
200-
orphansByTabId[tab] = [];
160+
port.onDisconnect.addListener(() => {
161+
contentScriptPorts.delete(tabId);
162+
});
201163
}
164+
});
202165

203-
if (name === 'content-script') {
204-
const orphan = orphansByTabId[tab].find(t => t.name === 'devtools');
205-
if (orphan) {
206-
doublePipe(orphan.port, port);
207-
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== orphan);
208-
} else {
209-
const newOrphan = { name, port };
210-
orphansByTabId[tab].push(newOrphan);
211-
port.onDisconnect.addListener(() => {
212-
if (__DEV__) console.warn('orphan devtools disconnected'); // eslint-disable-line no-console
213-
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== newOrphan);
166+
// Handle messages from panel
167+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
168+
if (message.type === 'panel-to-backend') {
169+
// Use the existing port to send to content script
170+
const port = contentScriptPorts.get(message.tabId);
171+
if (port) {
172+
port.postMessage({
173+
type: 'panel-message',
174+
data: message.data,
214175
});
215-
}
216-
} else if (name === 'devtools') {
217-
const orphan = orphansByTabId[tab].find(t => t.name === 'content-script');
218-
if (orphan) {
219-
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== orphan);
220176
} else {
221-
const newOrphan = { name, port };
222-
orphansByTabId[tab].push(newOrphan);
223-
port.onDisconnect.addListener(() => {
224-
if (__DEV__) console.warn('orphan content-script disconnected'); // eslint-disable-line no-console
225-
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== newOrphan);
226-
});
177+
console.error('No connection to content script for tab:', message.tabId);
227178
}
228179
}
180+
// Return true to indicate we'll respond asynchronously
181+
return true;
182+
});
183+
184+
// Handle messages from content script to panel
185+
chrome.runtime.onMessage.addListener((message, sender) => {
186+
if (sender.tab && message.type === 'content-to-panel') {
187+
// Broadcast to all extension pages
188+
chrome.runtime
189+
.sendMessage({
190+
tabId: sender.tab.id,
191+
data: message.data,
192+
})
193+
.catch(err => {
194+
// Ignore errors about receiving end not existing
195+
if (!err.message.includes('receiving end does not exist')) {
196+
console.error('Error sending message:', err);
197+
}
198+
});
199+
}
229200
});

src/shells/webextension/contentScript.js

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ import debugConnection from '../../utils/debugConnection';
1212
const contentScriptId = Math.random().toString(32).slice(2);
1313

1414
// proxy from main page to devtools (via the background page)
15-
const port = chrome.runtime.connect({ name: 'content-script' });
15+
let port = chrome.runtime.connect({ name: 'content-script' });
16+
17+
// Handle port disconnection and reconnection
18+
port.onDisconnect.addListener(() => {
19+
// Try to reconnect after a short delay
20+
setTimeout(() => {
21+
const newPort = chrome.runtime.connect({ name: 'content-script' });
22+
// Update port reference
23+
port = newPort;
24+
}, 100);
25+
});
1626

1727
const handshake = backendId => {
1828
function sendMessageToBackend(payload) {
@@ -77,6 +87,7 @@ const handshakeFailedTimeout = setTimeout(() => {
7787
}, 500 * 20);
7888

7989
let connected = false;
90+
let backendId;
8091

8192
window.addEventListener('message', function listener(message) {
8293
if (
@@ -85,7 +96,7 @@ window.addEventListener('message', function listener(message) {
8596
message.data.contentScriptId === contentScriptId
8697
) {
8798
debugConnection('[backend -> CONTENTSCRIPT]', message);
88-
const { backendId } = message.data;
99+
backendId = message.data.backendId;
89100
clearTimeout(handshakeFailedTimeout);
90101
clearInterval(pingInterval);
91102
debugConnection('[CONTENTSCRIPT -> backend]', 'backend:hello');
@@ -107,3 +118,67 @@ window.addEventListener('message', function listener(message) {
107118
setTimeout(() => window.removeEventListener('message', listener), 50000);
108119
}
109120
});
121+
122+
// Listen for messages from the background script
123+
chrome.runtime.onMessage.addListener((message, sender) => {
124+
if (message.type === 'panel-message') {
125+
// Forward to backend
126+
window.postMessage(
127+
{
128+
source: 'mobx-devtools-content-script',
129+
payload: message.data,
130+
contentScriptId,
131+
},
132+
'*',
133+
);
134+
}
135+
});
136+
137+
// Handle messages from backend
138+
window.addEventListener('message', evt => {
139+
if (evt.data.source === 'mobx-devtools-backend' && evt.data.contentScriptId === contentScriptId) {
140+
// Forward to panel via background script
141+
chrome.runtime
142+
.sendMessage({
143+
type: 'content-to-panel',
144+
data: evt.data.payload,
145+
})
146+
.catch(err => {
147+
// Ignore errors about receiving end not existing
148+
if (!err.message.includes('receiving end does not exist')) {
149+
console.error('Error sending message:', err);
150+
}
151+
});
152+
}
153+
});
154+
155+
// Add to port message listener
156+
port.onMessage.addListener(message => {
157+
if (message.type === 'panel-message') {
158+
window.postMessage(
159+
{
160+
source: 'mobx-devtools-content-script',
161+
payload: message.data,
162+
contentScriptId: contentScriptId,
163+
backendId: backendId,
164+
},
165+
'*',
166+
);
167+
}
168+
});
169+
170+
// Add this message handler for panel messages
171+
port.onMessage.addListener(message => {
172+
if (message.type === 'panel-message') {
173+
// Add these specific properties needed by the backend
174+
window.postMessage(
175+
{
176+
source: 'mobx-devtools-content-script',
177+
payload: message.data,
178+
contentScriptId: contentScriptId, // Make sure this matches the initial handshake
179+
backendId: backendId, // Make sure this is available from the handshake
180+
},
181+
'*',
182+
);
183+
}
184+
});
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<!doctype html>
22
<html>
3-
<head> </head>
3+
<head>
4+
<script src="panel-loader.js"></script>
5+
</head>
46
<body></body>
57
</html>

src/shells/webextension/injectGlobalHook.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1+
// Simply importing this file will install the global hook here
12
import installGlobalHook from '../../backend/utils/installGlobalHook';
23

3-
const script = document.createElement('script');
4-
script.textContent = `;(${installGlobalHook.toString()}(window))`;
5-
document.documentElement.appendChild(script);
6-
script.parentNode.removeChild(script);
7-
84
// if (__DEV__) {
95
window.addEventListener('test-open-mobx-devtools-window', () => {
106
console.log('test-open-mobx-devtools-window'); // eslint-disable-line no-console

src/shells/webextension/manifest.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"manifest_version": 2,
2+
"manifest_version": 3,
33
"name": "MobX Developer Tools",
44
"description": "Adds MobX debugging tools to the Chrome Developer Tools.",
55
"minimum_chrome_version": "44",
@@ -26,15 +26,20 @@
2626
}
2727
},
2828

29-
"content_security_policy": "script-src 'self'; object-src 'self'",
30-
"web_accessible_resources": ["main.html", "panel.html", "backend.js"],
29+
"web_accessible_resources": [
30+
{
31+
"resources": ["main.html", "panel.html", "backend.js"],
32+
"matches": ["<all_urls>"]
33+
}
34+
],
3135

3236
"background": {
33-
"scripts": ["background.js"],
34-
"persistent": false
37+
"service_worker": "background.js",
38+
"type": "module"
3539
},
3640

37-
"permissions": ["contextMenus", "storage", "file:///*", "http://*/*", "https://*/*"],
41+
"permissions": ["contextMenus", "storage", "scripting"],
42+
"host_permissions": ["<all_urls>"],
3843

3944
"browser_action": {
4045
"default_icon": {
@@ -51,4 +56,5 @@
5156
"run_at": "document_start"
5257
}
5358
]
59+
5460
}

src/shells/webextension/panel-loader.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@ function createPanelIfMobxLoaded() {
44
if (panelCreated) {
55
return;
66
}
7-
chrome.devtools.inspectedWindow.eval(
8-
'!!(Object.keys(window.__MOBX_DEVTOOLS_GLOBAL_HOOK__.collections).length)',
9-
pageHasMobx => {
7+
8+
chrome.scripting
9+
.executeScript({
10+
target: { tabId: chrome.devtools.inspectedWindow.tabId },
11+
func: () => {
12+
const pageHasMobx = !!Object.keys(window.__MOBX_DEVTOOLS_GLOBAL_HOOK__?.collections || {})
13+
.length;
14+
return pageHasMobx;
15+
},
16+
world: 'MAIN',
17+
})
18+
.then(([result]) => {
19+
const pageHasMobx = result?.result; // Access the result property
20+
1021
if (!pageHasMobx || panelCreated) {
1122
return;
1223
}
1324

1425
clearInterval(loadCheckInterval);
1526
panelCreated = true;
16-
chrome.devtools.panels.create('MobX', '', 'panel.html', () => {});
17-
},
18-
);
27+
chrome.devtools.panels.create('MobX', '', 'panel.html', panel => {});
28+
})
29+
.catch(err => {
30+
console.error('Failed to check MobX:', err);
31+
});
1932
}
2033

2134
chrome.devtools.network.onNavigated.addListener(createPanelIfMobxLoaded);

0 commit comments

Comments
 (0)