feat: add advanced context actions and optional translation
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user