This page documents the IPC layer that connects the background process, content scripts, and UI pages in the MV2 uBlock Origin extension. The central file is src/js/messaging.js which registers all message handlers that the background process exposes to other extension contexts. The transport layer is implemented in vAPI.messaging, which lives in platform/common/vapi-background.js
This page focuses on the background side of the messaging system: channel registration, dispatch routing, and the privilege model. For the content script side of the connection (how cosmetic and scriptlet filtering parameters reach the page), see 5.1. For the popup and dashboard UI clients that consume these channels, see 7.1 and 7.2. For the broader vAPI browser abstraction layer, see 8.2.
The background process is the sole owner of all filtering state. All other contexts — popup, dashboard, logger, content scripts, element picker — are clients that request data or trigger actions by posting messages to the background. vAPI.messaging is the single hub that receives every message and routes it to the correct handler.
Message Flow Overview
Sources: src/js/messaging.js59-68 src/js/start.js29-30
vAPI.messaging is a singleton object built in platform/common/vapi-background.js It wraps browser.runtime.onMessage and provides two registration APIs consumed by src/js/messaging.js:
| Method | Signature | Description |
|---|---|---|
vAPI.messaging.setup | setup(handler) | Registers the default (nameless) handler. Always treated as privileged. |
vAPI.messaging.listen | listen({ name, listener, privileged? }) | Registers a named channel handler. privileged defaults to false. |
vAPI.messaging.UNHANDLED | sentinel value | Returned by a handler to indicate it did not process the message, causing the dispatcher to skip to the next candidate. |
When browser.runtime.onMessage fires, the dispatcher reads a channel property from the incoming message object and selects the matching registered listener. If no match is found it falls back to the default handler. Messages for which no handler returns a non-UNHANDLED result are silently dropped.
Sources: src/js/messaging.js259 src/js/messaging.js632-636 src/js/messaging.js831-834 src/js/messaging.js892-895 src/js/messaging.js992-996
Every registered listener has a privilege flag. The dispatcher enforces that content scripts (unprivileged senders) cannot reach privileged handlers.
The privilege comment at src/js/messaging.js61-66 states the rule explicitly:
The nameless default handler is always deemed "privileged". Messages from privileged ports must never be relayed to listeners which are not privileged.
This means a content script cannot invoke the dashboard or popupPanel channels, even if it crafts a message claiming that channel name.
Sources: src/js/messaging.js61-66
src/js/messaging.js is loaded at startup via src/js/start.js30 It registers all background message handlers in a series of immediately-invoked block scopes, each isolated with { // >>>>> start of local scope ... } to avoid naming collisions.
Channel registration call sites in messaging.js
Sources: src/js/messaging.js259 src/js/messaging.js632-636 src/js/messaging.js831-834 src/js/messaging.js892-895 src/js/messaging.js992-996
Registered with vAPI.messaging.setup(onMessage) at src/js/messaging.js259 Handles messages sent without a channel name, primarily from trusted extension pages.
request.what | Sync/Async | Description |
|---|---|---|
getAssetContent | async | Fetches a filter list asset via io.get() |
listsFromNetFilter | async | Reverse-lookup: which lists contain a net filter |
listsFromCosmeticFilter | async | Reverse-lookup: which lists contain a cosmetic filter |
reloadAllFilters | async | Triggers µb.loadFilterLists() |
scriptlet | async | Executes a scriptlet file via vAPI.tabs.executeScript |
applyFilterListSelection | sync | Updates selected filter lists |
clickToLoad | sync | Marks a frame for click-to-load |
createUserFilter | sync | Appends a filter to user filter list |
getAppData | sync | Returns name, version, benchmark flag |
getDomainNames | sync | Resolves hostnames/URLs to domain names |
getTrustedScriptletTokens | sync | Returns tokens from redirectEngine |
getWhitelist | sync | Returns the current net whitelist |
launchElementPicker | sync | Opens the element picker in a tab |
loggerDisabled | sync | Clears in-memory filters |
gotoURL | sync | Opens a new tab |
readyToFilter | sync | Returns µb.readyToFilter flag |
reloadTab | sync | Reloads or navigates a tab |
setWhitelist | sync | Replaces the net whitelist |
toggleHostnameSwitch | sync | Toggles a per-site switch |
uiAccentStylesheet | sync | Updates the accent stylesheet |
uiStyles | sync | Returns UI theme settings |
userSettings | sync | Gets or sets a user setting |
Sources: src/js/messaging.js94-259
popupPanel (privileged)Registered at src/js/messaging.js632-636 Used exclusively by the popup panel UI (popup-fenix.js). The key response object is built by popupDataFromTabId, which assembles per-tab filtering state, firewall rules, and hostname details.
request.what | Sync/Async | Description |
|---|---|---|
getPopupData | async | Full popup state via popupDataFromRequest → popupDataFromTabId |
getHiddenElementCount | async | Counts hidden DOM elements via dom-survey-elements.js scriptlet |
getScriptCount | async | Counts scripts via dom-survey-scripts.js scriptlet |
hasPopupContentChanged | sync | Compares contentLastModified to detect page updates |
dismissUnprocessedRequest | sync | Clears unprocessed request flag |
launchReporter | sync (fire-and-forget) | Opens the support page with diagnostic data |
revertFirewallRules | sync | Copies permanent rules back to session |
saveFirewallRules | sync | Copies session rules to permanent storage |
toggleHostnameSwitch | sync | Toggles a switch, returns fresh popup data |
toggleFirewallRule | sync | Toggles a firewall rule, returns fresh popup data |
toggleNetFiltering | sync | Enables/disables net filtering for a page |
The popupDataFromTabId function at src/js/messaging.js345-431 is the main data assembler. It pulls from µb.tabContextManager, µb.userSettings, µb.hiddenSettings, pageStore, sessionFirewall, and sessionSwitches to build the complete popup state object.
Sources: src/js/messaging.js271-639
contentscript (unprivileged)Registered at src/js/messaging.js831-834 The primary consumer is contentscript.js running inside web pages. This is the only channel through which content scripts pull their filtering parameters from the background.
request.what | Sync/Async | Description |
|---|---|---|
retrieveContentScriptParameters | async | Main bootstrap: fetches cosmetic selectors, injects scriptlets, returns filtering parameters |
retrieveGenericCosmeticSelectors | sync | Retrieves generic cosmetic selectors for a frame |
cosmeticFiltersInjected | sync | Reports injected selectors back to the cache |
disableGenericCosmeticFilteringSurveyor | sync | Disables the generic filter surveyor for a hostname |
getCollapsibleBlockedRequests | sync | Returns list of blocked requests for element collapsing |
maybeGoodPopup | sync | Records a possibly legitimate popup attempt |
messageToLogger | sync | Forwards a scriptlet log message to the logger |
shouldRenderNoscriptTags | sync | Determines if <noscript> spoofing should apply |
The retrieveContentScriptParameters handler at src/js/messaging.js651-740 is the most critical: it calls cosmeticFilteringEngine.retrieveSpecificSelectors, optionally injects contentscript-extra.js for procedural filters, and calls scriptletFilteringEngine.injectNow. It returns a response object that contentscript.js uses to configure itself.
Sources: src/js/messaging.js644-837
elementPicker (unprivileged)Registered at src/js/messaging.js892-895 Used by the element picker content script (epicker.js).
request.what | Sync/Async | Description |
|---|---|---|
elementPickerArguments | async | Injects contentscript-extra.js, returns picker config from µb.epickerArgs |
elementPickerEprom | sync | Stores picker state back into µb.epickerArgs.eprom |
Sources: src/js/messaging.js846-898
cloudWidget (privileged)Registered at src/js/messaging.js992-996 Used by the cloud sync widget in the dashboard.
request.what | Sync/Async | Description |
|---|---|---|
cloudGetOptions | async | Returns current cloud storage options |
cloudSetOptions | async | Applies cloud storage options |
cloudPull | async | Pulls data from cloud, decodes via s14e.deserializeAsync or legacy lz4Codec |
cloudPush | async | Serializes data via s14e.serializeAsync, pushes to cloud |
cloudUsed | async | Reports storage usage for a data key |
Sources: src/js/messaging.js903-999
dashboard (privileged)Registered after line 1000 in src/js/messaging.js Used by the dashboard UI for settings management, filter list control, and backup/restore.
Representative messages include:
request.what | Description |
|---|---|
getLocalData | Returns storage usage and backup metadata |
backupUserData | Assembles a full backup object |
restoreUserData | Clears storage and restores from backup, then calls vAPI.app.restart() |
resetUserData | Clears all storage, restarts |
getLists | Returns available filter lists and cache metadata |
reloadAllFilters | Re-compiles all filter lists |
purgeAllCaches | Clears all cached filter list data |
readUserFilters | Returns raw user filter text |
writeUserFilters | Saves user filter text |
getHiddenSettings | Returns current hidden settings |
setHiddenSettings | Applies new hidden settings |
Sources: src/js/messaging.js1004-1177
All onMessage functions in messaging.js follow the same structure:
onMessage(request, sender, callback) → void
Async handlers call callback(result) from inside a .then() and return early (the framework detects no explicit return value, keeping the message channel open):
Sync handlers set a response variable and call callback(response) at the end of the switch:
Unhandled messages return vAPI.messaging.UNHANDLED to signal the dispatcher:
This pattern appears in every channel handler in src/js/messaging.js
Sources: src/js/messaging.js94-257 src/js/messaging.js513-630 src/js/messaging.js742-829
getPopupData request → popupDataFromTabId response
Sources: src/js/messaging.js345-447
retrieveContentScriptParameters — the content script bootstrap call
Sources: src/js/messaging.js651-740
messaging.js is loaded in the background process startup chain via src/js/start.js30 as a side-effecting import. All vAPI.messaging.setup() and vAPI.messaging.listen() calls execute at module evaluation time — they run before any message can be received, since the extension runtime completes module loading before dispatching any external events.
The µb singleton from src/js/background.js is imported by messaging.js at src/js/messaging.js57 and is accessed directly in every handler, which means handlers have direct read/write access to all background state without any intermediate layer.
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.