Theseus: add-on panels never leave their own page

The right-hand panel had neither a will-navigate nor a window-open
handler, so a link in a panel navigated the privileged view itself to
a remote page that kept sidebar-preload, and window.open made a bare
window with it; panel events were routed by the selected panel id, so
Aegis's state (every address, balances, WizardConnect sessions) would
then have reached that page. Both panels now open web links as tabs and
refuse to navigate away from file://, and events go only to a view that
has the add-on's own page loaded.
This commit is contained in:
Local Dev 2026-10-04 04:23:54 +02:00
parent 4ad8f4e401
commit bc1b5fc0f3

28
main.js
View file

@ -2620,10 +2620,14 @@ function initAddons() {
if (t.addonId !== addonId) continue;
try { t.view?.webContents?.send("addon-event", msg, payload); } catch {}
}
if (leftPanel && leftPanelLoadedId && leftPanelLoadedId.startsWith(addonId + ":")) {
// Delivered by what the view has actually loaded, not only by which
// panel was last selected — an add-on's state (addresses, balances)
// must never reach a document that is not that add-on's.
if (leftPanel && leftPanelLoadedId && leftPanelLoadedId.startsWith(addonId + ":") && addonIdForSender(leftPanel.webContents) === addonId) {
try { leftPanel.webContents.send("addon-event", msg, payload); } catch {}
}
if (!sidebar || !sidebarActivePanelId || !sidebarActivePanelId.startsWith(addonId + ":")) return;
if (addonIdForSender(sidebar.webContents) !== addonId) return;
try { sidebar.webContents.send("addon-event", msg, payload); } catch {}
},
hostRequire: (name) => require(name),
@ -2959,6 +2963,22 @@ function initAddons() {
// Given a webContents sender URL, work out which add-on folder it lives in.
// Used to gate storage IPC — a page hosted inside addons/<id>/ can only touch
// its own store.
// Add-on panels carry sidebar-preload. A link in a panel used to navigate
// the panel itself to a remote page (keeping that preload), and window.open
// made a bare BrowserWindow with it. Web links open as tabs instead, and a
// panel never leaves file://.
function lockPanelView(view) {
view.webContents.setWindowOpenHandler(({ url }) => {
if (url && /^(?:https?|bns):/i.test(url)) createTab(url);
return { action: "deny" };
});
view.webContents.on("will-navigate", (e) => {
const url = String(e.url || "");
if (/^file:/i.test(url)) return;
e.preventDefault();
if (/^(?:https?|bns):/i.test(url)) createTab(url);
});
}
function addonIdForSender(sender) {
try {
const u = new URL(sender.getURL());
@ -4400,6 +4420,7 @@ function createWindow() {
// panel loaded into this view (Aegis, Screenshot, etc.) picks up the
// brand scrollbar the moment its DOM is ready.
sidebar = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "sidebar-preload.js") } });
lockPanelView(sidebar);
win.contentView.addChildView(sidebar);
styleScrollbars(sidebar.webContents);
sidebar.setVisible(false);
@ -4433,10 +4454,7 @@ function createWindow() {
quickPanel.setVisible(false);
// Left add-on panels: same preload and IPC surface as the right sidebar.
leftPanel = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "sidebar-preload.js") } });
leftPanel.webContents.setWindowOpenHandler(({ url }) => {
if (url && /^(?:https?|bns):/i.test(url)) createTab(url);
return { action: "deny" };
});
lockPanelView(leftPanel);
try { leftPanel.setBackgroundColor(nativeTheme.shouldUseDarkColors ? "#0b0e14" : "#ffffff"); } catch {}
win.contentView.addChildView(leftPanel);
styleScrollbars(leftPanel.webContents);