Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/upgrade to manifest v3 #129

Merged
merged 18 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 57 additions & 86 deletions src/shells/webextension/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import debugConnection from '../../utils/debugConnection';
/*
* background.js
*
* Runs all the time and serves as a central message hub for panels, contentScript, backend
* Runs as a service worker serves as a central message hub for panels, contentScript, backend
*/

if (process.env.NODE_ENV === 'test') {
Expand Down Expand Up @@ -100,59 +100,27 @@ const installContentScript = tabId => {
if (err) {
handleInstallError(tabId, err);
} else {
chrome.tabs.executeScript(tabId, { file: '/contentScript.js' }, res => {
chrome.scripting.executeScript({ target: { tabId }, files: ['/backend.js'] }, res => {
const installError = chrome.runtime.lastError;
if (err || !res) handleInstallError(tabId, installError);
});
}
});
};

function doublePipe(one, two) {
if (!one.$i) {
one.$i = Math.random().toString(32).slice(2);
}
if (!two.$i) {
two.$i = Math.random().toString(32).slice(2);
}

debugConnection(`BACKGORUND: connect ${one.name} <-> ${two.name} [${one.$i} <-> ${two.$i}]`);

function lOne(message) {
debugConnection(`${one.name} -> BACKGORUND -> ${two.name} [${one.$i}-${two.$i}]`, message);
try {
two.postMessage(message);
} catch (e) {
if (__DEV__) console.error('Unexpected disconnect, error', e); // eslint-disable-line no-console
shutdown(); // eslint-disable-line no-use-before-define
}
}
function lTwo(message) {
debugConnection(`${two.name} -> BACKGORUND -> ${one.name} [${two.$i}-${one.$i}]`, message);
try {
one.postMessage(message);
} catch (e) {
if (__DEV__) console.error('Unexpected disconnect, error', e); // eslint-disable-line no-console
shutdown(); // eslint-disable-line no-use-before-define
}
}
one.onMessage.addListener(lOne);
two.onMessage.addListener(lTwo);
function shutdown() {
debugConnection(`SHUTDOWN ${one.name} <-> ${two.name} [${one.$i} <-> ${two.$i}]`);
one.onMessage.removeListener(lOne);
two.onMessage.removeListener(lTwo);
one.disconnect();
two.disconnect();
}
one.onDisconnect.addListener(shutdown);
two.onDisconnect.addListener(shutdown);
}

if (chrome.contextMenus) {
// electron doesn't support this api
chrome.contextMenus.onClicked.addListener((_, contentWindow) => {
openWindow(contentWindow.id);
chrome.contextMenus.onClicked.addListener((info, tab) => {
console.log('Context menu clicked', info, tab);
if (info.menuItemId === 'mobx-devtools') {
try {
console.log('Attempting to open window for tab', tab.id);
window.contentTabId = tab.id;
installContentScript(tab.id);
openWindow(tab.id);
} catch (err) {
console.error('Error opening devtools window:', err);
}
}
});
}

Expand All @@ -176,54 +144,57 @@ if (chrome.browserAction) {
});
}

chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'mobx-devtools',
title: 'Open Mobx DevTools',
contexts: ['all'],
});
// Keep service worker alive
chrome.runtime.onConnect.addListener(port => {
console.log('Service worker connected to port:', port.name);
});

// Create a long-lived connection for the content script
let contentScriptPorts = new Map();

