This page documents the runtime components of uBOLite (uBlock Origin Lite), the MV3-based product. It covers the service worker initialization sequence, the DNR ruleset management module, the content-script injection manager, the ext.js storage/runtime utility layer, and the message handling architecture.
For the build-time pipeline that produces the ruleset files read at runtime, see 10.2. For the filtering mode system (basic/optimal/complete) that governs injection decisions, see 10.4. For the dashboard UI that communicates with this runtime via messages, see 10.5.
The MV3 background context runs as a service worker. The entry point is platform/mv3/extension/js/background.js. The top-level start() function is called immediately, and all event listeners are gated on the isFullyInitialized promise.
The startup state is determined by loadRulesetConfig() in config.js, which checks storage layers in order:
| State | Condition | process flags set |
|---|---|---|
| Wakeup run | rulesetConfig found in storage.session | wakeupRun = true |
| Normal (re)start | rulesetConfig found only in storage.local | (none) |
| First run | Nothing found in either storage | firstRun = true |
Session storage persists as long as the browser session is alive. If session storage still has config, the service worker was suspended (not killed) and is resuming — the static rulesets it had activated are still in effect in the browser.
start() SequenceTitle: Service Worker Startup Flow
Sources: platform/mv3/extension/js/background.js650-762 platform/mv3/extension/js/config.js49-65
startSession() Stepsplatform/mv3/extension/js/background.js650-720
loadAdminConfig() reads enterprise overrides from browser.storage.managed and applies them to rulesetConfig.rulesetConfig.version with the current manifest version. If changed, patchDefaultRulesets() adds newly-default rulesets and removes obsolete ones.enableRulesets(rulesetConfig.enabledRulesets) synchronizes active static DNR rulesets with the browser.updateDynamicRules() re-validates and re-registers all regex rules as dynamic DNR rules. Otherwise, updateSessionRules() only refreshes session-scoped strict-block rules.syncWithBrowserPermissions() reconciles stored filtering modes with actual browser-granted host permissions.registerInjectables() recomputes and replaces all registered content scripts.sessionAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' }) allows content scripts to read from browser.storage.session.dnr.setExtensionActionOptions is available (Chromium), configures whether the toolbar badge shows a blocked-request count.config.js)platform/mv3/extension/js/config.js30-70
rulesetConfig is the mutable in-memory configuration object for the service worker:
| Field | Type | Description |
|---|---|---|
version | string | Stored manifest version; compared at startup to detect upgrades |
enabledRulesets | string[] | IDs of active static DNR rulesets |
autoReload | boolean | Auto-reload affected tabs after permission change |
showBlockedCount | boolean | Show blocked count in toolbar badge |
strictBlockMode | boolean | Redirect blocked main-frame navigations to /strictblock.html |
developerMode | boolean | Enables extra diagnostics and matched-rule inspection |
hasBroadHostPermissions | boolean | Cached omnipotence flag |
defaultConfig is a frozen snapshot of the initial values. The process object holds { firstRun, wakeupRun } flags used by background.js.
loadRulesetConfig() reads session storage first (fast wakeup path), then local storage. saveRulesetConfig() writes to both simultaneously.
ext.js Utility Layerplatform/mv3/extension/js/ext.js1-134
ext.js is the single import point for all browser API access in the MV3 runtime. It provides a consistent, promise-based API over the three storage tiers and exposes platform detection.
Title: ext.js — Browser API Abstraction
webextFlavor is detected from the extension URL prefix (moz-extension:, safari-web-extension:, or Chromium default). The browser and runtime exports come from the platform-specific ext-compat.js, which also exposes the dnr object wrapping browser.declarativeNetRequest.
Sources: platform/mv3/extension/js/ext.js26-134
ruleset-manager.js)platform/mv3/extension/js/ruleset-manager.js1-580
This module manages all interactions with browser.declarativeNetRequest via the dnr import from ext-compat.js.
| Tier | Browser API | ID Range | Contents |
|---|---|---|---|
| Static rulesets | dnr.updateEnabledRulesets() | (browser-managed) | Pre-compiled filter rules from build time |
| Dynamic rules | dnr.updateDynamicRules() | 1 – 4,999,999 | Regex-based block rules; validated per-browser on startup |
| Session rules | dnr.updateSessionRules() | 1 – N | Strict-block redirect rules; cleared when browser restarts |
Special ID constants for the dynamic rule set:
| Constant | Value | Purpose |
|---|---|---|
SPECIAL_RULES_REALM | 5,000,000 | Lower bound of reserved IDs |
TRUSTED_DIRECTIVE_BASE_RULE_ID | 8,000,000 | Allow-all rules implementing none-mode exemptions |
TRUSTED_DIRECTIVE_PRIORITY | 2,000,000 | Priority for allow-all trusted directives |
USER_RULES_BASE_RULE_ID | 9,000,000 | User-authored DNR rule IDs |
USER_RULES_PRIORITY | 1,000,000 | Priority for user rules |
STRICTBLOCK_PRIORITY | 29 | Priority for strict-block redirect rules (session) |
getRulesetDetails() platform/mv3/extension/js/ruleset-manager.js71-81
Fetches /rulesets/ruleset-details (a JSON file written at build time) and returns a Map<id, rulesetMetadata>. Cached for the service worker's lifetime via a module-level promise.
enableRulesets(ids) platform/mv3/extension/js/ruleset-manager.js519-580
Computes the delta between currently enabled rulesets and the desired set. Applies admin overrides (tokens prefixed + or -). Calls dnr.updateEnabledRulesets({ enableRulesetIds, disableRulesetIds }). Returns undefined if no change was needed, or { enabledRulesets } on success.
updateDynamicRules() platform/mv3/extension/js/ruleset-manager.js174-226
Removes all existing dynamic rules below SPECIAL_RULES_REALM, fetches per-ruleset regex rule files from /rulesets/regex/<id>.json, validates each regex via dnr.isRegexSupported() (results are cached in pruneInvalidRegexRules.validated), and installs the valid ones. Also calls updateSessionRules() at the end.
updateSessionRules() platform/mv3/extension/js/ruleset-manager.js343-383
Calls updateStrictBlockRules() to build redirect rules from /rulesets/strictblock/<id>.json, then applies them as session rules. Respects dnr.MAX_NUMBER_OF_REGEX_RULES by counting combined dynamic + session regex rules and silently dropping excess session rules.
filteringModesToDNR(modes) platform/mv3/extension/js/ruleset-manager.js406-430
Translates filtering mode details into a block of allow-all DNR rules (via dnr.setAllowAllRules()) starting at TRUSTED_DIRECTIVE_BASE_RULE_ID. Sites in the none mode set receive an allow-all rule that bypasses all static rulesets for them.
patchDefaultRulesets() platform/mv3/extension/js/ruleset-manager.js483-515
On version upgrade, reads the previous default ruleset IDs from localRead('defaultRulesetIds'), computes toAdd/toRemove diffs against the new defaults (getDefaultRulesetsFromEnv()), and updates rulesetConfig.enabledRulesets.
Title: Ruleset Manager Data Flow
Sources: platform/mv3/extension/js/ruleset-manager.js44-580
When strictBlockMode is enabled, blocked main-frame navigations redirect to /strictblock.html. This requires:
hasBroadHostPermissions())./rulesets/strictblock/<id>.json.excludedStrictBlockHostnames (stored in both local storage for permanent exclusion and session storage for temporary exclusion).excludeFromStrictBlock(hostname, permanent) adds a hostname to the appropriate store and calls updateSessionRules(). setStrictBlockMode(state) toggles the feature and applies it immediately.
scripting-manager.js)platform/mv3/extension/js/scripting-manager.js1-455
Manages browser.scripting.registerContentScripts() — the MV3 API for associating JS and CSS files with URL patterns dynamically.
registerInjectables()platform/mv3/extension/js/scripting-manager.js367-422
The central function. A module-level barrier flag prevents concurrent invocations. It:
context object (with an empty toAdd array) to each registration sub-function.browser.scripting.unregisterContentScripts() to clear all existing registrations.browser.scripting.registerContentScripts(toAdd) with the newly computed set.resetCSSCache() to clear cache.css.* session storage entries.Title: registerInjectables() — Content Script Registration Pipeline
Each sub-function appends one or more directive objects to context.toAdd. Each directive has an id, a list of js or css files, matches, optional excludeMatches, allFrames, runAt, and optionally a world (for scriptlets).
| Function | Directive ID(s) | Files | runAt | world |
|---|---|---|---|---|
registerScriptlet() | <rulesetId>.<world> | /rulesets/scripting/scriptlet/<world>/<rulesetId>.js | document_start | MAIN or ISOLATED |
registerCosmetic('specific') | css-specific | css-api.js, isolated-api.js, per-ruleset .js files, css-specific.js | document_start | — |
registerCosmetic('procedural') | css-procedural | css-api.js, isolated-api.js, per-ruleset .js files, css-procedural.js | document_start | — |
registerGeneric() | css-generic-all, css-generic-some | css-api.js, isolated-api.js, per-ruleset .js files, css-generic.js | document_idle | — |
registerHighGeneric() | css-generichigh | /rulesets/scripting/generichigh/<id>.css | document_end | — |
The filteringModeDetails object has four sets: none, basic, optimal, complete. Utility functions from utils.js convert these into match patterns:
matchesFromHostnames(hostnames) → ['*://*.example.com/*', ...]subtractHostnameIters(a, b) → hostnames in a not covered by any entry in bintersectHostnameIters(a, b) → hostnames in a that are covered by bnormalizeMatches(matches) → collapses to ['<all_urls>'] if <all_urls> is present| Filter type | Injected for modes | Excluded for modes |
|---|---|---|
| Scriptlets | optimal, complete | none, basic |
| Specific/Procedural CSS | optimal, complete | none, basic |
| Generic CSS | complete only | none, basic, optimal |
| Highly generic CSS | complete only | none, basic, optimal |
registerCosmetic('specific' | 'procedural', context) platform/mv3/extension/js/scripting-manager.js230-296 fetches per-ruleset JSON data from /rulesets/scripting/<realm>/<id>.json and writes it to browser.storage.local under the key css.<realm>.<id>. The injected content scripts read these entries from local storage at injection time to determine which selectors apply to the current document's hostname.
onWakeupRun() platform/mv3/extension/js/scripting-manager.js434-453 is called by start() on wakeup runs. If at least 15 minutes have elapsed since last cleanup and the number of cache.css.* entries in session storage exceeds 256+, the oldest entries (sorted by access time field t) are evicted down to 256. This prevents unbounded growth of the per-tab CSS cache.
Sources: platform/mv3/extension/js/scripting-manager.js39-455
platform/mv3/extension/js/background.js222-617
onMessage(request, sender, callback) is registered on runtime.onMessage. All calls wait on isFullyInitialized before dispatch.
Messages are partitioned by trust level:
request.what | Action |
|---|---|
insertCSS | browser.scripting.insertCSS() on sender tab/frame |
removeCSS | browser.scripting.removeCSS() on sender tab/frame |
toggleToolbarIcon | action.toggleToolbarIcon(tabId) |
startCustomFilters | filter-manager.startCustomFilters(tabId, frameId) |
terminateCustomFilters | filter-manager.terminateCustomFilters(tabId, frameId) |
injectCustomFilters | filter-manager.injectCustomFilters(tabId, frameId, hostname) |
injectCSSProceduralAPI | Executes /js/scripting/css-procedural-api.js in the tab |
Sender origin is validated against UBOL_ORIGIN (the extension's own URL, lowercased). Firefox does not set sender.origin, so the check is skipped there.
request.what | Handler |
|---|---|
applyRulesets | enableRulesets() → saveRulesetConfig() → registerInjectables() |
getOptionsPageData | Aggregates data from multiple async sources for the dashboard |
popupPanelData | Assembles popup state: omnipotence, filtering level, custom filters flag |
setFilteringMode | mode-manager.setFilteringMode() → registerInjectables() |
setDefaultFilteringMode | mode-manager.setDefaultFilteringMode() → registerInjectables() |
setFilteringModeDetails | mode-manager.setFilteringModeDetails() → registerInjectables() |
excludeFromStrictBlock | ruleset-manager.excludeFromStrictBlock(hostname, permanent) |
addCustomFilters / removeCustomFilters | filter-manager.*() → registerInjectables() |
setDeveloperMode | Toggles rulesetConfig.developerMode, broadcasts change |
getMatchedRules | debug.getMatchedRules(tabId) |
getEffectiveDynamicRules | ruleset-manager.getEffectiveDynamicRules() |
getEffectiveSessionRules | ruleset-manager.getEffectiveSessionRules() |
For push notifications from the service worker to open UI pages (dashboard, popup), broadcastMessage() from utils.js creates a BroadcastChannel('uBOL') and posts the message. This avoids enumerating open windows.
platform/mv3/extension/js/utils.js163-166
platform/mv3/extension/js/background.js140-206
Two paths exist for host permission grants:
Via popup (onPermissionGrantedThruExtension): The popup calls setPendingFilteringMode before requesting permissions, storing a pendingPermissionRequest object. On permissions.onAdded, the stored request is consumed and setFilteringMode() is called for the specific hostname.
Via browser UI (onPermissionGrantedThruBrowser): syncWithBrowserPermissions() reconciles stored modes with actual grants. If one origin was granted and the active tab matches it, that tab is reloaded.
Both paths call registerInjectables() after mode changes. Tab reload is conditional on rulesetConfig.autoReload.
Concurrent permission changes are serialized via onPermissionsChanged.pending — an array of in-flight promises that each new change waits on before proceeding.
Title: MV3 Runtime Storage Layers
All storage access goes through ext.js: localRead/localWrite, sessionRead/sessionWrite, adminRead.
Sources: platform/mv3/extension/js/ext.js51-131 platform/mv3/extension/js/config.js49-70 platform/mv3/extension/js/mode-manager.js224-295
platform/mv3/extension/js/admin.js51-200 platform/mv3/extension/managed_storage.json1-40
Enterprise deployments set policies via browser.storage.managed. The managed storage schema defines:
| Key | Type | Effect |
|---|---|---|
defaultFiltering | string | Forces default mode: none, basic, optimal, or complete |
noFiltering | string[] | Hostnames forced to none mode; prefix -hn to remove from list |
rulesets | string[] | Tokens like +easylist or -ublock-filters to force-add or force-remove rulesets |
showBlockedCount | boolean | Overrides the toolbar badge setting |
strictBlockMode | boolean | Overrides strict-block mode |
disabledFeatures | string[] | UI feature tokens to suppress (e.g., develop) |
disableFirstRunPage | boolean | Suppresses the first-run onboarding page |
loadAdminConfig() is called at each startSession(). Admin storage changes are monitored via browser.storage.onChanged; applyAdminConfig() applies delta changes to rulesetConfig and calls the relevant runtime APIs immediately.
adminReadEx() reads from managed storage with a fallback to local storage (for development) and caches results in session storage.
Title: MV3 Runtime Module Dependencies (platform/mv3/extension/js/)
Sources: platform/mv3/extension/js/background.js22-110 platform/mv3/extension/js/scripting-manager.js22-36 platform/mv3/extension/js/ruleset-manager.js22-40 platform/mv3/extension/js/mode-manager.js22-43 platform/mv3/extension/js/admin.js22-47
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.