diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index 0e2713fd..05d09a27 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -7,6 +7,7 @@ export default defineBackground({ type: "module", main() { var tabInfo = new Map(); + const BIB_EXPORT_TIMEOUT_MS = 5000; /* Show/hide import button for all tabs (when add-on is loaded). @@ -145,6 +146,9 @@ export default defineBackground({ async function sendBibEntryHttp(bibtex) { const baseUrl = await getBaseUrl(); + await browser.runtime.sendMessage({ + popupLog: `Trying JabRef HTTP endpoint at ${baseUrl}`, + }); const health = await fetch(baseUrl, { method: "GET", cache: "no-store" }); if (!(health.ok || health.status === 404)) { @@ -161,13 +165,23 @@ export default defineBackground({ const body = await resp.text().catch(() => ""); throw new Error(`HTTP ${resp.status}${body ? `: ${body}` : ""}`); } + + await browser.runtime.sendMessage({ + popupLog: "JabRef accepted data over HTTP", + }); } async function sendBibEntryNative(bibtex) { + await browser.runtime.sendMessage({ + popupLog: "Trying native messaging to reach JabRef", + }); const response = await browser.runtime.sendNativeMessage("org.jabref.jabref", { text: bibtex, }); if (response?.message === "ok") { + await browser.runtime.sendMessage({ + popupLog: "JabRef accepted data over native messaging", + }); return; } @@ -190,13 +204,22 @@ export default defineBackground({ try { await sendBibEntryHttp(bibtex); + await browser.runtime.sendMessage({ + popupLog: "Send to JabRef finished", + }); await browser.runtime.sendMessage({ popupClose: "close" }); return; } catch (httpError) { console.warn("JabRef: HTTP send failed, falling back to native messaging", httpError); + await browser.runtime.sendMessage({ + popupLog: `HTTP send failed, falling back to native messaging: ${httpError}`, + }); } await sendBibEntryNative(bibtex); + await browser.runtime.sendMessage({ + popupLog: "Send to JabRef finished", + }); await browser.runtime.sendMessage({ popupClose: "close" }); } @@ -314,6 +337,18 @@ export default defineBackground({ return cfg.exportMode || "bibtex"; } + async function raceWithTimeout(promise, timeoutMs, label) { + return Promise.race([ + promise, + new Promise((_, reject) => { + setTimeout( + () => reject(new Error(`${label} timed out after ${timeoutMs} ms`)), + timeoutMs, + ); + }), + ]); + } + async function prepareForExport(items) { const { takeSnapshots } = await browser.storage.sync.get({ takeSnapshots: false }); @@ -395,11 +430,24 @@ export default defineBackground({ return; } const { url, items } = message; + await browser.runtime.sendMessage({ + popupLog: `Translator returned ${items.length} item(s) for ${url}`, + }); const conversionMode = await getConversionMode(); await prepareForExport(items); await browser.runtime.sendMessage({ onConvertToBibtex: "convertStarted" }); - const bib = await exportItems(items, conversionMode); + await browser.runtime.sendMessage({ + popupLog: `Starting BibTeX export in background for ${items.length} item(s)`, + }); + const bib = await raceWithTimeout( + exportItems(items, conversionMode), + BIB_EXPORT_TIMEOUT_MS, + "BibTeX export", + ); console.debug("JabRef: Exported BibTeX: %o", bib); + await browser.runtime.sendMessage({ + popupLog: `BibTeX export finished using mode ${conversionMode}`, + }); await sendBibTexToJabRef(bib); } else if (message.eval) { console.debug( @@ -419,6 +467,11 @@ export default defineBackground({ } } catch (e) { console.error("JabRef: Error handling message in background.js", e); + try { + await browser.runtime.sendMessage({ + popupLog: `Background error: ${e instanceof Error ? e.message : String(e)}`, + }); + } catch {} throw e; } }); diff --git a/src/entrypoints/content/index.js b/src/entrypoints/content/index.js index fc21230c..8252dbbe 100644 --- a/src/entrypoints/content/index.js +++ b/src/entrypoints/content/index.js @@ -6,6 +6,11 @@ export default defineContentScript({ matches: [], async main() { + if (globalThis.__JABREF_CONTENT_SCRIPT_INITIALIZED__) { + console.debug("[contentScript] already initialized"); + return; + } + globalThis.__JABREF_CONTENT_SCRIPT_INITIALIZED__ = true; console.debug("[contentScript] started"); browser.runtime.onMessage.addListener(async (msg, _sender, _sendResponse) => { @@ -76,7 +81,17 @@ export default defineContentScript({ ); const result = await translateEngine.translate(document, translators); console.debug("Content script obtained translation result %o", result); - await browser.runtime.sendMessage({ type: "offscreenResult", url, items: result.items }); + console.debug( + "Content script sending offscreenResult with %o item(s) for %o", + result.items?.length ?? 0, + url, + ); + const response = await browser.runtime.sendMessage({ + type: "offscreenResult", + url, + items: result.items, + }); + console.debug("Content script received background ack for offscreenResult %o", response); }); }, }); diff --git a/src/entrypoints/options/index.html b/src/entrypoints/options/index.html index e5c3f361..4e2c68d2 100644 --- a/src/entrypoints/options/index.html +++ b/src/entrypoints/options/index.html @@ -86,6 +86,18 @@

Connection Status

Testing connection... +
+
+

Diagnostics

+
+
+ +
+
+ +
No test run yet.
+
+
diff --git a/src/entrypoints/options/main.js b/src/entrypoints/options/main.js index f31775ce..ef764a96 100644 --- a/src/entrypoints/options/main.js +++ b/src/entrypoints/options/main.js @@ -6,6 +6,50 @@ var ExportMode = Object.freeze({ }); const DEFAULT_PORT = 23119; +const NATIVE_MESSAGE_TIMEOUT_MS = 5000; +const NATIVE_MESSAGE_TEST_LOG_TAG = "JBE_NATIVE_TEST"; + +function raceWithTimeout(promise, timeoutMs, label) { + return Promise.race([ + promise, + new Promise((_, reject) => { + setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs} ms`)), timeoutMs); + }), + ]); +} + +function formatError(error) { + if (error instanceof Error) { + return error.message; + } + return String(error); +} + +async function sendNativeValidation() { + const requestId = `${NATIVE_MESSAGE_TEST_LOG_TAG}-${Date.now()}`; + console.log(`${NATIVE_MESSAGE_TEST_LOG_TAG} sending requestId=${requestId}`); + return raceWithTimeout( + browser.runtime.sendNativeMessage("org.jabref.jabref", { + status: "validate", + requestId, + }), + NATIVE_MESSAGE_TIMEOUT_MS, + "Native messaging", + ); +} + +function renderNativeStatus(statusElement, response) { + if (response.message === "jarNotFound") { + statusElement.setAttribute("class", "alert-error"); + statusElement.textContent = `Unable to locate JabRef at: ${response.path}`; + } else if (response.message === "jarFound") { + statusElement.setAttribute("class", "alert-positive"); + statusElement.textContent = "Communication to JabRef successful!"; + } else { + statusElement.setAttribute("class", "alert-error"); + statusElement.textContent = `Unexpected response: ${response.message}`; + } +} async function connectToJabRef(port) { const base = `http://localhost:${port}/`; @@ -24,25 +68,14 @@ async function connectToJabRef(port) { function checkConnections({ httpPort }) { let status = document.getElementById("connectionStatusNative"); - browser.runtime - .sendNativeMessage("org.jabref.jabref", { - status: "validate", - }) + status.textContent = "Testing connection..."; + sendNativeValidation() .then((response) => { - if (response.message === "jarNotFound") { - status.setAttribute("class", "alert-error"); - status.textContent = "Unable to locate JabRef at:
" + response.path; - } else if (response.message === "jarFound") { - status.setAttribute("class", "alert-positive"); - status.textContent = "Communication to JabRef successful!"; - } else { - status.setAttribute("class", "alert-error"); - status.innerHTML = "Unexpected response:
" + response.message; - } + renderNativeStatus(status, response); }) .catch((error) => { status.setAttribute("class", "alert-error"); - status.textContent = error.message; + status.textContent = formatError(error); }); let httpStatus = document.getElementById("connectionStatusHttp"); @@ -71,6 +104,25 @@ function checkConnections({ httpPort }) { }); } +function initializeNativeMessagingDiagnosticButton() { + const diagnosticButton = document.getElementById("testNativeMessage"); + const diagnosticResult = document.getElementById("nativeMessageResult"); + + diagnosticButton.addEventListener("click", async () => { + diagnosticButton.disabled = true; + diagnosticResult.textContent = "Running native messaging diagnostic..."; + + try { + const response = await sendNativeValidation(); + diagnosticResult.textContent = JSON.stringify(response, null, 2); + } catch (error) { + diagnosticResult.textContent = formatError(error); + } finally { + diagnosticButton.disabled = false; + } + }); +} + async function restoreOptions() { const options = await browser.storage.sync.get({ exportMode: ExportMode.BibTeX, @@ -126,6 +178,7 @@ function saveOptions() { async function init() { const options = await restoreOptions(); checkConnections(options); + initializeNativeMessagingDiagnosticButton(); document.getElementById("exportBiblatex").addEventListener("change", () => saveOptions()); document.getElementById("exportBibtex").addEventListener("change", () => saveOptions()); diff --git a/src/entrypoints/options/style.css b/src/entrypoints/options/style.css index 34506f4f..c3f29445 100644 --- a/src/entrypoints/options/style.css +++ b/src/entrypoints/options/style.css @@ -6,6 +6,11 @@ body { min-width: 500px; } +button { + font: inherit; + padding: 4px 10px; +} + label { cursor: default; margin-top: 1px; @@ -38,6 +43,30 @@ label { text-align: center; } +.diagnostics-cell { + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-start; +} + +#nativeMessageResult { + margin: 0; + padding: 8px; + width: min(100%, 42rem); + min-height: 6rem; + white-space: pre-wrap; + overflow-wrap: anywhere; + background: #f5f5f7; + border: 1px solid #d7d7db; + text-shadow: none; + font: + 12px/1.4 ui-monospace, + SFMono-Regular, + Menlo, + monospace; +} + input[type="radio"] { vertical-align: middle; -moz-appearance: none; diff --git a/src/entrypoints/popup/main.js b/src/entrypoints/popup/main.js index 7ca813e4..a51334d8 100644 --- a/src/entrypoints/popup/main.js +++ b/src/entrypoints/popup/main.js @@ -2,6 +2,9 @@ import "./style.css"; browser.runtime.onMessage.addListener(function (message, _sender, _sendResponse) { console.debug("JabRef: Received message in popup:", message); + if (message.popupLog) { + appendLog(message.popupLog, "info"); + } if (message.popupClose) { // The popup should be closed setTimeout(function () { @@ -10,8 +13,10 @@ browser.runtime.onMessage.addListener(function (message, _sender, _sendResponse) console.log("JabRef: Popup closed"); } else if (message.onConvertToBibtex) { document.getElementById("status").innerHTML = "Converting to BibTeX..."; + appendLog("Translation done, converting item data to BibTeX", "info"); } else if (message.onSendToJabRef) { document.getElementById("status").innerHTML = "Sending to JabRef..."; + appendLog("BibTeX ready, sending data to JabRef", "info"); } });