Design note: Firefox Add to Rollup¶
Status: shipped MVP (unsigned temporary add-on). Ingest-only; not a read-later library. Thunderbird XPI remains a non-goal.
Capture enqueues HTTPS URLs into the existing webpage_queue. The digest still
fetches the page later. rollup web must be running.
Reload¶
There is no signed XPI. The add-on is Load Temporary Add-on only
(about:debugging#/runtime/this-firefox → choose
extension/firefox/manifest.json).
After Firefox restarts the temporary add-on is gone. Load manifest.json
again. Options storage does not survive the unload — paste the capture token
from Articles if the options page is empty. rollup web must already be
running.
While iterating (Firefox still open): keep This Firefox debugging open and
click Reload next to Rollup after editing JS/CSS/HTML. Origin and token
in browser.storage.local survive Reload. Changes to manifest.json
(permissions, background.scripts, gecko.id) need Remove then Load
Temporary Add-on again.
First-load pairing (token copy, Test connection) is in
extension/firefox/README.md.
Packages¶
The add-on has no npm/bundler dependencies. It uses Firefox WebExtensions
APIs only (browser.action, browser.menus, browser.notifications,
browser.storage, browser.permissions, fetch). Manifest V3, gecko.id
addon@rollup.local, strict_min_version 109.
The capture route adds no new Python packages. It uses the existing
rollup[web] extra (flask>=3.1.3,<4) plus stdlib (hmac, secrets). Queue
validation stays in rollup.webpage.url / rollup.webpage.queue.
SLOC¶
Snapshot 2026-09-03 (cloc, excluding PNG and Markdown):
Tree |
Files |
Code |
|---|---|---|
|
5 |
433 |
Capture server + tests ( |
3 |
494 |
articles.py includes the older HTML form routes (/add, remove, retry), not
only capture. Re-count:
cloc --exclude-ext=md,png extension/firefox
cloc src/rollup/web/routes/articles.py src/rollup/web/secrets.py tests/test_web_articles_capture.py
No live Firefox harness; pytest covers the HTTP contract only.
Known issues¶
Unsigned / temporary. Load via
about:debugging. The add-on unloads when Firefox restarts — reload. AMO signing is out of scope.Firefox only. No Chrome/Safari build. Manifest uses
background.scripts(not Chromeservice_worker).rollup webrequired. There is no nativeMessaging helper and no enqueue path that talks to SQLite directly.Loopback HTTP. Origin must be
http://127.0.0.1,http://localhost, orhttp://[::1]. Custom ports need an extra host permission at save time.http://[::1]/*optional permission support varies by Firefox version.Restricted pages.
about:,moz-extension:,file:,blob:,data:URLs are rejected in the add-on before POST.Token on GET
/articles. Anyone who can read the loopback HTML can copy the token. Random websites cannot: no CORS. Do not put the token in query strings or TOML.Do not “fix” pairing with CORS or CSRF exemptions. Session cookies are
SameSite=Strict, somoz-extension://cannot use/articles/add. Bearer on/articles/captureis the intended seam.Test connection POSTs an empty
url(expects400 url_invalidif the token is good). It must not enqueue.Notifications require the Firefox permission prompt on first use.
Code reviews¶
Review this surface as a local capability, not a public API.
Must stay true
No
Access-Control-Allow-*headersForm POSTs still require session CSRF; capture ignores CSRF and requires Bearer
Host header remains loopback-only (
headers.trusted_host_allowed){state_dir}/extension_tokenis mode0600, no symlinks, never TOMLvalidate_queue_url+enqueue_urlunchanged for SSRF / duplicatesExtension sends URL + title only (no HTML body)
Test map (tests/test_web_articles_capture.py): created / duplicate /
retried; invalid URL + SSRF; missing/wrong token; CSRF does not authorize
capture; form-urlencoded → 415; GET → 405; non-loopback Host → 400; rotate
invalidates; token file perms + symlink refuse.
When reviewing a change, prefer those tests over a browser walkthrough.
If you change pairing (token header, origin rules, JSON shape), update this
note, WEB.md, and extension/firefox/README.md.