From 5d03f8fb389681915d139dc10bbb3683fb3fe9bf Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 14 Oct 2025 12:48:19 -0600 Subject: [PATCH] Fix bugs --- background.js | 240 ++++++++++++++++++++++++++++++++------------------ manifest.json | 7 -- 2 files changed, 153 insertions(+), 94 deletions(-) diff --git a/background.js b/background.js index a7fd41b..5cc289a 100644 --- a/background.js +++ b/background.js @@ -6,6 +6,7 @@ const executionContext = { }; let contextReadyPromise = null; let contextReadyResolver = null; +let preferWindowContext = true; function createReadyPromise() { contextReadyPromise = new Promise((resolve) => { @@ -23,62 +24,35 @@ function resolveReadyPromise() { } } -async function ensureDocumentContext() { - const hasOffscreenApi = Boolean(chrome.offscreen?.createDocument); - if (hasOffscreenApi) { - try { - if (chrome.offscreen.hasDocument) { - const exists = await chrome.offscreen.hasDocument(); - if (exists) { - executionContext.mode = "offscreen"; - if (!contextReadyPromise) { - contextReadyPromise = Promise.resolve(); - } - await contextReadyPromise; - return; - } - } - } catch (error) { - // ignore and fall back - } +function resetContextState() { + executionContext.windowId = null; + executionContext.tabId = null; + executionContext.mode = null; + contextReadyPromise = null; + contextReadyResolver = null; +} +async function closeOffscreenDocumentIfSupported() { + if (chrome.offscreen?.closeDocument) { try { - createReadyPromise(); - await chrome.offscreen.createDocument({ - url: chrome.runtime.getURL("offscreen.html"), - reasons: ["DOM_PARSER"], - justification: "Process O'Reilly pages to build EPUB downloads" - }); - executionContext.mode = "offscreen"; - await contextReadyPromise; - return; - } catch (error) { - const alreadyExists = String(error?.message || "").includes("already exists"); - if (!alreadyExists) { - console.warn("SafariBooks Downloader: Offscreen document failed, falling back to hidden window.", error); - } else { - executionContext.mode = "offscreen"; - if (!contextReadyPromise) { - contextReadyPromise = Promise.resolve(); - } - await contextReadyPromise; - return; - } + await chrome.offscreen.closeDocument(); + } catch (_) { + // ignore - document might already be gone } } +} - if (executionContext.mode === "window") { - if (executionContext.tabId != null) { - try { - await chrome.tabs.get(executionContext.tabId); - if (!contextReadyPromise) { - contextReadyPromise = Promise.resolve(); - } - await contextReadyPromise; - return; - } catch (_) { - executionContext.tabId = null; +async function ensureWindowContext() { + if (executionContext.mode === "window" && executionContext.tabId != null) { + try { + await chrome.tabs.get(executionContext.tabId); + if (!contextReadyPromise) { + contextReadyPromise = Promise.resolve(); } + await contextReadyPromise; + return; + } catch (_) { + resetContextState(); } } @@ -98,33 +72,124 @@ async function ensureDocumentContext() { await contextReadyPromise; } +async function ensureDocumentContext() { + if (!preferWindowContext) { + const hasOffscreenApi = Boolean(chrome.offscreen?.createDocument); + if (hasOffscreenApi) { + try { + if (chrome.offscreen.hasDocument) { + const exists = await chrome.offscreen.hasDocument(); + if (exists) { + executionContext.mode = "offscreen"; + if (!contextReadyPromise) { + contextReadyPromise = Promise.resolve(); + } + await contextReadyPromise; + return; + } + } + } catch (_) { + // ignore - we'll attempt to create a fresh document below + } + + try { + createReadyPromise(); + await chrome.offscreen.createDocument({ + url: chrome.runtime.getURL("offscreen.html"), + reasons: ["DOM_PARSER"], + justification: "Process O'Reilly pages to build EPUB downloads" + }); + executionContext.mode = "offscreen"; + await contextReadyPromise; + return; + } catch (error) { + const alreadyExists = String(error?.message || "").includes("already exists"); + if (alreadyExists) { + executionContext.mode = "offscreen"; + if (!contextReadyPromise) { + contextReadyPromise = Promise.resolve(); + } + await contextReadyPromise; + return; + } + console.warn("SafariBooks Downloader: Offscreen document unavailable, using hidden window instead.", error); + preferWindowContext = true; + await closeOffscreenDocumentIfSupported(); + resetContextState(); + } + } else { + preferWindowContext = true; + } + } + + await ensureWindowContext(); +} + +function cleanupWindowIfIdle() { + if (executionContext.mode === "window" && pendingResponses.size === 0 && executionContext.windowId != null) { + chrome.windows + .remove(executionContext.windowId) + .catch(() => {}) + .finally(() => { + resetContextState(); + }); + } +} + +function shouldRetryInWindow(error, pending) { + if (!error || pending.attempt >= 1) { + return false; + } + if (pending.contextMode !== "offscreen") { + return false; + } + const message = typeof error === "string" ? error : String(error); + return message.toLowerCase().includes("domparser"); +} + +async function startDownloadTask(bookId, options, sendResponse, attempt = 0) { + try { + await ensureDocumentContext(); + } catch (error) { + sendResponse({ ok: false, error: error?.message || String(error) }); + return; + } + + const requestId = crypto.randomUUID(); + const contextMode = executionContext.mode ?? (preferWindowContext ? "window" : "offscreen"); + pendingResponses.set(requestId, { sendResponse, bookId, options, attempt, contextMode }); + + try { + await chrome.runtime.sendMessage({ + type: "offscreen-download", + requestId, + bookId, + options + }); + } catch (error) { + const pending = pendingResponses.get(requestId); + if (pending) { + pendingResponses.delete(requestId); + pending.sendResponse({ ok: false, error: error?.message || String(error) }); + cleanupWindowIfIdle(); + } + } +} + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message?.type === "downloadBook") { - ensureDocumentContext() - .then(() => { - const requestId = crypto.randomUUID(); - pendingResponses.set(requestId, sendResponse); - chrome.runtime - .sendMessage({ - type: "offscreen-download", - requestId, - bookId: message.bookId, - options: { - theme: message.theme ?? "none", - kindle: Boolean(message.kindle) - } - }) - .catch((error) => { - const responder = pendingResponses.get(requestId); - if (responder) { - responder({ ok: false, error: error.message }); - pendingResponses.delete(requestId); - } - }); - }) - .catch((error) => { - sendResponse({ ok: false, error: error.message }); - }); + const bookId = message.bookId; + if (!bookId) { + sendResponse({ ok: false, error: "Missing book ID." }); + return false; + } + + const options = { + theme: message.theme ?? "none", + kindle: Boolean(message.kindle) + }; + + startDownloadTask(bookId, options, sendResponse); return true; } @@ -135,25 +200,26 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message?.type === "offscreen-download-complete") { const { requestId, ok, error } = message; - const responder = pendingResponses.get(requestId); - if (responder) { - responder({ ok, error }); - pendingResponses.delete(requestId); + const pending = pendingResponses.get(requestId); + if (!pending) { + return false; } - if (executionContext.mode === "window" && pendingResponses.size === 0 && executionContext.windowId != null) { - chrome.windows - .remove(executionContext.windowId) + if (!ok && shouldRetryInWindow(error, pending)) { + pendingResponses.delete(requestId); + preferWindowContext = true; + closeOffscreenDocumentIfSupported() .catch(() => {}) .finally(() => { - executionContext.windowId = null; - executionContext.tabId = null; - executionContext.mode = null; - contextReadyPromise = null; - contextReadyResolver = null; + resetContextState(); + startDownloadTask(pending.bookId, pending.options, pending.sendResponse, pending.attempt + 1); }); + return false; } + pendingResponses.delete(requestId); + pending.sendResponse({ ok, error }); + cleanupWindowIfIdle(); return false; } diff --git a/manifest.json b/manifest.json index 4691a7b..29d5eac 100644 --- a/manifest.json +++ b/manifest.json @@ -13,13 +13,6 @@ "service_worker": "background.js", "type": "module" }, - "offscreen": { - "page": "offscreen.html", - "reasons": [ - "DOM_PARSER" - ], - "justification": "Parse book HTML to build EPUB files in a DOM-enabled context" - }, "action": { "default_title": "SafariBooks Downloader", "default_popup": "index.html",