Fix bugs
This commit is contained in:
parent
66a45d11fb
commit
5d03f8fb38
2 changed files with 153 additions and 94 deletions
240
background.js
240
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue