Living document. Tracks the Chrome → Firefox MV3 port across sessions. Update after every session so the next session picks up exactly where this one left off.
- Firefox 128+ (ESR 128+) — matches the
strict_min_versionalready declared inmanifest-firefox.json - Manifest V3 — Firefox's MV3 is persistent background scripts, not service workers
- Distribution: eventual AMO listing, interim self-signed XPI via
about:debuggingfor dev
- One codebase, runtime-forked. The Chrome build must not regress. All Firefox-specific code paths gated behind a feature detect, not an environment variable or separate source tree.
- Feature detect, never UA-sniff where possible (
typeof chrome.offscreen,typeof chrome.sidePanel, etc.). Fall back tonavigator.userAgent.includes('Firefox')only for decisions that can't be expressed as API presence. - Degrade gracefully. If a feature isn't available on Firefox, hide its UI entry points rather than showing a broken button.
- Ship both artifacts from the same tag. Every release produces
ScriptVault-vX.Y.Z.crx,...zip, and...firefox.xpiattached to the same GitHub release.
-
manifest-firefox.jsonexists withbrowser_specific_settings.gecko, MV3, idScriptVault@sysadmindoc.dev -
build-firefox.sh+ equivalent Python packer can produce a.xpi - Compatibility gotchas catalogued (see "Known porting issues" below)
Goal: installs without manifest errors, opens the dashboard, basic userscripts register and run.
- Strip
manifest.sandboxfrommanifest-firefox.json. Firefox MV3 doesn't support sandboxed manifest pages. Verify the build script doesn't copy the Chromecontent_security_policyblock either. - Feature-flag
chrome.offscreen.bg/analyzer.jsandbackground.core.jscallchrome.offscreen.createDocument(...). On Firefox this throws. Under Firefox's persistent background script you can run Acorn inline. Plan:- Guard all
chrome.offscreen.*call sites withif (typeof chrome.offscreen !== 'undefined') - Provide a
loadAcornInline()fallback that importslib/acorn.min.jsviaimportScripts(bg script) or a dynamic<script>inject (Firefox bg is a full document, not a worker) - Same treatment for
lib/diff.min.js3-way merge
- Guard all
- Feature-flag
chrome.sidePanel. Dashboard has side-panel hooks. Firefox has no equivalent. Hide the toggle inpages/dashboard.jswhentypeof chrome.sidePanel === 'undefined'. - Strip
worldIdon Firefox.src/background/registration.ts/bg/*may setworldIdonchrome.userScripts.register(). Chrome 133+ only. Firefox rejects unknown fields. Guard withif (USERSCRIPT_API_SUPPORTS_WORLD_ID)after a runtime probe. - Monaco editor loading path. Firefox MV3
extension_pagesCSP allows'unsafe-eval'by default, so Monaco can load directly in the dashboard extension origin with no sandboxed iframe at all. Two options:- A (minimal): keep the iframe, drop the manifest
sandboxdeclaration, switch CSP to allow eval.monaco-adapter.jsalready posts with'*'targetOrigin. - B (cleaner): inline the Monaco container directly in the dashboard, delete the iframe on Firefox. More invasive.
- Decision needed: start with A to prove loading; revisit B in Phase 4 polish.
- A (minimal): keep the iframe, drop the manifest
- Build + temporary install test.
python build/pack-firefox.py(to be created, mirrorspack-release.py)- Load in Firefox Nightly:
about:debugging#/runtime/this-firefox → Load Temporary Add-on → select .xpi - Record the error console output. Each error becomes a Phase 1 checkbox.
Done-criteria: dashboard opens, popup opens, a test userscript can be created, saved, toggled on, and runs on a target page. Cloud sync and side panel can be broken.
Goal: existing-Chrome-user data import path + Firefox-specific storage behavior validated.
-
unlimitedStorage+storage.localquota. Firefox has different IDB quotas than Chrome. Validate the 26-script test fixture still fits. - Import JSON/ZIP backups from a Chrome export into the Firefox build. No data loss, metadata preserved,
updatedAtintact. - Cross-browser ID strategy. Script IDs are generated via
generateId()— confirm they're opaque and won't collide with Chrome IDs on re-import. - Migration logic —
modules/migration.jsruns v1.x → v2.0 migration. Confirm it's idempotent so re-importing a migrated file doesn't double-migrate. - Trash recovery + undo still works after a Firefox restart (persistent background means state survives, unlike Chrome SW).
Goal: decide which of these ship on Firefox v1 and which are explicitly out-of-scope.
- Cloud sync providers (
modules/sync-providers.js+sync-easycloud.js):- Google Drive PKCE — needs
moz-extension://<uuid>/redirect URI registered in Google Cloud Console - Dropbox PKCE — same
- OneDrive PKCE — same
- WebDAV — should work as-is (no OAuth)
- Easy Cloud (
chrome.identity+chrome.storage.managed) — may not work on Firefox depending on identity flow support - Decision needed: ship WebDAV only on Firefox v1? Defer OAuth providers to Phase 5?
- Google Drive PKCE — needs
- DeclarativeNetRequest rule management (
src/background/dnr-rules.ts) — Firefox MV3 DNR is supported but the dynamic rule API may differ. Test rule registration + teardown. -
@requireSRI verification (src/background/resource-loader.ts) — works the same on both (pure crypto). - Ed25519 signing (
bg/signing.js) — Web Crypto API, identical.
- Hide broken UI. Any feature not available on Firefox should have its entry point hidden, not just throw on click.
- Firefox compat shim (
pages/dashboard-firefox-compat.js) already exists — audit what it does today, expand as needed. - Dashboard "About" panel — show a "Firefox build" or "Chrome build" indicator with the current browser's version string.
- Icon adjustments. Firefox uses the
actionicon slightly differently; make sure it renders in the toolbar at all Firefox zoom levels. - Popup width — Firefox has a stricter popup sizing model; confirm the 360 px popup doesn't get cut off.
- Keyboard shortcuts — the
commandsblock already usesAlt+Shift+*, which works on both. Verify no Firefox conflicts. - Dark / light theme — verify both themes render correctly on
about:addons-injected chrome.
- AMO developer account set up under SysAdminDoc.
- Lint pass. Run
web-ext lintagainst the XPI; fix every warning Mozilla flags before submission. -
web-ext buildto produce a canonical source-plus-artifact pair. - Source submission bundle. AMO requires source code if the extension uses minified libraries (Monaco definitely qualifies). Prepare a zip of the source tree + exact build instructions.
build/pack-firefox.py+esbuild.config.mjssource paths must be documented. - Privacy policy + permissions rationale — reuse the Chrome listing as a starting point, adapt for AMO format.
- Initial AMO listing as unlisted for internal smoke testing before going public.
- Review feedback loop. Mozilla reviews typically flag
unsafe-eval, broadhost_permissions, and undocumented use ofuserScripts. Be ready to justify each. - Publish as listed once all feedback resolved.
- Dual-target release process. Every future release builds both Chrome and Firefox artifacts from the same tag and uploads both to the GitHub release.
- Regression tests. Add a Vitest suite that at least sanity-checks the Firefox manifest's JSON shape and the feature-flag branches in shared code.
- CI matrix. GitHub Actions job that produces both
.crx(Chrome, viabuild/pack-release.py) and.xpi(Firefox, viabuild/pack-firefox.py) on every tag push.
| # | Issue | File(s) | Blocker level |
|---|---|---|---|
| 1 | chrome.offscreen not in Firefox |
background.core.js, bg/analyzer.js, offscreen.html / .js |
Hard — AST analysis breaks |
| 2 | manifest.sandbox.pages not in Firefox |
manifest-firefox.json, pages/editor-sandbox.html |
Hard — Monaco won't load |
| 3 | chrome.sidePanel not in Firefox |
pages/dashboard.js, pages/sidepanel.html |
Medium — UI-only |
| 4 | Per-script worldId is Chrome 133+ only |
src/background/registration.ts |
Medium — registration fails if set |
| 5 | Google / Dropbox / OneDrive PKCE redirect URI | modules/sync-providers.js, modules/sync-easycloud.js |
Medium — can defer |
| 6 | Chrome CRX3 format irrelevant on Firefox | build/pack-release.py |
Low — separate build path needed anyway |
| 7 | navigator.userAgent / runtime.getBrowserInfo detection |
shared modules | Low — wrapper helper |
- Cloud sync scope for v1 Firefox build — ship WebDAV only (fast path) or all providers (slower, needs per-provider redirect URI registration)?
- Editor approach — keep the sandboxed iframe architecture on Firefox (simpler code, small perf overhead) or inline Monaco directly in the dashboard (cleaner, more work)?
- Source tree strategy — stay with runtime feature detects in shared files, or split Firefox-specific code into a
firefox/overlay directory? - AMO listing visibility — go public on day one, or start unlisted with a private signing for personal testing?
ScriptVault-firefox-v2.1.7.xpi/.zipcurrently sits in the repo root from the exploratory build. Delete, gitignore, or keep as the "current nightly"?
- Assessed existing Firefox skeleton:
manifest-firefox.json+build-firefox.shalready in place. - Built a current
ScriptVault-firefox-v2.1.7.xpivia Python packer (not yet tested in actual Firefox). - Catalogued the 7 known porting issues above.
- This roadmap file created. Next session starts at Phase 1 checkbox 1.
Update this file at the end of every Firefox-port session. Link to it from the repo's CLAUDE.md so any future Claude session resumes from the correct phase without re-deriving the plan.