A small Firefox and Zen Browser extension that automatically unloads tabs after they have been inactive for a configurable amount of time, while keeping protected tabs—such as Zen Essentials—loaded.
This project is vibe coded. Most of its implementation was produced with AI assistance and then reviewed with automated tests and linting.
Zen Mods are CSS-only and cannot schedule tab unloading. A WebExtension can use Firefox's native tab discard API, preserving the tab, its history, and its position while releasing the page from memory.
- Unloads eligible pinned and unpinned tabs older than the configured inactivity threshold.
- Defaults to a 60-minute threshold and scans every 5 minutes.
- Always skips active, highlighted, already-unloaded, loading, protected, and browser-protected tabs.
- Skips audible, private, and attention-requesting tabs by default.
- Never unloads internal or local URLs such as
about:,file:,data:, or extension pages. - Supports case-insensitive URL exclusion globs, one per line.
- Includes a dry-run mode that reports candidates without unloading them.
- Keeps scheduled scans paused during first-run setup; pre-setup manual scans are forced dry runs.
- Stores only bounded aggregate scan history—never tab URLs or titles.
- Uses no host permissions, content scripts, analytics, or network requests.
Firefox's WebExtension API does not expose Zen's internal zen-essential marker. The extension therefore never guesses which tabs are Essentials.
After installing:
- The settings page opens automatically.
- In Open tabs, choose Select pinned, then select the tabs that are Zen Essentials.
- Choose Protect selected.
- Choose Finish setup. If automation is enabled in the unload policy, scheduled scans begin then.
Alternatively, right-click an individual tab and choose Protect from stale unloading.
Protection is stored with Firefox session data, follows a restored tab across close/restore and browser restart, and sets the tab's autoDiscardable property to false. This also protects it from Firefox and Zen's native memory-pressure unloader. The extension records the tab's previous autoDiscardable value and restores that exact value when protection is removed, so it does not weaken protection applied elsewhere.
Protection is local session metadata. It is not inferred for new Essentials, synchronized to another device, or guaranteed to transfer when another extension recreates a tab rather than restoring it.
A tab is unloaded only when all of these are true:
- It is an open pinned or unpinned web tab.
- It is not active or part of the current highlighted selection.
- It is loaded and not currently loading.
- It is not protected by this extension or another browser protection.
- Its URL is a normal web URL and does not match an exclusion glob.
- Its
lastAccessedtimestamp is at least the configured age. - Any enabled audio, private-window, and attention safeguards pass.
The extension fetches current tab and protection state immediately before each discard instead of relying on the initial scan snapshot. A protection change already in progress causes that tab to be skipped. Firefox may still reject a discard—for example, when a page has a blocking beforeunload prompt. Such failures are counted and do not stop the rest of a scan. Browser alarms can also run later than scheduled; eligibility always uses the tab's actual lastAccessed timestamp.
Requirements: Node.js 22 or newer.
npm installinstalls the local development toolchain.npm testruns unit and mocked browser-API tests for eligibility, glob matching, onboarding, protection restoration, and scanning.npm run lintvalidates the extension withweb-ext.npm run validateruns manifest policy checks, syntax checks, tests, and linting.npm run buildcreates an unsigned XPI inweb-ext-artifacts/.
The VS Code tasks Validate extension and Build unsigned XPI run the same commands.
No extension is loaded by this repository setup. To test it later in a disposable browser session, use web-ext run with a dedicated profile. To load it temporarily in Zen, open about:debugging#/runtime/this-firefox, choose Load Temporary Add-on, and select extension/manifest.json. Temporary installations disappear when Zen exits.
Do not combine the test with a production profile until first-run setup is complete and dry-run results are satisfactory.
Release builds of Firefox and Zen normally require signed extensions. Build the XPI, submit it to Mozilla Add-ons for unlisted signing, and install the returned signed XPI. The project declares Firefox 142 or newer; Zen 1.22.1b uses Firefox 155 and satisfies that requirement.
tabs: list open tabs, inspect their state and last-accessed time, changeautoDiscardable, and discard eligible tabs.alarms: run periodic scans without a persistent background page.storage: save settings and aggregate scan summaries.sessions: attach persistent protection metadata to restored tabs.menus: expose protect/unprotect commands in the tab context menu.
No website access is requested.
extension/: files packaged into the XPI.extension/src/logic.js: pure eligibility and glob logic shared with tests.extension/src/protection.js: sessions-based protection andautoDiscardablerestoration.extension/src/background.js: scheduling, protection, context menus, and scans.extension/popup/: toolbar popup.extension/options/: settings and bulk protection UI.tests/: Node unit tests.scripts/validate-manifest.mjs: permission and manifest policy checks.
MIT
0 comments
log in to comment.