feat(dictionary): add Hachidori backend support

- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
This commit is contained in:
2026-09-22 00:21:19 -07:00
parent 1508863dbb
commit d9fdc7ef6d
446 changed files with 109060 additions and 244 deletions
+44
View File
@@ -0,0 +1,44 @@
// Chrome MV3 lifecycle for the shared offscreen engine document.
// Firefox imports this shared module but never calls its guarded Chrome path.
// SPDX-License-Identifier: GPL-3.0-or-later
import { extensionApi as chrome } from "./browser-api.js";
let creating = null;
export function chromeOffscreenSupported() {
return typeof chrome.runtime.getContexts === "function"
&& typeof chrome.offscreen?.createDocument === "function";
}
async function offscreenExists(url) {
const contexts = await chrome.runtime.getContexts({
contextTypes: ["OFFSCREEN_DOCUMENT"],
documentUrls: [chrome.runtime.getURL(url)],
});
return contexts.length > 0;
}
async function createOffscreen(url) {
try {
await chrome.offscreen.createDocument({
url,
reasons: ["DOM_SCRAPING", "AUDIO_PLAYBACK", "DISPLAY_MEDIA"],
justification:
"Runs the dictionary engine and pronunciation audio, and owns explicitly started local display capture across control-page closure.",
});
} catch (error) {
// Another extension context may have won the race; only a genuine absence
// is a failure.
if (!(await offscreenExists(url))) throw error;
} finally {
creating = null;
}
}
export async function ensureChromeOffscreen(url) {
if (!chromeOffscreenSupported()) return;
if (await offscreenExists(url)) return;
if (creating === null) creating = createOffscreen(url);
await creating;
}