This page documents ScriptletFilteringEngineEx, the MV2 runtime component responsible for matching scriptlet filter rules to page hostnames and injecting the resulting JavaScript into web pages. It covers the MRU cache, the persistent content script registration mechanism, broadcast-driven cache invalidation, and the two-world (main/isolated) injection model.
For the redirect resource system that provides scriptlet source code, see Redirect Engine. For the dispatch layer that routes scriptlet filter rules from compiled lists into this engine, see Static Extended Filtering. For the MV3 equivalent, see MV3 Runtime System.
Scriptlet filters have the syntax example.com##+js(scriptlet-name, arg1, arg2). When filter lists are compiled, staticExtFilteringEngine in src/js/static-ext-filtering.js detects scriptlet rules via parser.isScriptletFilter() and routes them to the scriptlet engine's compile() method. At page-load time, the engine matches compiled rules against the requesting hostname and injects the resulting JavaScript before page scripts execute.
Scriptlet filter rule flow through the system:
Sources: src/js/scriptlet-filtering.js1-395 src/js/static-ext-filtering.js92-136
ScriptletFilteringEngineEx extends ScriptletFilteringEngine from scriptlet-filtering-core.js. The base class handles filter storage, hostname matching, and scriptlet code assembly. The extended class adds:
browser.contentScriptsBroadcastChannelSources: src/js/scriptlet-filtering.js188-386
contentScriptRegistererThis module-level object manages persistent scriptlet registrations using the browser.contentScripts API, which makes the browser automatically re-inject the script on subsequent navigations to the same hostname without requiring another background round-trip.
| Method | Behavior |
|---|---|
register(hostname, code) | Calls browser.contentScripts.register() for *://*.hostname/*. Returns true if already registered with identical code (skip re-injection). |
unregister(hostname) | Removes and unregisters the script for an exact hostname. |
flush(hostname) | Removes the hostname and all subdomains (e.g., flush('example.com') also removes sub.example.com). |
reset() | Unregisters all tracked hostnames. |
The hostnameToDetails Map stores { id, handle, code } per hostname. The id counter guards against race conditions where a re-registration completes before the previous one's Promise resolves.
Sources: src/js/scriptlet-filtering.js40-106
retrieve() — Cache and Assembly Logicretrieve(request) is the main lookup method. It is called by injectNow() for every frame navigation.
The options object passed to super.retrieve() includes:
| Option | Source | Purpose |
|---|---|---|
scriptletGlobals.warOrigin | vAPI.getURL('/web_accessible_resources') | Base URL for WAR fetch inside scriptlets |
scriptletGlobals.warSecret | vAPI.warSecret.long() | Authenticates WAR requests |
scriptletGlobals.bcSecret | vAPI.generateSecret(3) | Channel name for logger comms (logger only) |
scriptletGlobals.logLevel | this.logLevel | Controls scriptlet log verbosity (logger only) |
debug | vAPI.webextFlavor.soup.has('devbuild') | Enables debug output in scriptlets |
debugScriptlets | µb.hiddenSettings.debugScriptlets | Per-scriptlet debug mode |
Sources: src/js/scriptlet-filtering.js253-325
injectNow() — Injection Entry PointinjectNow(details) is the public method called from traffic handling when a frame navigates. It combines the assembled script code with logger infrastructure and dispatches to the browser.
Sources: src/js/scriptlet-filtering.js327-370
Scriptlet resources (defined in src/js/resources/scriptlets.js and loaded by RedirectEngine) may specify a target execution world:
| World | Assembler | Purpose |
|---|---|---|
MAIN (default) | vAPI.scriptletsInjector(hostname, scriptletDetails) | Runs in page JS context; can access/modify page globals |
ISOLATED | isolatedWorldInjector.assemble(hostname, scriptletDetails) | Runs in extension context; cannot access page globals directly |
isolatedWorldInjector src/js/scriptlet-filtering.js110-143 wraps the scriptlet function body in an IIFE that:
self.uBO_isolatedScriptlets to prevent double-executiondocument.location.hostname matches the intended hostnameself.uBO_isolatedScriptlets = 'done' after runningBoth world outputs are joined into a single code string. If both are present, they are separated by a blank line.
Sources: src/js/scriptlet-filtering.js110-143 src/js/scriptlet-filtering.js306-312
When logger.enabled is true, the engine:
bcSecret string (3-segment random secret) used as a BroadcastChannel name.onScriptletMessageInjector.assemble(scriptletDetails) to the injected code src/js/scriptlet-filtering.js145-184 This code, running in the page, creates a BroadcastChannel(bcSecret) and forwards messages from scriptlets to vAPI.messaging.bcSecret and logLevel as scriptletGlobals so the scriptlet code knows the channel name.toLogger() src/js/scriptlet-filtering.js372-385 writes matched filter entries to the uBlock logger using µb.filteringContext with realm 'extended' and type 'scriptlet'.The logLevel can be updated at runtime via the loggerLevelChanged broadcast. When it changes, the engine injects scriptlet-loglevel-N.js into all open tabs to synchronize their log verbosity.
Sources: src/js/scriptlet-filtering.js145-184 src/js/scriptlet-filtering.js215-233 src/js/scriptlet-filtering.js372-385
The engine subscribes to the global broadcast bus (onBroadcast from broadcast.js) in its constructor. The table below lists all broadcast messages handled:
msg.what | Action |
|---|---|
filteringBehaviorChanged (direction ≤ 0, specific hostname) | contentScriptRegisterer.flush(msg.hostname) |
filteringBehaviorChanged (direction ≤ 0, no hostname) | contentScriptRegisterer.reset() |
hiddenSettingsChanged | Sets this.isDevBuild = undefined, then clears cache |
loggerEnabled / loggerDisabled | this.clearCache() |
loggerLevelChanged | Updates this.logLevel, injects log-level scriptlet to all tabs, clears cache |
clearCache() calls both this.scriptletCache.reset() and contentScriptRegisterer.reset(), forcing full re-evaluation on the next page load.
Additionally, retrieve() checks this.scriptletCache.resetTime < reng.modifyTime on every call. If RedirectEngine has been updated (e.g., user-supplied resources loaded), the cache is invalidated before lookup.
Sources: src/js/scriptlet-filtering.js196-250
reset() and freeze() LifecycleThese methods are called by staticExtFilteringEngine when filter lists are reloaded:
reset() src/js/scriptlet-filtering.js236-240: calls super.reset() to clear compiled filters, rotates warSecret via vAPI.warSecret.long(this.warSecret), and clears the cache.freeze() src/js/scriptlet-filtering.js242-246: calls super.freeze() to finalize the filter store, rotates warSecret, and clears the cache.WAR secret rotation ensures that any previously cached scriptlet code embedding the old secret becomes invalid and is discarded.
Sources: src/js/scriptlet-filtering.js236-251 src/js/static-ext-filtering.js85-90
src/js/scriptlet-filtering.js exports:
| Export | Type | Description |
|---|---|---|
ScriptletFilteringEngineEx | named class export | The extended engine class |
scriptletFilteringEngine | default export | Singleton instance used throughout the background |
static-ext-filtering.js imports scriptletFilteringEngine as the default import and calls compile(), fromCompiledContent(), toSelfie(), and fromSelfie() on it, along with reset() and freeze() during filter list lifecycle events.
Sources: src/js/scriptlet-filtering.js388-393 src/js/static-ext-filtering.js26-27 src/js/static-ext-filtering.js78-163
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.