How uBlock Origin Uses Hostname Switches for Per-Site Configuration
uBlock Origin implements per-site configuration by storing blocking preferences as 32-bit bit-fields called hostname switches, which are evaluated through a hierarchical domain lookup that inherits settings from parent domains unless overridden.
uBlock Origin's granular control over filtering behavior relies on hostname switches, a compact configuration system that attaches per-site settings directly to domain names. These switches enable users to override global rules for specific sites while automatically propagating settings to subdomains. According to the gorhill/uBlock source code, this mechanism is implemented in src/js/hnswitches.js using a DynamicSwitchRuleFiltering class that manages bit-field storage and hierarchical lookups.
The Hostname Switch Data Structure
The core of the system resides in the DynamicSwitchRuleFiltering class, which maintains a Map of hostnames to 32-bit integers representing switch states.
The 32-Bit Bit-Field Implementation
In src/js/hnswitches.js, the constructor initializes this.switches as a Map where each hostname maps to a 32-bit integer:
class DynamicSwitchRuleFiltering {
constructor() {
this.reset();
}
reset() {
// Map: hostname → 32-bit integer holding the bits for all switches
this.switches = new Map();
…
}
…
}
Each switch consumes two bits within this integer to represent three possible states: 0 (inherit from parent), 1 (enabled), or 2 (forced off). This compact representation allows a single 32-bit value to store the state of up to 16 different switches per hostname.
Switch Offset Definitions
The bit positions for each switch are defined in the switchBitOffsets object:
const switchBitOffsets = {
'no-strict-blocking': 0,
'no-popups': 2,
'no-cosmetic-filtering': 4,
'no-remote-fonts': 6,
'no-large-media': 8,
'no-csp-reports': 10,
'no-scripting': 12,
};
Available switches include no-scripting, no-cosmetic-filtering, no-large-media, and no-popups, among others. Each offset indicates the starting bit position for that switch's 2-bit state field.
Hierarchical Evaluation Algorithm
When uBlock Origin needs to determine if a switch applies to a specific site, it invokes the evaluateZ() method, which performs a bottom-up traversal of the domain hierarchy.
The evaluateZ Method
The evaluateZ() method in src/js/hnswitches.js accepts a switchName and hostname, then walks the domain tree from most specific to least specific:
evaluateZ(switchName, hostname) {
const bitOffset = switchBitOffsets[switchName];
…
for (const shn of decomposeHostname(hostname, decomposedSource)) {
let bits = this.switches.get(shn);
if (bits === undefined) { continue; }
bits = bits >>> bitOffset & 3;
if (bits === 0) { continue; } // inherit → keep walking
this.z = shn; // the hostname that decided
this.r = bits;
return bits === 1; // true if enabled
}
this.r = 0;
return false;
}
The method returns true if the switch is enabled, false if disabled or not found, and stores the matching hostname in this.z for reference.
Domain Decomposition Strategy
The lookup relies on decomposeHostname() from src/js/uri-utils.js, which transforms a hostname like sub.example.com into an array ["sub.example.com", "example.com", "*"]. This enables the switch manager to check for rules at the exact subdomain, the parent domain, and finally the global wildcard level, ensuring inheritance works correctly throughout the domain tree.
Integration with Filtering Engines
Hostname switches integrate with uBlock Origin's primary filtering components to enforce per-site policies at both the network and page levels.
Network Request Filtering
In src/js/ublock.js, the engine queries sessionSwitches.evaluateZ() to determine whether to block network requests based on site-specific rules:
if (sessionSwitches.evaluateZ(details.name, details.hostname) === false) {
// request is allowed
}
This check enables features like no-large-media blocking or no-remote-fonts restrictions to be applied selectively per domain.
Page-Level Policy Enforcement
The src/js/pagestore.js module uses the same evaluation method to control page-wide behaviors such as cosmetic filtering and script execution:
if (sessionSwitches.evaluateZ('no-large-media', fctxt.getTabHostname()) !== true) {
// allow large media on this site
}
These checks occur during page loading, allowing uBlock Origin to disable specific filtering engines for sites where they might break functionality.
Managing Switch States
Users manipulate hostname switches through the popup UI or dashboard, which trigger specific toggle methods that handle both immediate and persistent storage.
Toggle Operations and Deep Propagation
The toggleZ() method in src/js/hnswitches.js provides the interface for changing switch states:
toggleZ(switchName, hostname, deep, newState) {
if (deep) {
return this.toggleBranchZ(switchName, hostname, newState);
}
return this.toggleOneZ(switchName, hostname, newState);
}
When the deep parameter is true, the method invokes toggleBranchZ(), which applies the change to the target hostname and clears any descendant overrides. This ensures the new state propagates correctly to all subdomains without conflicting legacy rules.
Persistence: Session vs. Permanent Storage
uBlock Origin maintains two instances of the switch manager:
permanentSwitches: Stored insrc/js/filtering-engines.jsat line 23, persisted across browser sessionssessionSwitches: Stored in the same file at line 34, held in memory only and cleared on restart
At startup, src/js/start.js (line 344) merges permanent rules into the active session:
sessionSwitches.assign(permanentSwitches);
This architecture allows users to test temporary configurations without affecting their saved preferences.
Practical Implementation Examples
Enabling No-Scripting for a Specific Domain
To programmatically enable the no-scripting switch for example.com:
import sessionSwitches from './filtering-engines.js';
// Enable no-scripting for example.com (1 == true)
sessionSwitches.toggle('no-scripting', 'example.com', 1);
This immediately blocks JavaScript execution on the specified domain.
Querying Switch State Programmatically
To check if a switch is active for the current tab:
import { hostnameFromURI } from './uri-utils.js';
import sessionSwitches from './filtering-engines.js';
function isNoScriptingAllowed(tabUrl) {
const hn = hostnameFromURI(tabUrl);
return sessionSwitches.evaluateZ('no-scripting', hn);
}
// Usage
if (isNoScriptingAllowed('https://sub.example.com/page')) {
console.log('Scripts are blocked on this site');
}
Configuring Deep Subtree Rules
To apply a switch change to an entire domain tree, removing any conflicting subdomain rules:
// Turn off "no-cosmetic-filtering" for the entire example.com tree
sessionSwitches.toggleZ('no-cosmetic-filtering', 'example.com', true, false);
The true flag ensures the change propagates to all subdomains by clearing their existing overrides.
Summary
- Hostname switches in uBlock Origin are stored as 32-bit bit-fields in a Map structure, with each switch occupying 2 bits to represent inherit, enabled, or forced-off states.
- The
evaluateZ()method performs hierarchical lookups usingdecomposeHostname(), checking from the specific subdomain up to the wildcard level to respect inheritance rules. - Two storage instances exist:
permanentSwitchesfor persistent configuration andsessionSwitchesfor temporary in-memory rules. - The
toggleZ()method supports deep propagation, which clears descendant overrides when applying changes to parent domains. - Integration occurs in
src/js/ublock.jsfor network filtering andsrc/js/pagestore.jsfor page-level policies like cosmetic filtering and script blocking.
Frequently Asked Questions
What is a hostname switch in uBlock Origin?
A hostname switch is a per-site configuration mechanism that stores blocking preferences (like disabling scripts or cosmetic filters) as a bit-field attached to a specific domain name. According to the source code in src/js/hnswitches.js, each switch uses 2 bits within a 32-bit integer to represent whether the feature is inherited, enabled, or forced off for that hostname.
How does uBlock Origin handle switch inheritance across subdomains?
When evaluating a switch, uBlock Origin calls decomposeHostname() from src/js/uri-utils.js to generate a hierarchy from the specific subdomain to the root, including a wildcard. The evaluateZ() method walks this array from most specific to least specific, using the first non-inherited value it encounters. This allows parent domain rules to automatically apply to subdomains unless explicitly overridden.
What is the difference between sessionSwitches and permanentSwitches?
permanentSwitches persists user configurations across browser restarts in src/js/filtering-engines.js, while sessionSwitches exists only in memory and resets when the browser closes. At startup, src/js/start.js merges permanent rules into the session via sessionSwitches.assign(), allowing users to test temporary changes without modifying saved settings.
How do deep toggle operations work in the hostname switch system?
When calling toggleZ() with deep set to true, the system invokes toggleBranchZ(), which applies the switch state to the target hostname and recursively clears any conflicting states from descendant domains. This ensures clean inheritance throughout the domain tree, preventing stale subdomain overrides from interfering with the new parent-level policy.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →