1. Architecture Overview
uBlock Origin is a wide-spectrum content blocker for modern web browsers, created and maintained by Raymond Hill. Unlike simple ad blockers, uBlock Origin implements a multi-layered filtering architecture that processes network requests, modifies page content cosmetically, injects scriptlets to neutralize tracking scripts, and rewrites HTML before rendering. Every layer operates through purpose-built data structures optimized for minimal memory consumption and sub-millisecond matching latency, handling hundreds of thousands of filter rules simultaneously.
The extension is structured as a platform-abstracted core with browser-specific adapters. The filtering engines, data structures, UI modules, and serialization logic all live in src/js/, while platform/ contains per-browser entry points that adapt the shared core to each browser's extension API surface. This separation allows a single codebase to target Chrome (MV2), Firefox, Opera, Thunderbird, Safari, and the Manifest V3 variant (uBOL), as well as a headless Node.js package (@gorhill/ubo-core) for server-side filter matching.
The architecture prioritizes correctness and performance over developer convenience. Filter compilation transforms human-readable filter syntax into compact internal representations stored in custom trie structures. WASM modules accelerate hot paths in Firefox. A custom LZ4-based serialization system compresses engine state for fast persistence and restoration, avoiding the overhead of JSON serialization for the large filter datasets involved.
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ Browser Extension Host │
│ │
│ ┌──────────────────────── Background Page ───────────────────────┐ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ │ Asset │ │ Filter List │ │ Settings Manager │ │ │
│ │ │ Manager │ │ Compiler │ │ (user + advanced + │ │ │
│ │ │ (fetch, │ │ (text → │ │ managed storage) │ │ │
│ │ │ cache, │ │ internal │ └──────────────────────┘ │ │
│ │ │ update) │ │ repr) │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Filtering Engine Stack │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ │ Static Net Filter │ │ Cosmetic Filtering │ │ │ │
│ │ │ │ Engine │ │ Engine │ │ │ │
│ │ │ │ (biditrie-based) │ │ (CSS selectors, proc.) │ │ │ │
│ │ │ └────────────────────┘ └──────────────────────────┘ │ │ │
│ │ │ ┌────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ │ Dynamic Net Filter │ │ Scriptlet Filtering │ │ │ │
│ │ │ │ (per-host rules) │ │ Engine │ │ │ │
│ │ │ └────────────────────┘ └──────────────────────────┘ │ │ │
│ │ │ ┌────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ │ Dynamic URL Rule │ │ HTML Filtering │ │ │ │
│ │ │ │ Filtering │ │ (Firefox only) │ │ │ │
│ │ │ └────────────────────┘ └──────────────────────────┘ │ │ │
│ │ │ ┌────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ │ Redirect Engine │ │ HN Switches │ │ │ │
│ │ │ │ (surrogates) │ │ (per-host toggles) │ │ │ │
│ │ │ └────────────────────┘ └──────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ ┌─────────────┐ ┌──────────────────────┐ │ │
│ │ │ Serializer │ │ WASM Accel │ │ Data Structures │ │ │
│ │ │ (s14e + LZ4) │ │ (biditrie, │ │ (BidiTrie, HNTrie, │ │ │
│ │ │ │ │ hntrie, │ │ MRUCache, JSONPath) │ │ │
│ │ │ │ │ PSL, LZ4) │ │ │ │ │
│ │ └──────────────┘ └─────────────┘ └──────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ vAPI messaging │
│ │ │
│ ┌───────────────── Content Scripts ─────────────────────────────┐ │
│ │ contentscript.js contentscript-extra.js │ │
│ │ (DOM observation, (ProceduralFilterer, │ │
│ │ declarative cosmetic) 18+ PSelectorTask types) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────── UI Pages ────────────────────────────────┐ │
│ │ Popup Panel │ Dashboard (filters, lists, settings, logger) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Platform Adapters │
│ platform/chromium/ │ platform/firefox/ │ platform/opera/ │
│ platform/mv3/ │ platform/nodejs/ │ platform/thunderbird/ │
│ platform/safari/ │ platform/common/ │
└─────────────────────────────────────────────────────────────────────┘
Note
The architecture is deliberately modular — each filtering engine is independent and can be individually disabled, serialized, or tested in isolation. The
platform/nodejs/adapter exposes only the Static Net Filtering Engine, which is the largest and most complex component.
Component Inventory
| Component | Source Location | Responsibility |
|---|---|---|
| Static Net Filtering Engine | src/js/static-net-filtering.js |
URL-pattern matching with 60+ filter classes, trie-based |
| Cosmetic Filtering Engine | src/js/cosmetic-filtering.js |
CSS element hiding and injection |
| Scriptlet Filtering Engine | src/js/scriptlet-filtering-core.js, scriptlet-filtering.js |
JavaScript injection to neutralize trackers |
| Dynamic Net Filtering | src/js/dynamic-net-filtering.js |
Per-hostname allow/block matrix |
| Dynamic URL Rule Filtering | src/js/url-net-filtering.js |
Per-URL dynamic rules |
| HTML Filtering | src/js/html-filtering.js |
Pre-render HTML content modification (Firefox only) |
| Redirect Engine | src/js/redirect-engine.js |
Surrogate resource serving |
| HN Switches | src/js/hnswitches.js |
Per-hostname feature toggles |
| BidiTrieContainer | src/js/biditrie.js |
Bidirectional trie for fast string matching |
| HNTrieContainer | src/js/hntrie.js |
Hostname-specialized trie |
| MRUCache | src/js/mrucache.js |
Most-recently-used eviction cache |
| JSONPath | src/js/jsonpath.js |
JSON path expression evaluator |
| Serializer | src/js/s14e-serializer.js |
LZ4-compressed custom serialization |
| ProceduralFilterer | src/js/contentscript-extra.js |
Procedural cosmetic filtering orchestration |
| Platform Abstraction (vAPI) | platform/common/vapi-*.js |
Cross-browser API normalization |
| Node.js API | platform/nodejs/index.js |
Headless SNFE for server-side use |
Technology Stack
| Layer | Technology |
|---|---|
| Language | JavaScript (ES modules) |
| Extension API | WebExtensions (MV2), declarativeNetRequest (MV3) |
| WASM modules | biditrie, hntrie, publicsuffixlist, LZ4 (with JS fallback) |
| UI framework | Vanilla JS, CodeMirror 5.x (filter editor) |
| Compression | LZ4 (custom LZ4BlockJS class in s14e-serializer.js) |
| Bundled libraries | CodeMirror 5.x, csstree, diff, hsluv, js-beautify, lz4, publicsuffixlist, regexanalyzer |
| Linting | ESLint + globals (dev dependency) |
| License | GPLv3 |
Data Flow
A network request passes through filtering in this order:
- HN Switches — checked first; if a per-hostname switch (e.g., "no strict blocking") is set, it can modify subsequent engine behavior.
- Dynamic Net Filtering — per-hostname allow/block rules from the matrix UI. If a dynamic rule matches with an explicit allow or block, processing may short-circuit.
- Dynamic URL Rule Filtering — per-URL rules set via the logger UI.
- Static Net Filtering Engine — the main rule-matching engine. Compiled filter lists are evaluated against the request URL, origin, and type. This is where the bulk of the 300,000+ EasyList-derived rules execute.
- Redirect Engine — if the request is blocked but a redirect surrogate is available, the request is redirected to a neutered resource instead of being cancelled.
- Cosmetic Filtering Engine — injects CSS to hide elements matching cosmetic filter rules. Runs in content scripts.
- Scriptlet Filtering Engine — injects JavaScript snippets to defuse anti-adblock scripts and tracking mechanisms.
- HTML Filtering — on Firefox, modifies HTML response bodies before they reach the DOM renderer (using
filterResponseData()).
2. System Requirements
Browser Requirements
| Browser | Minimum Version | Extension Manifest | Notes |
|---|---|---|---|
| Google Chrome | 93.0 | MV2 | Primary development target |
| Firefox | Current ESR+ | MV2 | WASM enabled by default; HTML filtering supported |
| Opera | Current | MV2 | Uses Chromium adapter |
| Thunderbird | Current | MV2 | Email content filtering |
| Safari | Current | — | Platform adapter in platform/safari/ |
| Chrome (MV3) | — | MV3 | uBOL variant, uses declarativeNetRequest |
Browser Permissions
The Chromium MV2 manifest declares the following permissions:
| Permission | Purpose |
|---|---|
contextMenus |
Right-click menu integration for element blocking |
privacy |
Access to browser privacy settings (e.g., WebRTC leak prevention) |
storage |
Extension storage for settings and filter data |
tabs |
Tab URL access for per-site filtering decisions |
unlimitedStorage |
Large filter list datasets exceed default quotas |
webNavigation |
Page lifecycle tracking for cosmetic filter injection timing |
webRequest |
Network request interception (core blocking mechanism) |
webRequestBlocking |
Synchronous request blocking/modification |
<all_urls> |
Content script injection and request observation on all origins |
Important
The
<all_urls>andwebRequestBlockingpermissions are fundamental to uBlock Origin's operation. Enterprise administrators deploying via group policy should understand that these permissions grant the extension visibility into all network traffic within the browser. This is by design — a content blocker cannot function without observing what to block.
Node.js Requirements (npm package)
| Requirement | Version |
|---|---|
| Node.js (repository root tooling) | >= 22 |
Node.js (@gorhill/ubo-core npm package) |
>= 18 |
| npm package version | @gorhill/ubo-core v0.1.30 |
| Runtime dependencies | Zero (self-contained) |
3. Configuration Reference
uBlock Origin has three configuration layers: user settings (exposed in the dashboard), advanced settings (hidden, power-user), and managed storage (enterprise deployment). Managed storage takes precedence over user settings when deployed via browser policy.
Managed Storage Schema (Enterprise Deployment)
The managed storage schema is defined in platform/common/managed_storage.json and provides the mechanism for enterprise administrators to control uBlock Origin configuration across an organization. All keys are optional.
| Key | Type | Description |
|---|---|---|
adminSettings |
String (JSON) | A JSON string in uBlock Origin's backup format that overwrites all local settings. This is the nuclear option — it replaces the user's entire configuration. |
advancedSettings |
Array of [name, value] pairs |
Overrides specific advanced (hidden) settings. Each entry is a two-element array: ["settingName", "value"]. |
userSettings |
Array of [name, value] pairs |
Overrides specific user-facing settings. Same format as advancedSettings. |
disableDashboard |
Boolean | When true, prevents users from accessing the settings dashboard entirely. |
disabledPopupPanelParts |
Array of strings | Hides specific sections of the popup panel UI. |
toAdd |
Object | Additive configuration — merges with the user's existing settings rather than replacing them. |
toAdd.trustedSiteDirectives |
Array of strings | Domain names to add to the user's trusted sites (whitelist). Example: ["intranet.company.com", "wiki.company.com"] |
toOverwrite |
Object | Replacement configuration — completely overwrites the specified user settings. |
toOverwrite.filters |
Array of strings | Custom filter lines that replace the user's custom filters entirely. Each string is one filter rule. |
toOverwrite.filterLists |
Array of strings | Filter list identifiers or URLs that replace the user's subscribed filter lists. |
toOverwrite.trustedSiteDirectives |
Array of strings | Trusted site domains that replace the user's entire whitelist. |
Important
The
adminSettingskey accepts a JSON string (not an object) — it must be the serialized form of a uBlock Origin backup. UsingadminSettingsoverwrites everything, whiletoAddandtoOverwriteallow surgical modifications. PrefertoAddandtoOverwritefor maintainability.
Managed Storage Example (Chrome Enterprise)
{
"name": "uBlock Origin",
"description": "Enterprise-managed uBlock Origin configuration",
"type": "object",
"properties": {
"advancedSettings": {
"value": [
["userResourcesLocation", "https://internal.company.com/ubo-resources.txt"]
]
},
"disableDashboard": {
"value": false
},
"toAdd": {
"value": {
"trustedSiteDirectives": [
"intranet.company.com",
"*.internal.company.com",
"crm.company.com"
]
}
},
"toOverwrite": {
"value": {
"filterLists": [
"user-filters",
"ublock-filters",
"ublock-badware",
"ublock-privacy",
"ublock-unbreak",
"easylist",
"easyprivacy",
"urlhaus-1",
"plowe-0",
"https://internal.company.com/custom-blocklist.txt"
]
}
}
}
}
Advanced (Hidden) Settings
Advanced settings are accessible through the dashboard by enabling "I am an advanced user" and navigating to the advanced settings page. They are presented as a key-value editor in advanced-settings.js. These settings control internal engine behavior and are not intended for casual users.
Enterprise administrators can set advanced settings via managed storage using the advancedSettings key as an array of [name, value] pairs. The userResourcesLocation setting is particularly relevant for enterprise deployments, allowing administrators to specify internal URLs for custom scriptlet resources.
User Settings
User settings are the standard preferences exposed in the dashboard UI. Enterprise administrators can override them via managed storage using the userSettings key. See the User Guide for a description of each user-facing setting.
Filter List Management Configuration
Filter lists are organized into categories as defined in 3p-filters.js:
| Category ID | Description |
|---|---|
user |
User-defined custom filters |
default |
Default built-in lists (uBlock filters, EasyList, etc.) |
ads |
Advertising-focused lists |
privacy |
Privacy/tracking protection lists |
malware |
Malware domain lists |
multipurpose |
Multi-purpose lists |
cookies |
Cookie notice lists |
social |
Social media widget lists |
annoyances |
Annoyance lists (popups, newsletters, etc.) |
regions |
Regional/language-specific lists |
unknown |
Uncategorized lists |
custom |
User-imported external lists (by URL) |
Filter lists can be specified by their built-in identifier (e.g., easylist, easyprivacy, ublock-filters) or by full URL for custom lists. When deploying via managed storage, use the toOverwrite.filterLists array to define the exact set of lists.
4. Deployment & Scaling
Enterprise Deployment via Managed Storage
The primary enterprise deployment mechanism uses the browser's managed storage API, which allows organization administrators to push configuration through group policy (Windows), configuration profiles (macOS), or JSON policy files (Linux).
Chrome (Windows Group Policy):
- Install the Chrome ADMX templates.
- Navigate to
Computer Configuration > Administrative Templates > Google Chrome > Extensions. - Use "Configure extension management settings" to force-install uBlock Origin and provide managed storage JSON.
- The managed storage schema from
platform/common/managed_storage.jsondefines all accepted keys.
Chrome (Linux managed storage):
Place a JSON file at /etc/opt/chrome/managed_storage/cjpalhdlnbpafiamejdnhcphjbkeiagm.json (where the long string is the Chrome Web Store extension ID).
Firefox (Enterprise Policy):
Firefox uses the ExtensionSettings policy or managed-storage-manifests directory. The extension ID for Firefox is [email removed].
Note
The
disableDashboardmanaged storage key is particularly useful in kiosk or locked-down environments where end users should not modify content blocking behavior.
Group Policy / MDM Deployment Patterns
Common enterprise deployment patterns:
| Pattern | Approach | Keys Used |
|---|---|---|
| Lock down completely | Force-install extension, disable dashboard, overwrite all settings | adminSettings + disableDashboard: true |
| Allow user customization with baseline | Set baseline filter lists and trusted sites, leave dashboard open | toOverwrite.filterLists + toAdd.trustedSiteDirectives |
| Add internal sites to whitelist | Merge trusted sites without affecting user's custom config | toAdd.trustedSiteDirectives |
| Custom filter lists only | Replace all filter subscriptions with organization-approved lists | toOverwrite.filterLists |
| Hide popup panel sections | Simplify the UI for non-technical users | disabledPopupPanelParts |
MV3 Migration Considerations
uBlock Origin's MV3 variant, uBlock Origin Lite (uBOL), lives in platform/mv3/ and uses Chrome's declarativeNetRequest API instead of webRequest/webRequestBlocking.
Key architectural differences in MV3:
- No synchronous blocking —
declarativeNetRequestis declarative, not programmatic. The browser evaluates rules, not the extension. - Rule count limits — Chrome imposes static and dynamic rule count caps that constrain the number of filter rules uBOL can load simultaneously.
- No HTML filtering —
filterResponseData()is not available in MV3. - Reduced cosmetic filtering — procedural cosmetic filters have limited support.
- No redirect surrogates — the Redirect Engine operates differently under MV3 constraints.
Administrators planning for the Chrome MV3 transition should understand that uBOL is a functionally reduced version. Organizations requiring full filtering capabilities should evaluate Firefox or advocate for MV2 extension support in their browser policy.
Note
As of this writing, Chrome continues to support MV2 extensions for enterprise-managed installations. Administrators can use the
ExtensionManifestV2AvailabilityChrome policy to extend MV2 support.
5. Operations
Filter List Update Lifecycle
Filter list updates follow this cycle:
- Check interval — uBlock Origin periodically checks for updates to subscribed filter lists. The default interval is configurable through advanced settings.
- Fetch — Updated list content is downloaded from the list's URL.
- Compile — The raw filter text is compiled by
FilterCompilerinto the internal representation used by each engine. - Load — Compiled rules are loaded into the appropriate filtering engines (static net, cosmetic, scriptlet, etc.).
- Serialize — Engine state is serialized via
s14e-serializer.jsusing LZ4 compression and persisted to extension storage. - Cache — The raw list content and compiled state are cached. On subsequent browser starts, the extension deserializes cached state rather than recompiling from raw text.
The 3p-filters.js UI module provides the interface for managing filter list subscriptions, triggering manual updates, and searching across all available lists.
Tip
When deploying custom internal filter lists via managed storage, host them on an internal HTTPS endpoint with proper cache headers. The asset manager respects standard HTTP caching semantics during the fetch phase.
Asset Caching and Expiration
Assets (filter lists, redirect resources) are managed by the asset manager, which stores content in extension storage. Key behaviors:
- Compiled cache — Compiled filter state is persisted separately from raw list content. If the compiled cache is valid, the extension skips recompilation on startup.
- Selfie mechanism — The term "selfie" in the codebase refers to a serialized snapshot of the entire engine state. When the engine detects its selfie is invalid (e.g., after a list update), it logs "Bad selfie" and triggers full recompilation from raw filter text.
- Storage quotas — The
unlimitedStoragepermission ensures that large filter datasets (which can exceed 100 MB when compiled) do not hit browser storage limits.
Backup and Restore Procedures
uBlock Origin's backup system produces a JSON-formatted backup file containing all user settings, custom filters, trusted site directives, and filter list subscriptions. The backup format is the same format accepted by the adminSettings managed storage key.
Backup: Dashboard > Settings > "Backup to file" exports a JSON file.
Restore: Dashboard > Settings > "Restore from file" imports a previously exported backup.
For enterprise deployments, a backup file can be captured from a properly configured reference installation and distributed as the adminSettings value in managed storage. See the User Guide for step-by-step backup instructions.
Tip
Periodically export a backup from a reference installation and version-control it. This serves as both documentation of the approved configuration and a quick recovery mechanism.
Cloud Sync Architecture
The custom filters editor (1p-filters.js) includes cloud sync capability for synchronizing custom filter rules across devices. This uses the browser's built-in extension sync storage (browser.storage.sync). Cloud sync is limited by the browser's sync storage quotas (typically 100 KB total, 8 KB per item in Chrome).
6. Performance
Memory Architecture
uBlock Origin's memory efficiency is achieved through custom data structures that avoid the overhead of JavaScript objects for large datasets:
BidiTrieContainer (biditrie.js) — A bidirectional trie that stores filter patterns in a compact array-based format. Unlike a traditional trie that matches left-to-right, the bidirectional trie can match patterns from either direction, enabling efficient lookup for both prefix and suffix patterns. The trie uses typed arrays (Uint32Array) for the node table, avoiding the per-object overhead of JavaScript objects.
HNTrieContainer (hntrie.js) — A specialized trie for hostname matching. Hostnames are stored reversed (e.g., com.example instead of example.com) so that domain hierarchies naturally form trie branches. This enables efficient matching of hostname-based filter rules including wildcard domain patterns.
MRUCache (mrucache.js) — A most-recently-used cache used throughout the filtering pipeline to avoid redundant lookups. Recently evaluated URLs and their results are cached, so repeated requests to the same resource (common during page loads) resolve instantly.
Typed array storage — Both trie implementations store their data in typed arrays rather than linked node objects. This provides cache-friendly memory layout, eliminates GC pressure from millions of small objects, and enables direct serialization without traversal.
WASM Acceleration
WebAssembly modules accelerate the performance-critical trie operations:
| WASM Module | Source | Purpose |
|---|---|---|
src/js/wasm/biditrie.wasm |
Compiled from biditrie.wat |
Accelerates bidirectional trie lookups |
src/js/wasm/hntrie.wasm |
Compiled from hntrie.wat |
Accelerates hostname trie lookups |
src/lib/publicsuffixlist/wasm/publicsuffixlist.wasm |
— | Accelerates public suffix list lookups |
src/lib/lz4/ |
— | LZ4 compression/decompression with JS fallback |
WASM is enabled by default in Firefox. In Chrome, WASM usage requires the 'wasm-unsafe-eval' Content Security Policy directive, which may not be available in all extension contexts.
Note
When WASM modules are unavailable (due to CSP restrictions or platform limitations), the engines automatically fall back to pure JavaScript implementations. Performance is reduced but functionality is identical.
Filter Matching Performance Characteristics
The Static Net Filtering Engine processes requests through a multi-stage pipeline designed for early rejection:
- Token extraction — The request URL is tokenized. Tokens are substrings that serve as candidate lookup keys.
- Token hash lookup — Each token is hashed and looked up in a bucket map. Most tokens produce no match, allowing rapid rejection of the majority of filter rules.
- Bucket evaluation — When a token matches a bucket, the filters within that bucket are evaluated. Buckets may contain individual filters or nested
FilterBucket>FilterBucketIfstructures that apply additional preconditions (origin-based, regex-based) before evaluating their contents. - Pattern matching — Individual filter patterns are matched against the URL using the bidirectional trie.
- Domain/entity matching — Domain and entity restrictions are evaluated using the hostname trie.
This architecture means that the engine's performance scales with the number of distinct tokens, not the total number of filter rules. Adding more filter rules to existing token buckets has minimal marginal cost.
Cosmetic Filtering Overhead
Cosmetic filtering operates in content scripts and has a different performance profile than network filtering:
- Declarative cosmetic filters — Simple CSS selectors (e.g.,
##.ad-banner) are batched and injected as a single<style>element. This is handled by the browser's native CSS engine and has negligible overhead. - Procedural cosmetic filters — Complex selectors using
:has-text(),:if(),:xpath(), and other procedural operators require DOM traversal by theProceduralFilterer. These are more expensive and run viaMutationObserverto react to dynamic content changes.
Note
The procedural cosmetic filtering system (
contentscript-extra.js) implements 18+PSelectorTasksubtypes. Each task type has different performance characteristics.:xpath()tasks are typically the most expensive as they invoke the browser's XPath engine. Administrators managing custom filter lists should prefer simple CSS selectors where possible.
Optimization Strategies
For enterprise deployments managing performance:
- Reduce filter list count — Each additional filter list adds compilation time and memory. Only subscribe to lists relevant to your region and use case.
- Prefer static over dynamic rules — Dynamic net filtering rules (the per-hostname matrix) are evaluated sequentially. Static rules benefit from the trie-based acceleration.
- WASM in Firefox — For performance-critical deployments, Firefox's default WASM support provides measurable improvement in filter matching speed.
- Monitor via the logger — The network request logger (
logger-ui.js) shows which rules match each request, enabling identification of expensive or redundant rules.
7. Security
Permission Model
uBlock Origin requires broad permissions by necessity — a content blocker must observe and intercept network traffic to function. The security model is designed around minimizing the attack surface despite these broad permissions:
- No remote code execution — All filtering logic is bundled with the extension. Filter lists contain declarative rules, not executable code. Scriptlet injections are pre-defined within the extension, not fetched from filter lists.
- No external service dependencies — The extension does not phone home, does not have accounts, and does not transmit browsing data. Filter list fetches are the only network activity.
- Content script isolation — Content scripts run in an isolated world with no access to the page's JavaScript context. Communication with the background page uses the extension messaging API exclusively.
Warning
The
<all_urls>permission grants the extension access to observe all browser network traffic, including HTTPS requests. While uBlock Origin processes this data locally and does not transmit it, a compromised extension installation could theoretically exfiltrate browsing data. Always install from official distribution channels (Chrome Web Store, Firefox Add-ons) and verify that automatic updates are enabled.
Content Script Isolation
Content scripts (contentscript.js, contentscript-extra.js) operate under the browser's content script isolation model:
- Separate JavaScript context — Content scripts cannot access page JavaScript variables or functions, and vice versa.
- DOM access only — Content scripts can read and modify the DOM but cannot intercept the page's network requests directly.
- Messaging channel — All communication between content scripts and the background page uses
browser.runtime.sendMessage()/browser.runtime.onMessage, which is type-checked and validated.
Privileged Origin Validation
The vAPI background implementation (vapi-background.js) handles tab management, storage, and browser settings. Access to privileged browser APIs (privacy settings, context menus) is restricted to the background page context, which runs with the extension's full permission set.
Anti-Tampering Measures
- Selfie validation — Serialized engine state includes integrity markers. The "Bad selfie" error (logged when validation fails) triggers a full recompilation from source filter lists, preventing tampering with cached filter state.
- Filter compiler isolation — The
FilterCompilerprocesses raw filter text into an internal binary representation. Malformed filter rules are rejected during compilation rather than causing runtime errors. - Redirect resource validation — The
RedirectEngineonly serves redirect resources that are bundled with the extension. Filter lists can reference redirect targets by name, but cannot inject arbitrary content.
Managed Storage Security
Caution
The
adminSettingsmanaged storage key accepts a complete configuration backup as a JSON string. A malicious or poorly constructedadminSettingsvalue could disable all filtering, whitelist malicious domains, or remove all filter lists. Treat managed storage policy files with the same security controls as any other system configuration pushed via group policy.
When using managed storage for enterprise deployment:
- Restrict write access to managed storage policy files to authorized administrators only.
- Version-control policy files and review changes.
- Use
toAddandtoOverwritefor incremental changes rather thanadminSettingsfor full replacement, reducing the risk of accidentally clearing user protections. - Test policy changes against a reference installation before fleet-wide deployment.
8. Developer Internals
Repository Structure
gorhill/uBlock/
├── src/
│ ├── js/ # Core JavaScript (79 files)
│ │ ├── static-net-filtering.js # Static Net Filtering Engine (largest)
│ │ ├── cosmetic-filtering.js # Cosmetic Filtering Engine
│ │ ├── scriptlet-filtering-core.js # Scriptlet core
│ │ ├── scriptlet-filtering.js # Scriptlet engine extension
│ │ ├── dynamic-net-filtering.js # Dynamic per-host rules
│ │ ├── url-net-filtering.js # Dynamic URL rules
│ │ ├── html-filtering.js # HTML filtering (Firefox)
│ │ ├── redirect-engine.js # Redirect/surrogate engine
│ │ ├── hnswitches.js # Per-host toggle switches
│ │ ├── biditrie.js # BidiTrieContainer
│ │ ├── hntrie.js # HNTrieContainer
│ │ ├── mrucache.js # MRU cache
│ │ ├── jsonpath.js # JSON path evaluator
│ │ ├── s14e-serializer.js # LZ4 serialization
│ │ ├── contentscript-extra.js # Procedural cosmetic filtering
│ │ ├── popup-fen498.js # Popup panel UI
│ │ ├── 1p-filters.js # Custom filters editor
│ │ ├── 3p-filters.js # Filter list management
│ │ ├── advanced-settings.js # Hidden settings editor
│ │ ├── logger-ui.js # Network request logger
│ │ ├── asset-viewer.js # Filter list viewer
│ │ ├── about.js # Version info
│ │ └── wasm/ # WASM modules + .wat sources
│ ├── css/ # Stylesheets
│ ├── lib/ # Bundled third-party libraries
│ │ ├── codemirror/ # CodeMirror 5.x (filter editor)
│ │ ├── csstree/ # CSS parser
│ │ ├── diff/ # Text diff
│ │ ├── hsluv/ # Color space
│ │ ├── js-beautify/ # Code formatting
│ │ ├── lz4/ # LZ4 codec (WASM + JS fallback)
│ │ ├── publicsuffixlist/ # PSL library (WASM accelerated)
│ │ └── regexanalyzer/ # Regex analysis
│ ├── img/ # Icons and images
│ └── *.html # UI pages (popup, dashboard)
├── platform/
│ ├── common/ # Shared platform code
│ │ ├── vapi-background.js # Background page API abstraction
│ │ ├── vapi-client.js # Content script messaging
│ │ ├── vapi-common.js # Shared utilities
│ │ └── managed_storage.json # Enterprise config schema
│ ├── chromium/ # Chrome MV2 adapter
│ │ ├── manifest.json # Extension manifest
│ │ ├── webext.js # Promisified browser API
│ │ └── vapi-background-ext.js
│ ├── firefox/ # Firefox adapter
│ ├── opera/ # Opera adapter
│ ├── thunderbird/ # Thunderbird adapter
│ ├── safari/ # Safari adapter
│ ├── mv3/ # Manifest V3 (uBOL)
│ └── nodejs/ # Node.js headless API
│ └── index.js # @gorhill/ubo-core entry point
└── .eslintrc.yml # ESLint configuration
Module Reference (HIGH-Priority Modules)
static-net-filtering.js — Static Net Filtering Engine
The largest and most complex module. Contains the FilterCompiler class for converting text rules into internal representation, and 60+ Filter* classes organized in a deep hierarchy for matching network requests against compiled rules. The engine uses BidiTrieContainer for pattern storage and lookup.
cosmetic-filtering.js — Cosmetic Filtering Engine
Manages CSS-based element hiding. Processes cosmetic filter rules (those starting with ## or #@#) and generates per-page stylesheets injected via content scripts. Maintains a "selfie" (serialized snapshot) for fast startup; emits "Bad selfie" on corruption.
scriptlet-filtering-core.js / scriptlet-filtering.js
Two-layer design: ScriptletFilteringEngine (core) provides the base scriptlet matching and injection logic; ScriptletFilteringEngineEx (extension) adds browser-specific integration. Scriptlets are pre-defined JavaScript snippets that neutralize tracking scripts and anti-adblock mechanisms.
dynamic-net-filtering.js — DynamicHostRuleFiltering
Implements the per-hostname allow/block matrix accessible from the popup panel's advanced mode. Rules are stored as a flat structure keyed by {source-hostname, destination-hostname, request-type} tuples.
biditrie.js — BidiTrieContainer
The core data structure for the Static Net Filtering Engine. Implements a bidirectional trie using typed arrays (Uint32Array). Supports both forward and reverse pattern matching, enabling efficient evaluation of both prefix-anchored and suffix-anchored filter patterns. Has a corresponding WASM implementation for acceleration.
hntrie.js — HNTrieContainer
Hostname-specialized trie that stores reversed hostnames for hierarchical matching. Used by domain-based filter classes (FilterFromDomainHit, FilterToDomainHitSet, etc.) to efficiently evaluate domain restrictions on filter rules.
s14e-serializer.js — Serialization
Custom serialization framework with built-in LZ4 compression via the LZ4BlockJS class. Used to persist and restore engine state across browser sessions. The "s14e" name is an abbreviation of "serializer" (s + 14 letters + e).
contentscript-extra.js — Procedural Cosmetic Filtering
Implements the ProceduralFilterer and 18+ PSelectorTask subtypes for procedural cosmetic filtering. Procedural filters extend CSS selector syntax with operations like :has-text(), :if(), :matches-css(), :xpath(), and :upward().
platform/common/vapi-background.js — vAPI Background
The platform abstraction layer for the background page. Provides a unified API surface (vAPI) for storage, tabs, messaging, browser settings, and extension lifecycle across all supported browsers. Each browser platform extends or overrides specific vAPI methods.
Filter Class Hierarchy Deep Dive
The Static Net Filtering Engine's power comes from its extensive filter class hierarchy. Each class represents a specific type of filter rule optimization:
Filter (base)
├── FilterPatternPlain
│ ├── FilterPatternPlain1 # Single-character patterns
│ └── FilterPatternPlainX # Multi-character patterns
├── FilterDomainHit # Domain restriction base
│ ├── FilterFromDomainHit # Match if request FROM domain
│ ├── FilterFromDomainMiss # Match if request NOT from domain
│ ├── FilterFromEntityHit # Match entity (domain+TLD)
│ ├── FilterFromEntityMiss # Negate entity match
│ ├── FilterToDomainHit # Match if request TO domain
│ └── FilterToDomainMiss # Match if request NOT to domain
├── FilterDomainHitSet # Set-based domain matching
│ ├── FilterFromDomainHitSet # FROM any domain in set
│ ├── FilterToDomainHitSet # TO any domain in set
│ ├── FilterFromDomainMissSet # NOT from any domain in set
│ └── FilterToDomainMissSet # NOT to any domain in set
├── FilterCollection # Container for multiple filters
│ ├── FilterDomainHitAny # Match any domain in collection
│ ├── FilterCompositeAll # All sub-filters must match (AND)
│ └── FilterBucket # Token-indexed bucket of filters
│ └── FilterBucketIf # Conditional bucket
│ ├── FilterBucketIfOriginHits # Evaluate only if origin matches
│ └── FilterBucketIfRegexHits # Evaluate only if regex matches
└── FilterCompiler # Compiles text → internal repr
FilterBucket optimization — When a token bucket accumulates enough filters, the engine promotes it from a flat FilterBucket to a FilterBucketIf with a precondition check. This avoids evaluating the entire bucket when the precondition (origin hostname or regex) does not match, significantly reducing per-request evaluation cost for heavily populated buckets.
FilterCompositeAll — Represents a filter with multiple constraints that must all match (e.g., a pattern with both domain and type restrictions). The sub-filters are evaluated in order of expected rejection rate, with the cheapest check first.
Static Net Filtering Compilation Pipeline
The journey from a text filter rule to a matched network request:
Raw filter text (e.g., "||example.com^$third-party")
│
▼
FilterCompiler.compile()
│ Parse syntax: anchors, options, pattern extraction
│ Validate: reject malformed rules
│ Classify: determine filter type, domain restrictions, options
│
▼
Internal representation (tokenized, typed)
│
▼
Engine.fromCompiled()
│ Instantiate appropriate Filter* class
│ Extract tokens for bucket assignment
│ Insert into token→bucket map
│
▼
BidiTrieContainer
│ Store pattern in bidirectional trie
│ Share trie nodes across filters with common prefixes/suffixes
│
▼
Ready for matching
│
▼
Engine.matchRequest({url, originURL, type})
│ Tokenize request URL
│ Look up each token in bucket map
│ Evaluate matching buckets (BucketIf preconditions first)
│ Return: 0 (no match), 1 (block), 2 (allow/exception)
Procedural Cosmetic Filtering
The ProceduralFilterer in contentscript-extra.js orchestrates complex element hiding that cannot be expressed with pure CSS selectors. Each procedural filter is a chain of PSelectorTask instances:
| Task Class | Selector Syntax | Function |
|---|---|---|
PSelectorHasTextTask |
:has-text(regex) |
Match elements containing text |
PSelectorIfTask |
:if(selector) |
Conditional selector (alias for :has()) |
PSelectorIfNotTask |
:if-not(selector) |
Negative conditional |
PSelectorMatchesCSSTask |
:matches-css(prop: value) |
Match computed CSS properties |
PSelectorMatchesCSSBeforeTask |
:matches-css-before(...) |
Match ::before pseudo-element CSS |
PSelectorMatchesCSSAfterTask |
:matches-css-after(...) |
Match ::after pseudo-element CSS |
PSelectorMinTextLengthTask |
:min-text-length(n) |
Match elements with minimum text length |
PSelectorSpathTask |
:spath(selector) |
Apply subsequent CSS selector |
PSelectorUpwardTask |
:upward(n or selector) |
Traverse up n ancestors or to matching ancestor |
PSelectorXpathTask |
:xpath(expr) |
Apply XPath expression |
PSelectorWatchAttrs |
:watch-attr(attr1, attr2) |
Re-evaluate when attributes change |
PSelectorOthersTask |
:others(...) |
Match other elements relative to current |
PSelectorStyleTask |
:style(declarations) |
Apply inline styles instead of hiding |
The ProceduralFilterer uses MutationObserver to watch for DOM changes and re-evaluate procedural filters when new elements are added or attributes change.
Code Patterns and Conventions
ES Modules — The codebase uses ES module syntax (import/export) throughout src/js/. Modules are loaded as type="module" in HTML pages or via dynamic import().
vAPI pattern — All browser-specific functionality is accessed through the vAPI global object, which is populated by the platform-specific vapi-*.js files. Code in src/js/ never directly calls browser.* or chrome.* APIs.
Singleton engines — The filtering engines are singletons. The StaticNetFilteringEngine constructor enforces single-instance via an explicit check: "Only a single instance is supported."
Typed array preference — Performance-critical data structures use Uint32Array, Uint8Array, and ArrayBuffer rather than JavaScript objects or Map instances.
Self-descriptive IDs — Filter classes use numeric type IDs for serialization. The mapping between class and ID is maintained within static-net-filtering.js.
Testing
The @gorhill/ubo-core npm package (at platform/nodejs/) includes a test suite for the Static Net Filtering Engine. Tests validate filter compilation, matching correctness, serialization round-trips, and the removeparam (query filtering) functionality.
Run tests with:
cd platform/nodejs
npm test
The root package.json requires Node.js >= 22 for development tooling, while the npm package itself requires Node.js >= 18.
Build Process
uBlock Origin does not use a traditional bundler (no webpack, rollup, or vite). The extension is structured to run directly from source:
- Development — Load the
platform/chromium/(orplatform/firefox/, etc.) directory as an unpacked extension in the browser. - Release packaging — Platform-specific packaging scripts create
.zipor.xpifiles for distribution. Theplatform/mv3/directory contains additional build tooling for the Manifest V3 variant. - WASM compilation —
.wat(WebAssembly Text) files insrc/js/wasm/are compiled to.wasmusingwat2wasmfrom the WebAssembly Binary Toolkit.
Contributing
Contributions follow standard GitHub fork-and-PR workflow. The repository uses ESLint (configured in .eslintrc.yml) with the globals package for environment-aware linting. There are no pre-commit hooks or CI pipelines defined in the repository root.
The canonical issue tracker has moved — see GitHub issue #3726, which directs all new issues to https://github.com/uBlockOrigin/uBlock-issues/issues.
9. Integration Points
@gorhill/ubo-core Node.js API
The @gorhill/ubo-core npm package (v0.1.30) exposes the Static Net Filtering Engine for server-side use. It is a zero-dependency package requiring Node.js >= 18.
Entry point: platform/nodejs/index.js
Exports:
enableWASM()— Enable WASM acceleration for the enginepslInit(raw)— Initialize the public suffix list from raw dataStaticNetFilteringEngine— The main engine class
StaticNetFilteringEngine Complete Reference
import { StaticNetFilteringEngine } from '@gorhill/ubo-core';
// Create a singleton instance (initializes public suffix list)
const engine = await StaticNetFilteringEngine.create({ noPSL: false });
// Load filter lists (resets engine state first)
await engine.useLists([
{ name: 'easylist', raw: '... filter list content ...' },
{ name: 'easyprivacy', raw: '... filter list content ...' },
]);
// Match a network request against loaded filters
// Returns: 0 (no match), 1 (block), 2 (allow/exception)
const result = engine.matchRequest({
url: 'https://ads.example.com/tracker.js',
originURL: 'https://www.example.com/',
type: 'script',
});
// Check if last block result was flagged as "important"
const isImportant = engine.isBlockImportant();
// Get log data for the last match (for diagnostics)
const logData = engine.toLogData();
// Match and fetch modifiers (e.g., for CSP, redirect, removeparam)
const modifiers = engine.matchAndFetchModifiers(
{ url: '...', originURL: '...', type: '...' },
'csp' // modifier name
);
// Check if URL has filterable query parameters
const hasQuery = engine.hasQuery({
url: 'https://example.com/page?utm_source=tracker',
originURL: 'https://example.com/',
type: 'main_frame',
});
// Filter (remove) tracked query parameters from URL
const filtered = engine.filterQuery({
url: 'https://example.com/page?utm_source=tracker&id=123',
originURL: 'https://example.com/',
type: 'main_frame',
});
// filtered.url contains the cleaned URL
// Compile a filter list from text
const compiler = engine.createCompiler(parser);
const writer = /* ... */;
engine.compileList(
{ name: 'custom', raw: '||example.com^\n@@||example.com/allowed' },
compiler,
writer,
{} // options
);
// Serialize engine state (LZ4 compressed)
const serialized = engine.serialize();
// Restore engine from serialized state
engine.deserialize(serialized);
// Release the singleton instance
StaticNetFilteringEngine.release();
Note
The
StaticNetFilteringEngineenforces a singleton pattern. Callingcreate()when an instance already exists throws"Only a single instance is supported.". Callrelease()before creating a new instance.
Match Result Codes
| Code | Meaning |
|---|---|
0 |
No match — request is not affected by any filter rule |
1 |
Block — request matches a blocking filter |
2 |
Allow — request matches an exception (allowlist) filter |
Request Type Values
The type field in matchRequest() corresponds to browser webRequest resource types: main_frame, sub_frame, stylesheet, script, image, font, object, xmlhttprequest, ping, csp_report, media, websocket, other.
WASM Acceleration API
import { enableWASM } from '@gorhill/ubo-core';
// Enable WASM before creating the engine
const wasmEnabled = await enableWASM();
// Returns true if WASM modules loaded successfully, false otherwise
// Then create the engine — it will use WASM-accelerated trie operations
const engine = await StaticNetFilteringEngine.create();
WASM modules are loaded from the same directory as the JavaScript modules. In the browser extension context, WASM loading is handled automatically by the platform adapter. In Node.js, enableWASM() must be called explicitly before engine creation.
Browser Extension Messaging Protocol
Content scripts communicate with the background page through the browser's extension messaging API, abstracted by vAPI:
Content script to background (vapi-client.js):
vAPI.messaging.send(channel, message)— Send a message to the background page on a named channel and await a response.
Background to content script (vapi-background.js):
- Tab-targeted messaging for injecting cosmetic filter updates, scriptlet payloads, and configuration changes.
Message channels are used to route messages to the appropriate handler in the background page. Each UI page (popup, dashboard, logger) uses its own channel, and content scripts use dedicated channels for cosmetic filtering, scriptlet injection, and DOM surveying.
The platform-specific implementations (platform/chromium/webext.js, etc.) handle the differences between Chrome's callback-based API and Firefox's Promise-based API. The Chromium adapter (webext.js) wraps Chrome's callback-style chrome.* APIs in Promises for consistency with the Firefox browser.* API.
10. Troubleshooting
Error Messages Reference
The following error messages are defined in the uBlock Origin source code. Their presence in browser console logs indicates specific failure conditions:
| Error Message | Source File | Cause & Resolution |
|---|---|---|
"Pending useLists() operation" |
platform/nodejs/index.js:161 |
A useLists() call was made while a previous call is still in progress. Wait for the previous operation to complete before calling useLists() again. |
"Only a single instance is supported." |
platform/nodejs/index.js:210 |
Attempted to create a second StaticNetFilteringEngine instance. Call StaticNetFilteringEngine.release() before creating a new instance. |
"Failed to initialize public suffix list." |
platform/nodejs/index.js:268 |
The public suffix list could not be loaded during engine creation. Ensure the PSL data file is accessible, or pass { noPSL: true } to create() if PSL is not needed. |
"Invalid µBlock.denseBase64 encoding" |
src/js/base64-custom.js:96 |
Corrupted or truncated base64-encoded data encountered during deserialization. This typically indicates corrupted extension storage. Clear the extension's storage and restart. |
"Bad selfie" |
src/js/cosmetic-filtering.js:572, src/js/static-ext-filtering-db.js:232 |
Serialized engine state failed integrity validation. The engine will automatically recompile from raw filter lists. No manual intervention required unless the error recurs on every startup, which indicates persistent storage corruption. |
"Unmatched bracket" |
src/js/regex-analyzer.js:184 |
A filter rule contains an invalid regular expression with an unmatched bracket. Check custom filter rules for syntax errors. |
Tip
The "Bad selfie" error is recoverable — the engine rebuilds itself automatically. If you see this message once after an extension update, it is normal behavior. Persistent "Bad selfie" errors on every startup indicate corrupted extension storage that should be cleared.
Common Issues from GitHub
The following open issues in the gorhill/uBlock repository document known behaviors and long-standing feature requests:
| Issue | Status | Summary |
|---|---|---|
| #3726 | Open | Issue tracker has moved to uBlockOrigin/uBlock-issues. New bugs should be filed there. |
| #3178 | Open | Feature request: way to disable blocking temporarily (per-tab or global). |
| #3604 | Open | Feature request: block sending Referrer information. |
| #3348 | Open | "Create" filter button disabled in element picker under certain conditions. |
| #2034 | Open | Show unused hostname(s) between a hostname and its base domain in the dynamic filtering matrix. |
| #2035 | Open | Ability to select hostname in element picker. |
| #2000 | Open | Element picker should not prevent page from receiving mouse events. |
| #614 | Open | Disable tooltip should be Cmd+click on Mac. |
| #544 | Open | Addressing inconsistencies in the dynamic rule matrix UI. |
Tip
If you encounter a bug, file it at the current issue tracker:
https://github.com/uBlockOrigin/uBlock-issues/issues(not the main repository).
Diagnostic Procedures
Extension not blocking requests:
- Check the popup panel — is uBlock Origin enabled for the current site? The power button toggles per-site filtering.
- Check trusted site directives — the site may be whitelisted via managed storage (
toAdd.trustedSiteDirectives) or user settings. - Open the network request logger (
logger-ui.js) and reload the page. Examine whether requests are being matched. - Verify filter lists are loaded — open the filter list management page (
3p-filters.js) and check for compilation errors.
High memory usage:
- Check which filter lists are subscribed — more lists means more memory. Regional lists add significant overhead.
- Verify WASM is active (Firefox) — WASM trie implementations are more memory-efficient than the JS fallback.
- Check for excessive custom filter rules — large custom rule sets that do not benefit from trie compression (e.g., many regex-based rules) consume more memory per rule.
Extension storage corruption:
- Symptoms: persistent "Bad selfie" errors, filter lists failing to load, settings reverting.
- Export a backup of custom filters and settings via the dashboard.
- Clear the extension's storage (browser settings > extension details > clear data).
- Reimport the backup.
- For enterprise deployments, the managed storage configuration will be re-applied automatically after storage is cleared.