chrome.runtime.onConnect.addListener(port => {
let tab = null;
let name = null;
if (isNumeric(port.name)) {
tab = port.name;
name = 'devtools';
installContentScript(+port.name);
} else {
tab = port.sender.tab.id;
name = 'content-script';
}
if (port.name === 'content-script') {
const tabId = port.sender.tab.id;
contentScriptPorts.set(tabId, port);

if (!orphansByTabId[tab]) {
orphansByTabId[tab] = [];
port.onDisconnect.addListener(() => {
contentScriptPorts.delete(tabId);
});
}
});

if (name === 'content-script') {
const orphan = orphansByTabId[tab].find(t => t.name === 'devtools');
if (orphan) {
doublePipe(orphan.port, port);
coolsoftwaretyler marked this conversation as resolved.
Show resolved Hide resolved
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== orphan);
} else {
const newOrphan = { name, port };
orphansByTabId[tab].push(newOrphan);
port.onDisconnect.addListener(() => {
if (__DEV__) console.warn('orphan devtools disconnected'); // eslint-disable-line no-console
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== newOrphan);
// Handle messages from panel
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'panel-to-backend') {
// Use the existing port to send to content script
const port = contentScriptPorts.get(message.tabId);
if (port) {
port.postMessage({
type: 'panel-message',
data: message.data,
});
}
} else if (name === 'devtools') {
const orphan = orphansByTabId[tab].find(t => t.name === 'content-script');
if (orphan) {
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== orphan);
} else {
const newOrphan = { name, port };
orphansByTabId[tab].push(newOrphan);
port.onDisconnect.addListener(() => {
if (__DEV__) console.warn('orphan content-script disconnected'); // eslint-disable-line no-console
orphansByTabId[tab] = orphansByTabId[tab].filter(t => t !== newOrphan);
});
console.error('No connection to content script for tab:', message.tabId);
}
}
// Return true to indicate we'll respond asynchronously
return true;
});

// Handle messages from content script to panel
chrome.runtime.onMessage.addListener((message, sender) => {
if (sender.tab && message.type === 'content-to-panel') {
// Broadcast to all extension pages
chrome.runtime
.sendMessage({
tabId: sender.tab.id,
data: message.data,
})
.catch(err => {
// Ignore errors about receiving end not existing
if (!err.message.includes('receiving end does not exist')) {
console.error('Error sending message:', err);
}
});
}
});
79 changes: 77 additions & 2 deletions src/shells/webextension/contentScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ import debugConnection from '../../utils/debugConnection';
const contentScriptId = Math.random().toString(32).slice(2);

// proxy from main page to devtools (via the background page)
const port = chrome.runtime.connect({ name: 'content-script' });
let port = chrome.runtime.connect({ name: 'content-script' });

// Handle port disconnection and reconnection
port.onDisconnect.addListener(() => {
// Try to reconnect after a short delay
setTimeout(() => {
const newPort = chrome.runtime.connect({ name: 'content-script' });
// Update port reference
port = newPort;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should't we also listen for onDisconnect on this newPort?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe! I am not 100% sure how all of this is wired up. What should we be doing on port disconnection?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I just see that you try reconnecting after the first disconnection here, thought it would make sense to do the same for the next disconnections

}, 100);
});

