Files
SubMiner/vendor/hachidori/extension/README.md
T
sudacode d9fdc7ef6d 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
2026-09-22 00:21:19 -07:00

187 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# The Hachidori extension
This folder is the shared extension source and the Manifest V3 package exactly
as Chrome 128 or newer loads it, with no build step. The JavaScript is plain ES
modules and classic scripts, the dictionary engine is committed WebAssembly
under `vendor/`, and everything runs inside the browser. To run it in Chrome
from a checkout, open `chrome://extensions`, turn on **Developer mode**, choose
**Load unpacked** and select this folder. `scripts/package-store.py` zips this
same folder, with the licence files, for the Chrome Web Store.
`manifest.firefox.json` is the reviewed Firefox MV2 manifest. The same
packager writes the Firefox XPI from these sources minus the Chrome-only files
listed in `scripts/firefox-package.json`, with that manifest in place of
`manifest.json`; `scripts/prepare-firefox.mjs` stages the same layout in an
ignored directory for lint and the Firefox smoke test. See the
[Firefox guide](../docs/firefox.md) to build and temporarily install the
unsigned XPI.
[The architecture guide](../docs/architecture.md) explains how the pieces
work together and lists every runtime message and stored key. This page says
where things are.
## Entry points
Drag a lookup popup's bottom-right corner to resize it. The size is shared by
subsequent and nested lookups in that page, including after closing and reopening
the popup. Reloading or navigating the page (or restarting the browser) starts a
new reading session with the saved Design dimensions. Dragging does not change
those saved settings or other tabs.
The popup action row is one non-wrapping keyboard and visual group: a nested
Close or Back control first, then Anki, pronunciation, personal-dictionary
edit, and custom buttons in saved order. A custom button opens a URL template
or mines with a chosen Anki Template. Actions share a 36-pixel height and a
5-pixel gap. At narrow popup widths the whole action row scrolls horizontally
instead of wrapping, clipping, or overlapping controls. Browser mode opens link
buttons in a Chrome tab; overlay mode asks its embedding host to open the same
validated URL in the system browser.
`manifest.json` names them.
| File | Runs as | Role |
| --- | --- | --- |
| `background.js` | the service worker | Routes every runtime message and owns everything in `chrome.storage.local`: dictionary metadata, options, the personal dictionary, update schedules, lookup counts, automatic-backup metadata, first-run and sharing state. It also owns the alarms, the Anki gateway and the sharing host and client. It holds no engine state, so Chrome may stop it whenever it is idle. |
| `firefox-background.html`, `firefox-background.js` | Firefoxs persistent MV2 background page | Loads the shared background module and hosts `offscreen.html` in one authenticated hidden iframe so the engine remains warm. |
| `content.js`, with the classic scripts listed under `content_scripts` | every web page | Scans the Japanese text near the pointer, renders the popup in a closed shadow root through `render/popup.js` and `render/glossary.js`, and adds the popup's Anki and pronunciation controls (`anki-content.js`, `audio-content.js`). Chrome also injects `capture-content.js`; Firefox does not. `content.css` is the only style the page itself receives: the source highlight. |
| `offscreen.html`, `offscreen.js` | Chromes offscreen document or Firefoxs hidden background iframe | Owns the dictionary engine. `engine-worker.js` runs the pthread build with direct OPFS once `opfs-capability-worker.js` has proved the browser can, `engine-worker-idbfs.js` runs the pthread build on IDBFS when the browser has shared memory but no OPFS access handles (Electron), both through `engine-worker-runtime.js`; `engine-service.js` is also the single-thread IDBFS fallback. Pronunciation, Anki and the first-run installer load here on demand. Chrome also hosts media capture here. |
| `settings.html`, `settings.js` | the options page | Dictionaries, groups, updates, the personal dictionary, Reading, Design, pronunciation, Anki, keybinds, backup and sharing, with media capture where supported and global search. The larger sections have their own `*-settings.js` controller; `design-preview.html` is the live preview inside Design. |
| `startup.html`, `startup.js` | a tab opened once after install | First-run setup: recommended dictionaries, Anki detection, a practice lookup, and the offer to use a Hachidori that another browser on this computer already shares. Overlay mode skips it. |
| `toolbar.html`, `toolbar.js` | the toolbar button's popup | Turns lookups on and off, shows the sharing state and opens Settings. Chrome also exposes the recording action here. |
| `capture.html`, `capture.js` | a Chrome-only tab opened from the toolbar or Settings | Controls media capture. The recorder itself, `capture-host.js`, runs in the offscreen document and keeps going when this tab closes. Firefox does not expose this entry point. |
`overlay-mode.js`, its `browser-api.js` dependency, `render/reader.css` and
`icons.css` are the only files web pages may fetch
(`web_accessible_resources`). The popup and its Anki controls load the two
stylesheets; overlay hosts use the shared mode contract.
## Modules by feature
Files share a prefix with the feature they belong to. A rule that more than
one context needs lives in a module with no Chrome dependency, so Settings,
the service worker and both engine runtimes run the same code.
- **Dictionaries and stored state.** `reader-options.js` is the one stored
options view every context reads. `dictionary-group-state.js` and
`dictionary-groups.js` hold the group rules and their Settings controls,
`dictionary-name-drafts.js` the autosaved names, `dictionary-progress.js`
the import progress. `managed-dictionary-source.js` and
`recommended-dictionaries.js` define the trusted update sources and the
starter set; `custom-dictionary.js` the personal dictionary's source format
and archive; `setup-state.js` the first-run stages and initial selections;
`setup-installer.js` the offscreen recommended installer, observed from startup
and Settings by `recommended-install-client.js`. `json-value.js` and `response-limits.js`
are the comparison and size rules the transaction boundaries share.
- **Lookup statistics.** `lookup-stats-identity.js`, a classic script so the
content script can use it, and `lookup-stats.js`.
- **Anki.** `anki.js` is the AnkiConnect gateway and `anki-setup.js`
recognises an existing mining setup. `anki-templates.js`, `anki-values.js`,
`anki-glossary.js`, `anki-pitch.js`, `anki-resources.js` and `anki-audio.js` build the note
fields and media. Stored Anki Templates group each destination, note type,
field mapping and duplicate policy; the first powers the built-in action and
custom Anki buttons select the others by stable ID. Settings edits every
field mapping through an accessible marker combobox while retaining the
mapping string exactly. `anki-duplicates.js` and
`anki-enrichment.js` handle a
note that already exists; `anki-digest.js` hashes media.
`anki-client-media.js` validates final screenshot, capture and browser-speech
media crossing a linked-browser boundary. `anki-mining.js` and
`anki-worker.js` are the mining service in the
service worker. `anki-index.js` and `anki-index-cache.js` provide the shared
scoped duplicate and maturity index, including cache-only View readiness and
click-time live ID repair. `anki-offscreen.js` launches
`anki-index-worker.js` for complete refreshes without moving note fields
through the service worker.
- **Pronunciation.** `audio-sources.js`, `audio-repository.js`,
`audio-cache.js` and `audio-player.js` fetch, keep and play audio in the
offscreen document (`audio-offscreen.js`); `speech.js` wraps the browser's
text-to-speech.
- **Media capture.** `capture-host.js` is the offscreen recorder.
`capture-session.js`, `capture-buffer.js`, `capture-timeline.js` and
`capture-speech.js` are its bounded buffers, occurrence timeline and speech
detection. `capture-audio-worklet.js`, `capture-frame-client.js` with
`capture-frame-worker.js`, and `capture-encoder-client.js` with
`capture-encoder-worker.js` move audio sampling, frame grabbing and animated
AVIF encoding (`avif-sequence.js`) off the main thread.
`texthooker-protocol.js` parses the text a texthooker sends.
- **Backup.** `backup-archive.js` is the manual ZIP format, `backup-state.js`
the shared snapshot rules, `backup-automatic.js` the two-record daily
retention, cadence and age rules, `backup-downloads.js` the pending downloads,
and `backup-settings.js` the manual and automatic restore controls.
- **Sharing.** `sharing-protocol.js` is the wire contract both sides import;
`sharing-host.js` and `sharing-client.js` are the two roles in the service
worker; `sharing-settings.js` is the Settings section. `anki-addon.js` pins
and downloads the compatible `.ankiaddon` release from
[hachidori-anki](https://github.com/bee-san/hachidori-anki), which owns the
Python relay, its tests, and packaging.
- **Pages.** `settings-search.js` and `settings-dom.js` serve Settings;
`experimental-settings.js` renders the Advanced → Experimental features
switches from the registry in `reader-options.js`;
`keybind-settings.js`, `custom-button-settings.js` and `external-links.js`
the keybinds and custom buttons in the popup; `local-file-access.js` the
notice about Chrome's *Allow access to file URLs* permission;
`startup-practice.js` the practice step. `visual-novel.js` and
`visual-novel.css` draw the background scenes behind the startup page and
the Design preview from the images in `assets/` (see
`assets/ATTRIBUTION.md`); `design-preview.js` renders the preview from
`sample-meal.svg` and local sample data.
- **Renderer.** `render/` is the popup renderer ported from GameSentenceMiner,
which adapts Hoshi Reader and Yomitan; `render/ATTRIBUTION.md` records what
came from where.
- **Overlay mode.** `overlay-mode.js` is the one switch a host such as the
GameSentenceMiner overlay flips in its copy. It also defines the shared
host-capability policy used by Settings, the toolbar, the reader and the
service worker; see
[overlay mode](../docs/overlay-mode.md).
- **Vendored code.** `vendor/hoshidicts-threaded.{mjs,wasm}`,
`vendor/hoshidicts-threaded-idbfs.{mjs,wasm}` and
`vendor/hoshidicts.{mjs,wasm}` are the three builds of the hoshidicts engine
from `wasm/build.sh`, `vendor/avif-encoder.{mjs,wasm}` the AVIF encoder
from `wasm/avif/`, and `vendor/zip.js` the pinned zip.js runtime. They are
committed build output: update them with their source change and otherwise
leave them alone.
- `icons/` holds the extension's icons.
## Conventions
- Every script, stylesheet and page starts with an
`SPDX-License-Identifier: GPL-3.0-or-later` line; the files under `render/`
also keep their upstream copyright lines.
- A rule the content script needs as well as the module contexts lives in a
classic script that publishes one `globalThis.HD…` object
(`HDReaderOptions`, `HDLookupStats`, `HDDictionaryGroups`, …); modules
import such a file for its side effect.
- Runtime messages are objects with a `target` and an `hd_*` `type`, and
they carry explicit ids, revisions or generations so a stale reply fails
closed. Stored values are revisioned and written only by the service
worker; a page edits them by compare-and-set. The offscreen document never
touches `chrome.storage` itself.
- Nothing here is generated except `vendor/`. There is no bundler,
transpiler or minifier: what is committed is what ships.
## Checking a change
```sh
node test/make-fixture.mjs # writes the dictionary fixtures once
node test/extension-smoke.mjs # this folder's JavaScript against the real engine, in Node
node test/chrome-e2e.mjs # this folder loaded unpacked into a real Chrome
```
[The test guide](../test/README.md) says what each suite proves and how to
install the browser and jsdom they need; the validation list in
[AGENTS.md](../AGENTS.md) says which checks each kind of change requires.
Sharing changes have their own suites, listed in [sharing](../docs/sharing.md).
## More
- [Privacy](../docs/privacy.md): what leaves the browser, and when.
- [Sharing](../docs/sharing.md), [overlay mode](../docs/overlay-mode.md),
[media capture](../docs/media-capture.md),
[the backup format](../docs/backup-format.md),
[update schedules](../docs/update-schedules.md) and
[lookup statistics](../docs/lookup-statistics.md) describe those features.
- [Building a source archive](../docs/source-build.md) covers `wasm/` and
the `third_party/hoshidicts` submodule behind `vendor/`.