feat: add advanced context actions and optional translation

This commit is contained in:
golem
2026-10-05 15:38:16 -06:00
parent 33c4dad00f
commit 5308932bf1
34 changed files with 1579 additions and 159 deletions
+61
View File
@@ -0,0 +1,61 @@
// Pending source content exists only during the initial handshake. The dialog
// owns a structured clone after receipt, independent of the event page's life.
export async function showActionDialog(browser, payload) {
const id = crypto.randomUUID();
const url = browser.runtime.getURL('dialog/action.html') + '?request=' + id;
let tabId;
let port;
let finished = false;
let resolveOpened;
const opened = new Promise(resolve => { resolveOpened = resolve; });
let timer;
let resolveDone, rejectDone;
const done = new Promise((resolve, reject) => { resolveDone = resolve; rejectDone = reject; });
// Attach rejection handling immediately, including when window creation stalls.
void done.catch(() => {});
function finish(error) {
if (finished) return;
finished = true;
clearTimeout(timer);
payload = null;
browser.runtime.onConnect.removeListener(onConnect);
browser.tabs.onRemoved.removeListener(onRemoved);
if (port) {
port.onMessage.removeListener(onMessage);
port.onDisconnect.removeListener(onDisconnect);
port.disconnect();
}
if (error) rejectDone(error); else resolveDone();
}
function onMessage(message) {
if (message?.type === 'received') finish();
}
function onDisconnect() { finish(); }
function onRemoved(removedId) { if (removedId === tabId) finish(); }
async function onConnect(candidate) {
if (candidate.name !== 'action-dialog:' + id) return;
await opened;
if (finished || port || candidate.sender?.id !== browser.runtime.id ||
candidate.sender?.url !== url || candidate.sender?.tab?.id !== tabId) return;
port = candidate;
port.onMessage.addListener(onMessage);
port.onDisconnect.addListener(onDisconnect);
try { port.postMessage({ type: 'initialize', payload }); }
catch { finish(new Error('Could not initialize action dialog')); }
}
browser.runtime.onConnect.addListener(onConnect);
browser.tabs.onRemoved.addListener(onRemoved);
timer = setTimeout(() => finish(new Error('Action dialog initialization timed out')), 15000);
try {
try {
const win = await browser.windows.create({ url, type: 'popup', width: 480, height: 360 });
tabId = win.tabs?.[0]?.id;
if (typeof tabId !== 'number') throw new Error('Missing dialog tab');
} catch {
const tab = await browser.tabs.create({ url });
tabId = tab.id;
}
} catch (error) { finish(error); }
finally { resolveOpened(); }
return done;
}