Skip to content

Latest commit

 

History

History
135 lines (111 loc) · 6.21 KB

README.md

File metadata and controls

135 lines (111 loc) · 6.21 KB

Hydra Firefox Extension

Same code, same transport, same behaviour as the Chrome extension — including automatic download capture, which Firefox fully supports, and HLS/DASH stream sniffing.

Layout

Firefox has its own background.js in this directory; everything else is shared with the Chrome extension:

File Owner
manifest.json, background.js, README.md Firefox's own, tracked here
core.js, content.js, popup.*, welcome.*, icons/ copies of extensions/chrome/*, regenerated by the sync below

core.js is the browser-neutral part of the extension — transport, capture gates, stream sniffing, menus, the popup's messages — and it binds whichever namespace exists (browser on Firefox, chrome on Chromium). background.js is the one thing the browsers do not agree on: how a download is captured. The manifest loads core.js first, so background.js reaches the shared functions as plain globals.

scripts/sync-extension-resources.sh firefox

Edit extensions/chrome/* for shared behaviour and extensions/firefox/background.js for capture — the copies are overwritten on every sync.

Firefox-specific details

  • Manifest V3 with an event page (background.scripts) instead of a service worker — Firefox's MV3 background is a non-persistent event page, and the shared code runs unchanged on both. The blocking webRequest listener is registered synchronously at the top level of background.js, which is what lets Firefox persist it across event-page shutdowns and wake the script for a response rather than letting it through.
  • The manifest overrides the default MV3 CSP. Firefox's default for extension pages is script-src 'self'; upgrade-insecure-requests;, and that second directive rewrites the extension's ws://127.0.0.1:6799 to wss://, which the app does not speak. With it in place the WebSocket never connected: every request fell through to spawning hydra-host, and the toolbar showed the app as down even while it was running. The manifest's content_security_policy.extension_pages keeps the script restriction and drops the upgrade; the socket never leaves the machine.
  • Capture happens at the response, not at the download. Firefox has no onDeterminingFilename, and a download it has already created cannot be handed to Hydra without leaving a "Canceled" row in the browser's own download list — downloads.erase() drops the extension's view of it, not the browser's memory. So Gecko decides one event earlier, in a blocking webRequest.onHeadersReceived: a response bound for Hydra is cancelled before a download exists, and one left to the browser is never touched. That is what webRequestBlocking is in the manifest for; Chromium has no such permission under MV3 and does not need one, because it can park a download with downloads.pause() and hand it back intact. downloads.onCreated stays registered underneath as the net for transfers that arrive without a response we were shown.
  • Stable add-on id (hydra@ja7ad.github.io, in browser_specific_settings.gecko) — native messaging allow-lists Firefox add-ons by id (allowed_extensions), not by an extension-origin URL the way Chromium does.
  • The proxy setting is readable, but only per browser. proxy.settings answers with Firefox's own object (proxyType, http/ssl/ftp, socks + socksVersion, passthrough) rather than Chromium's, and the shared core reads whichever it is handed. Only Manual proxy configuration names an address; "Use system proxy settings" and an automatic configuration URL do not, and are passed on as nothing. Reading the setting needs no private-browsing access — only set() does, and this extension never sets anything.
  • Site access can be switched off. Firefox grants host_permissions at install (127+), but a user can revoke them from about:addons → Hydra → Permissions; if cookies stop being attached, that is where to look.

Install

  1. Register the native host. The Hydra app does this itself on every start — it writes Firefox's manifest into ~/Library/Application Support/Mozilla/NativeMessagingHosts on macOS, ~/.mozilla/native-messaging-hosts on Linux, and points HKCU\Software\Mozilla\NativeMessagingHosts at it on Windows — so normally there is nothing to do here. From a source checkout, where the app may not have been launched yet, the script does the same thing:

    scripts/install-native-host.sh
  2. Assemble the extension directory:

    scripts/sync-extension-resources.sh firefox
  3. Load it: about:debugging#/runtime/this-firefox → Load Temporary Add-on → pick extensions/firefox/manifest.json.

Packaged .xpi

scripts/build-firefox-xpi.sh      # Firefox only
scripts/build-extensions.sh       # Firefox .xpi + Chromium .zip + INSTALL.txt

Both write target/extensions/hydra-firefox-<version>.xpi (and verify that manifest.json sits at the archive root, which Firefox requires) alongside target/extensions/firefox/, the unpacked copy the archive was made from. Load either — Load Temporary Add-on accepts an .xpi directly, or the directory's manifest.json.

build-extensions.sh --out DIR packs into DIR instead; that is how the DMG, PKG, deb, rpm and Windows installers put the extension inside the installed application, next to an INSTALL.txt written with that machine's paths.

Temporary add-ons are cleared when Firefox restarts. For a permanent install the package must be signed by Mozilla:

export WEB_EXT_API_KEY=user:12345678:123
export WEB_EXT_API_SECRET=...
scripts/build-firefox-xpi.sh --sign

(keys from https://addons.mozilla.org/developers/addon/api/key/; the unlisted channel means self-distribution, not a public listing). The alternative is Developer Edition / Nightly / ESR with xpinstall.signatures.required=false.

Transport

Identical to Chrome: a persistent WebSocket to ws://127.0.0.1:6799 (fallback 16799), authenticated by the moz-extension://… origin, with the native host as the fallback that can launch the app. See the protocol table.