Migrate to Manifest V3#10
Conversation
- Replace background.scripts with a service worker (background.js) that loads the existing scripts via importScripts. The script list the build tooling needs lives in a separate background_scripts.json, keeping the manifest free of MV2-only keys. - Persist contextId and lastFocusedWindowUrl to chrome.storage.session so key events that revive a terminated service worker can still be remapped (onFocus does not fire again on revival). This adds the storage permission. - Make wscript emit an MV3 manifest plus a generated background.js service worker for combined IMEs. An IME's scripts are found in background_scripts.json next to its manifest, under background.scripts (MV2), or as background.service_worker (MV3).
ChromeOS doesn't wait for a terminated MV3 service worker to wake up before dispatching a key event: keys pressed while the worker is asleep fall through to the browser's default handling, so restoring state from chrome.storage.session on revival isn't enough on its own. Prevent the worker from idling out while the IME is in use by calling a cheap extension API every 20 seconds, which resets Chrome's 30-second idle timer. The keepalive starts on onActivate (and on focus/key events, the wake-up paths when the worker died mid-activation) and stops on onDeactivated so the worker can still be reclaimed while the IME isn't active.
ChromeOS moved crosh from the crosh extension to a System Web App at chrome-untrusted://crosh/, so the hardcoded extension URL no longer matches and keys were being remapped inside crosh. Match known URL prefixes instead of exact URLs, and add the crosh SWA and the Terminal app, which wants raw keys for the same reason.
|
Thaks for the PR. Just so I can decide how to answer questions I have about the changes: Are you familiar with Chrome extension development and just used Claude for convenience, or did you rely on Claude to come up with solutions? |
|
Thanks for taking a look, and fair question. To be upfront, I'm not so experienced in Chrome extension development and I relied on Claude to come up with most of the solutions. That said, all the testing was done by me on an actual ChromeOS device, and it did shape the changes: the initial version only persisted state to chrome.storage.session, but my testing showed keys still fell through to the browser's default handling after the service worker idled out (e.g. C-a became select-all), which is what led to the keepalive approach. I also found during testing that the hardcoded crosh URL no longer matches now that crosh is a System Web App. For any questions I can't answer shortly, I'll dig in and follow up, so replies might just take me a little longer. And if you'd rather take the changes in a different direction, I'm happy to iterate — and feel free to push changes directly if that's easier (I've enabled maintainer edits). If the PR feels too large, I can also split it up; the crosh URL fix in particular is really an independent fix and could go in its own PR. |
Summary
Chrome has been phasing out Manifest V2 extensions, so this PR migrates both the premade IME and the build tooling to Manifest V3. All changes have been verified on a ChromeOS device (see Testing below).
Changes
Service worker migration
background.scriptswith a single service worker (remapper/background.js) that loads the existing scripts viaimportScripts. The script files themselves needed no changes.remapper/background_scripts.json, keepingmanifest.jsonfree of MV2-only keys (and free of the "'background.scripts'requires manifest version of 2 or lower" warning).Surviving service worker termination
MV3 service workers are terminated after ~30s of inactivity, which broke remapping in two ways, each needing its own fix:
contextIdandlastFocusedWindowUrlare now persisted tochrome.storage.sessionand restored on worker startup, sinceonFocusdoesn't fire again when a worker is revived by a key event alone. This adds thestoragepermission.onActivate(and on focus/key events as a fallback) and stopping ononDeactivated, so the worker can still be reclaimed while the IME isn't in use.crosh blacklist fix
Found while testing this PR, though it's a pre-existing issue: ChromeOS moved crosh from the crosh extension to a System Web App at
chrome-untrusted://crosh/, so the hardcoded extension URL no longer matched and keys were being remapped inside crosh. The blacklist now matches URL prefixes and includes the crosh SWA and the Terminal app (which wants raw keys for the same reason).Build tooling
wscriptnow emits an MV3 manifest plus a generated top-levelbackground.jsthatimportScriptseach stacked IME's scripts, remapper first.background_scripts.jsonnext to its manifest, frombackground.scripts(MV2 fallback IMEs, unchanged), or frombackground.service_worker(MV3 fallback IMEs).Documentation
README notes the MV3 architecture and one new limitation: fallback IMEs whose background scripts rely on DOM APIs (
window,document, …) won't work inside a service worker.Testing
On a ChromeOS device, with the
remapperdirectory loaded unpacked:keymap.jswork in a regular text field; unmapped keys pass through.C-abecame select-all).M-b, whose shell behavior — backward-word — is distinguishable from its remapping), while remapping continues to work in regular text fields.Build tooling was verified by building both a standalone remapper and a remapper combined with a fallback MV2 IME: generated manifests are valid MV3, the generated
background.jsloads scripts in the right order, and thechrome.input.ime→Remapper.hijackrewrite still applies.https://claude.ai/code/session_01J8HdQQm7honRLVYMNkeqiR