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 contextReadyPromise = null;
|
||||||
let contextReadyResolver = null;
|
let contextReadyResolver = null;
|
||||||
|
let preferWindowContext = true;
|
||||||
|
|
||||||
function createReadyPromise() {
|
function createReadyPromise() {
|
||||||
contextReadyPromise = new Promise((resolve) => {
|
contextReadyPromise = new Promise((resolve) => {
|
||||||
|
|
@ -23,62 +24,35 @@ function resolveReadyPromise() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureDocumentContext() {
|
function resetContextState() {
|
||||||
const hasOffscreenApi = Boolean(chrome.offscreen?.createDocument);
|
executionContext.windowId = null;
|
||||||
if (hasOffscreenApi) {
|
executionContext.tabId = null;
|
||||||
try {
|
executionContext.mode = null;
|
||||||
if (chrome.offscreen.hasDocument) {
|
contextReadyPromise = null;
|
||||||
const exists = await chrome.offscreen.hasDocument();
|
contextReadyResolver = null;
|
||||||
if (exists) {
|
}
|
||||||
executionContext.mode = "offscreen";
|
|
||||||
if (!contextReadyPromise) {
|
|
||||||
contextReadyPromise = Promise.resolve();
|
|
||||||
}
|
|
||||||
await contextReadyPromise;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// ignore and fall back
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function closeOffscreenDocumentIfSupported() {
|
||||||
|
if (chrome.offscreen?.closeDocument) {
|
||||||
try {
|
try {
|
||||||
createReadyPromise();
|
await chrome.offscreen.closeDocument();
|
||||||
await chrome.offscreen.createDocument({
|
} catch (_) {
|
||||||
url: chrome.runtime.getURL("offscreen.html"),
|
// ignore - document might already be gone
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (executionContext.mode === "window") {
|
async function ensureWindowContext() {
|
||||||
if (executionContext.tabId != null) {
|
if (executionContext.mode === "window" && executionContext.tabId != null) {
|
||||||
try {
|
try {
|
||||||
await chrome.tabs.get(executionContext.tabId);
|
await chrome.tabs.get(executionContext.tabId);
|
||||||
if (!contextReadyPromise) {
|
if (!contextReadyPromise) {
|
||||||
contextReadyPromise = Promise.resolve();
|
contextReadyPromise = Promise.resolve();
|
||||||
}
|
|
||||||
await contextReadyPromise;
|
|
||||||
return;
|
|
||||||
} catch (_) {
|
|
||||||
executionContext.tabId = null;
|
|
||||||
}
|
}
|
||||||
|
await contextReadyPromise;
|
||||||
|
return;
|
||||||
|
} catch (_) {
|
||||||
|
resetContextState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,33 +72,124 @@ async function ensureDocumentContext() {
|
||||||
await contextReadyPromise;
|
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) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
if (message?.type === "downloadBook") {
|
if (message?.type === "downloadBook") {
|
||||||
ensureDocumentContext()
|
const bookId = message.bookId;
|
||||||
.then(() => {
|
if (!bookId) {
|
||||||
const requestId = crypto.randomUUID();
|
sendResponse({ ok: false, error: "Missing book ID." });
|
||||||
pendingResponses.set(requestId, sendResponse);
|
return false;
|
||||||
chrome.runtime
|
}
|
||||||
.sendMessage({
|
|
||||||
type: "offscreen-download",
|
const options = {
|
||||||
requestId,
|
theme: message.theme ?? "none",
|
||||||
bookId: message.bookId,
|
kindle: Boolean(message.kindle)
|
||||||
options: {
|
};
|
||||||
theme: message.theme ?? "none",
|
|
||||||
kindle: Boolean(message.kindle)
|
startDownloadTask(bookId, options, sendResponse);
|
||||||
}
|
|
||||||
})
|
|
||||||
.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 });
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,25 +200,26 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
|
||||||
if (message?.type === "offscreen-download-complete") {
|
if (message?.type === "offscreen-download-complete") {
|
||||||
const { requestId, ok, error } = message;
|
const { requestId, ok, error } = message;
|
||||||
const responder = pendingResponses.get(requestId);
|
const pending = pendingResponses.get(requestId);
|
||||||
if (responder) {
|
if (!pending) {
|
||||||
responder({ ok, error });
|
return false;
|
||||||
pendingResponses.delete(requestId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (executionContext.mode === "window" && pendingResponses.size === 0 && executionContext.windowId != null) {
|
if (!ok && shouldRetryInWindow(error, pending)) {
|
||||||
chrome.windows
|
pendingResponses.delete(requestId);
|
||||||
.remove(executionContext.windowId)
|
preferWindowContext = true;
|
||||||
|
closeOffscreenDocumentIfSupported()
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
executionContext.windowId = null;
|
resetContextState();
|
||||||
executionContext.tabId = null;
|
startDownloadTask(pending.bookId, pending.options, pending.sendResponse, pending.attempt + 1);
|
||||||
executionContext.mode = null;
|
|
||||||
contextReadyPromise = null;
|
|
||||||
contextReadyResolver = null;
|
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingResponses.delete(requestId);
|
||||||
|
pending.sendResponse({ ok, error });
|
||||||
|
cleanupWindowIfIdle();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,6 @@
|
||||||
"service_worker": "background.js",
|
"service_worker": "background.js",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
},
|
},
|
||||||
"offscreen": {
|
|
||||||
"page": "offscreen.html",
|
|
||||||
"reasons": [
|
|
||||||
"DOM_PARSER"
|
|
||||||
],
|
|
||||||
"justification": "Parse book HTML to build EPUB files in a DOM-enabled context"
|
|
||||||
},
|
|
||||||
"action": {
|
"action": {
|
||||||
"default_title": "SafariBooks Downloader",
|
"default_title": "SafariBooks Downloader",
|
||||||
"default_popup": "index.html",
|
"default_popup": "index.html",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue