From a590cfc3adefaffc4bd59df887fcdeafaf1b1038 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 26 Feb 2026 12:16:41 +0700 Subject: [PATCH] Show approval in browser-action popup instead of a separate window Use action.openPopup() to show the approval in the toolbar popup, which is anchored to the browser window and cannot trigger a macOS Space switch. Falls back to a separate window if openPopup() is unavailable. A port connection detects when the popup is dismissed without a response, and the popup URL is reset to the main UI after every approval resolution. --- src/background/index.js | 97 ++++++++++++++++++++++++++----------- src/popup/views/approval.js | 3 ++ 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index fc1e330..503d378 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -18,6 +18,8 @@ const runtime = const windowsApi = typeof browser !== "undefined" ? browser.windows : chrome.windows; const tabsApi = typeof browser !== "undefined" ? browser.tabs : chrome.tabs; +const actionApi = + typeof browser !== "undefined" ? browser.browserAction : chrome.action; // Connected sites (in-memory, non-persisted): { "origin:address": true } const connectedSites = {}; @@ -82,42 +84,81 @@ async function proxyRpc(method, params) { return json.result; } -// Open an approval popup and return a promise that resolves with the user decision +function resetPopupUrl() { + if (actionApi && typeof actionApi.setPopup === "function") { + actionApi.setPopup({ popup: "src/popup/index.html" }); + } +} + +// Fallback: open approval in a separate window (used when openPopup is unavailable) +function openApprovalWindow(id) { + const popupUrl = runtime.getURL("src/popup/index.html?approval=" + id); + const popupWidth = 400; + const popupHeight = 500; + + windowsApi.getLastFocused((currentWin) => { + const opts = { + url: popupUrl, + type: "popup", + width: popupWidth, + height: popupHeight, + }; + if (currentWin) { + opts.left = Math.round( + currentWin.left + (currentWin.width - popupWidth) / 2, + ); + opts.top = Math.round( + currentWin.top + (currentWin.height - popupHeight) / 2, + ); + } + windowsApi.create(opts, (win) => { + if (win) { + pendingApprovals[id].windowId = win.id; + } + }); + }); +} + +// Open an approval popup and return a promise that resolves with the user decision. +// Prefers the browser-action popup (anchored to toolbar, no macOS Space switch). function requestApproval(origin, hostname) { return new Promise((resolve) => { const id = nextApprovalId++; pendingApprovals[id] = { origin, hostname, resolve }; - const popupUrl = runtime.getURL("src/popup/index.html?approval=" + id); - const popupWidth = 400; - const popupHeight = 500; - - // Center the popup over the focused window so macOS keeps it - // on the same Space instead of switching desktops. - windowsApi.getLastFocused((currentWin) => { - const opts = { - url: popupUrl, - type: "popup", - width: popupWidth, - height: popupHeight, - }; - if (currentWin) { - opts.left = Math.round( - currentWin.left + (currentWin.width - popupWidth) / 2, - ); - opts.top = Math.round( - currentWin.top + (currentWin.height - popupHeight) / 2, - ); - } - windowsApi.create(opts, (win) => { - if (win) { - pendingApprovals[id].windowId = win.id; - } + if (actionApi && typeof actionApi.openPopup === "function") { + actionApi.setPopup({ + popup: "src/popup/index.html?approval=" + id, }); - }); + try { + const result = actionApi.openPopup(); + if (result && typeof result.catch === "function") { + result.catch(() => openApprovalWindow(id)); + } + } catch { + openApprovalWindow(id); + } + } else { + openApprovalWindow(id); + } }); } +// Detect when an approval popup (browser-action) closes without a response +runtime.onConnect.addListener((port) => { + if (port.name.startsWith("approval:")) { + const id = parseInt(port.name.split(":")[1], 10); + port.onDisconnect.addListener(() => { + const approval = pendingApprovals[id]; + if (approval) { + approval.resolve({ approved: false, remember: false }); + delete pendingApprovals[id]; + } + resetPopupUrl(); + }); + } +}); + // Handle connection requests (eth_requestAccounts, wallet_requestPermissions) async function handleConnectionRequest(origin) { const s = await getState(); @@ -361,6 +402,7 @@ async function broadcastAccountsChanged() { } delete pendingApprovals[id]; } + resetPopupUrl(); const s = await getState(); const activeAddress = await getActiveAddress(); const allowed = activeAddress ? s.allowedSites[activeAddress] || [] : []; @@ -451,6 +493,7 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { }); delete pendingApprovals[msg.id]; } + resetPopupUrl(); return false; } diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 47eb361..1ddecf8 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -8,6 +8,9 @@ let approvalId = null; function show(id) { approvalId = id; + // Connect a port so the background detects if the popup closes + // without an explicit response (e.g. user clicks away). + runtime.connect({ name: "approval:" + id }); runtime.sendMessage({ type: "AUTISTMASK_GET_APPROVAL", id }, (details) => { if (!details) { window.close();