62 lines
2.4 KiB
JavaScript
62 lines
2.4 KiB
JavaScript
// 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;
|
|
}
|