This commit is contained in:
Joey Yakimowich-Payne 2025-10-14 12:48:19 -06:00
commit 5d03f8fb38
2 changed files with 153 additions and 94 deletions

View file

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

View file

@ -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",