Show approval in browser-action popup instead of a separate window
Some checks failed
check / check (push) Has been cancelled

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.
This commit is contained in:
2026-02-26 12:16:41 +07:00
parent dce3b4aa08
commit a590cfc3ad
2 changed files with 73 additions and 27 deletions

View File

@@ -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,18 +84,18 @@ async function proxyRpc(method, params) {
return json.result;
}
// Open an approval popup and return a promise that resolves with the user decision
function requestApproval(origin, hostname) {
return new Promise((resolve) => {
const id = nextApprovalId++;
pendingApprovals[id] = { origin, hostname, resolve };
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;
// 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,
@@ -115,9 +117,48 @@ function requestApproval(origin, hostname) {
}
});
});
}
// 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 };
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;
}

View File

@@ -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();