// 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; }