const handshake = backendId => {
function sendMessageToBackend(payload) {
Expand Down Expand Up @@ -77,6 +87,7 @@ const handshakeFailedTimeout = setTimeout(() => {
}, 500 * 20);

let connected = false;
let backendId;

window.addEventListener('message', function listener(message) {
if (
Expand All @@ -85,7 +96,7 @@ window.addEventListener('message', function listener(message) {
message.data.contentScriptId === contentScriptId
) {
debugConnection('[backend -> CONTENTSCRIPT]', message);
const { backendId } = message.data;
backendId = message.data.backendId;
clearTimeout(handshakeFailedTimeout);
clearInterval(pingInterval);
debugConnection('[CONTENTSCRIPT -> backend]', 'backend:hello');
Expand All @@ -107,3 +118,67 @@ window.addEventListener('message', function listener(message) {
setTimeout(() => window.removeEventListener('message', listener), 50000);
}
});

// Listen for messages from the background script
chrome.runtime.onMessage.addListener((message, sender) => {
if (message.type === 'panel-message') {
// Forward to backend
window.postMessage(
{
source: 'mobx-devtools-content-script',
payload: message.data,
contentScriptId,
},
'*',
);
}
});

// Handle messages from backend
window.addEventListener('message', evt => {
if (evt.data.source === 'mobx-devtools-backend' && evt.data.contentScriptId === contentScriptId) {
// Forward to panel via background script
chrome.runtime
.sendMessage({
type: 'content-to-panel',
data: evt.data.payload,
})
.catch(err => {
// Ignore errors about receiving end not existing
if (!err.message.includes('receiving end does not exist')) {
console.error('Error sending message:', err);
}
});
}
});

// Add to port message listener
port.onMessage.addListener(message => {
if (message.type === 'panel-message') {
window.postMessage(
{
source: 'mobx-devtools-content-script',
payload: message.data,
contentScriptId: contentScriptId,
backendId: backendId,
},
'*',
);
}
});

// Add this message handler for panel messages
port.onMessage.addListener(message => {
if (message.type === 'panel-message') {
// Add these specific properties needed by the backend
window.postMessage(
{
source: 'mobx-devtools-content-script',
payload: message.data,
contentScriptId: contentScriptId, // Make sure this matches the initial handshake
backendId: backendId, // Make sure this is available from the handshake
},
'*',
);
}
});
4 changes: 3 additions & 1 deletion src/shells/webextension/html/panel-loader.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<!doctype html>
<html>
<head> </head>
<head>
<script src="panel-loader.js"></script>
</head>
<body></body>
</html>
6 changes: 1 addition & 5 deletions src/shells/webextension/injectGlobalHook.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// Simply importing this file will install the global hook here
import installGlobalHook from '../../backend/utils/installGlobalHook';

const script = document.createElement('script');
script.textContent = `;(${installGlobalHook.toString()}(window))`;
document.documentElement.appendChild(script);
script.parentNode.removeChild(script);

// if (__DEV__) {
window.addEventListener('test-open-mobx-devtools-window', () => {
console.log('test-open-mobx-devtools-window'); // eslint-disable-line no-console
Expand Down
18 changes: 12 additions & 6 deletions src/shells/webextension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "MobX Developer Tools",
"description": "Adds MobX debugging tools to the Chrome Developer Tools.",
"minimum_chrome_version": "44",
Expand All @@ -26,15 +26,20 @@
}
},

"content_security_policy": "script-src 'self'; object-src 'self'",
"web_accessible_resources": ["main.html", "panel.html", "backend.js"],
"web_accessible_resources": [
{
"resources": ["main.html", "panel.html", "backend.js"],
"matches": ["<all_urls>"]
}
],

"background": {
"scripts": ["background.js"],
"persistent": false
"service_worker": "background.js",
"type": "module"
},

"permissions": ["contextMenus", "storage", "file:///*", "http://*/*", "https://*/*"],
"permissions": ["contextMenus", "storage", "scripting"],
"host_permissions": ["<all_urls>"],

"browser_action": {
"default_icon": {
Expand All @@ -51,4 +56,5 @@
"run_at": "document_start"
}
]

}
25 changes: 19 additions & 6 deletions src/shells/webextension/panel-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@ function createPanelIfMobxLoaded() {
if (panelCreated) {
return;
}
chrome.devtools.inspectedWindow.eval(
'!!(Object.keys(window.__MOBX_DEVTOOLS_GLOBAL_HOOK__.collections).length)',
pageHasMobx => {

chrome.scripting
.executeScript({
target: { tabId: chrome.devtools.inspectedWindow.tabId },
func: () => {
const pageHasMobx = !!Object.keys(window.__MOBX_DEVTOOLS_GLOBAL_HOOK__?.collections || {})
.length;
return pageHasMobx;
},
world: 'MAIN',
})
.then(([result]) => {
const pageHasMobx = result?.result; // Access the result property

if (!pageHasMobx || panelCreated) {
return;
}

clearInterval(loadCheckInterval);
panelCreated = true;
chrome.devtools.panels.create('MobX', '', 'panel.html', () => {});
},
);
chrome.devtools.panels.create('MobX', '', 'panel.html', panel => {});
})
.catch(err => {
console.error('Failed to check MobX:', err);
});
}

chrome.devtools.network.onNavigated.addListener(createPanelIfMobxLoaded);
Expand Down
Loading
Loading