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 = const windowsApi =
typeof browser !== "undefined" ? browser.windows : chrome.windows; typeof browser !== "undefined" ? browser.windows : chrome.windows;
const tabsApi = typeof browser !== "undefined" ? browser.tabs : chrome.tabs; 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 } // Connected sites (in-memory, non-persisted): { "origin:address": true }
const connectedSites = {}; const connectedSites = {};
@@ -82,42 +84,81 @@ async function proxyRpc(method, params) {
return json.result; 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) { function requestApproval(origin, hostname) {
return new Promise((resolve) => { return new Promise((resolve) => {
const id = nextApprovalId++; const id = nextApprovalId++;
pendingApprovals[id] = { origin, hostname, resolve }; pendingApprovals[id] = { origin, hostname, resolve };
const popupUrl = runtime.getURL("src/popup/index.html?approval=" + id); if (actionApi && typeof actionApi.openPopup === "function") {
const popupWidth = 400; actionApi.setPopup({
const popupHeight = 500; popup: "src/popup/index.html?approval=" + id,
// 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;
}
}); });
}); 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) // Handle connection requests (eth_requestAccounts, wallet_requestPermissions)
async function handleConnectionRequest(origin) { async function handleConnectionRequest(origin) {
const s = await getState(); const s = await getState();
@@ -361,6 +402,7 @@ async function broadcastAccountsChanged() {
} }
delete pendingApprovals[id]; delete pendingApprovals[id];
} }
resetPopupUrl();
const s = await getState(); const s = await getState();
const activeAddress = await getActiveAddress(); const activeAddress = await getActiveAddress();
const allowed = activeAddress ? s.allowedSites[activeAddress] || [] : []; const allowed = activeAddress ? s.allowedSites[activeAddress] || [] : [];
@@ -451,6 +493,7 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
}); });
delete pendingApprovals[msg.id]; delete pendingApprovals[msg.id];
} }
resetPopupUrl();
return false; return false;
} }

View File

@@ -8,6 +8,9 @@ let approvalId = null;
function show(id) { function show(id) {
approvalId = 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) => { runtime.sendMessage({ type: "AUTISTMASK_GET_APPROVAL", id }, (details) => {
if (!details) { if (!details) {
window.close(); window.close();