The vAPI layer isolates the rest of the uBlock Origin codebase from direct browser API calls. Every feature that touches the browser — tab management, network interception, storage, messaging, the browser toolbar button — goes through vAPI. This makes it possible to share the bulk of the filtering and UI logic between Chromium and Firefox while confining platform-specific behavior to dedicated files.
This page covers the file structure and load order of the vAPI layer, each major vAPI component and the browser APIs it wraps, and the platform-specific extension points for Chromium and Firefox.
For how vAPI.messaging is used in practice by the background-to-UI IPC layer, see Messaging System. For how vAPI.storage is layered under the multi-tier cache, see Storage Management. For how tab lifecycle hooks are implemented on top of vAPI.Tabs, see Tab Management and Page Stores.
vAPI is assembled from multiple files, each adding properties to the global vAPI object. The set of files loaded depends on context (background vs. content script) and platform (Chromium vs. Firefox).
Diagram: vAPI file structure, context, and load order
Sources: platform/common/vapi.js1-85 platform/common/vapi-common.js1-30 platform/common/vapi-background.js1-50 platform/chromium/vapi-background-ext.js1-40 platform/firefox/vapi-background-ext.js1-30 platform/chromium/webext.js1-30
webext Promisification Layer (Chromium)On Chromium, chrome.* APIs use callbacks. platform/chromium/webext.js wraps each used API entry into a promise-returning function via two factory functions:
| Factory | Error behaviour |
|---|---|
promisify(thisArg, fnName) | Rejects the promise with chrome.runtime.lastError.message |
promisifyNoFail(thisArg, fnName, outFn) | Always resolves; transforms the result through outFn |
The exported webext object covers: alarms, browserAction, menus, privacy, storage.local, storage.sync, storage.managed, tabs, webNavigation, and windows.
vapi-background.js imports this webext object and delegates all raw API calls to it, never touching chrome.* directly. On Firefox the native browser.* API already returns promises, so a simpler shim is used.
Sources: platform/chromium/webext.js25-60 platform/chromium/webext.js62-181 platform/common/vapi-background.js27-29
vapi.js)platform/common/vapi.js runs first in every context. It has two jobs:
self.browser is present (Firefox), assign it to self.chrome so that code referencing either name works. If only self.chrome exists (Chromium), assign it to self.browser.vAPI object. In a content script context, a guard check prevents re-initialization: the object is only created if self.vAPI is absent or vAPI.uBO !== true. Subsequent script loads respect the existing object.Sources: platform/common/vapi.js26-65
vapi-common.js)platform/common/vapi-common.js is loaded in every context. It populates utility members that both the background page and content scripts rely on.
vAPI.webextFlavorA browser-detection record built at module load time. The soup property is a Set<string> of capability and browser tags.
| Tag | Meaning |
|---|---|
ublock | Always present |
webext | Always present |
firefox | Running on Gecko/Firefox |
chromium | Running on Chromium |
mobile | User-agent contains "Mobile" |
devbuild | Version string has a non-numeric suffix |
user_stylesheet | Platform supports cssOrigin: 'user' |
html_filtering | Platform supports HTML response filtering (Firefox only) |
native_css_has | Browser has native CSS :has() selector support |
vAPI.webextFlavor.major is the integer major version of the detected browser. After detection completes, a webextFlavor CustomEvent is dispatched on window so other modules can react asynchronously.
Sources: platform/common/vapi-common.js145-204
vAPI.deferA timer-management utility for scheduling callbacks without storing raw timer IDs throughout the codebase.
vAPI.defer.create(callback) returns a Client instance:
| Method | Scheduling mechanism |
|---|---|
on(delay) | setTimeout; no-op if already pending |
offon(delay) | Cancel existing timer, then on() |
onvsync(delay) | setTimeout → requestAnimationFrame |
onidle(delay, options) | setTimeout → requestIdleCallback |
onraf() | requestAnimationFrame directly |
off() | Cancel whichever timer type is active |
ongoing() | true if a timer is pending |
vAPI.defer.once(delay) returns a Promise that resolves after delay. The delay argument can be a raw millisecond number or an object with a sec, min, or hr key.
Sources: platform/common/vapi-common.js33-141
| Member | Description |
|---|---|
vAPI.T0 | Date.now() at module load; used as an extension startup timestamp |
vAPI.getURL | Alias for browser.runtime.getURL |
vAPI.download(details) | Triggers a file download via a synthetic <a> element click |
vAPI.closePopup() | Closes popup window; window.close() on Firefox, window.open('','_self').close() on Chromium |
vAPI.localStorage | Async key/value store; proxies reads and writes through vAPI.messaging to the background page's browser.storage.local |
Sources: platform/common/vapi-common.js27-31 platform/common/vapi-common.js206-275
vapi-background.js)These members are set up only in the background page context.
| Flag | Condition |
|---|---|
vAPI.cantWebsocket | true if browser.webRequest.ResourceType.WEBSOCKET is unavailable |
vAPI.canWASM | true on Firefox unconditionally; on Chromium requires 'wasm-unsafe-eval' in the manifest CSP |
vAPI.supportsUserStylesheets | true when the user_stylesheet soup tag is present |
Sources: platform/common/vapi-background.js34-44
vAPI.app| Member | Description |
|---|---|
name | Extension name from the manifest; strips any dev* build suffix |
version | Version string; the fourth component is converted to bN (beta) or rcN (release candidate) |
intFromVersion(s) | Encodes a dotted-decimal or b/rc-tagged version string into a comparable integer |
restart() | Calls browser.runtime.reload() |
Sources: platform/common/vapi-background.js48-84
vAPI.storageA thin error-absorbing wrapper around webext.storage.local. All calls catch exceptions, log to console, and return undefined rather than propagating. getBytesInUse is conditionally added only when the underlying API supports it.
vAPI.sessionStorageWraps browser.storage.session (in-memory, session-scoped). If the browser does not support it in MV2, a no-op shim with unavailable: true is used instead.
Sources: platform/common/vapi-background.js108-165
vAPI.browserSettingsManages privacy-related browser settings through the browser.privacy API. Only defined when webext.privacy is available.
| Setting key | API property touched |
|---|---|
prefetching | privacy.network.networkPredictionEnabled |
hyperlinkAuditing | privacy.websites.hyperlinkAuditingEnabled |
webrtcIPAddress | privacy.network.webRTCIPHandlingPolicy |
The set(details) method iterates the keys of its argument object and applies each matching privacy setting. Enabling a setting calls .clear(); disabling calls .set({value: ..., scope: 'regular'}).
Sources: platform/common/vapi-background.js177-258
vAPI.Tabs ClassvAPI.Tabs wraps browser.tabs and browser.webNavigation. An instance is instantiated as vAPI.tabs during background initialization. The constructor registers all event listeners immediately.
Diagram: vAPI.Tabs class hierarchy and event wiring
The five abstract hooks (onActivated, onClosed, onCreated, onNavigation, onUpdated) are empty in the base class. tab.js overrides them on the instantiated vAPI.tabs object to implement per-tab state tracking.
The Chromium subclass addresses a Chromium bug: webNavigation.onCreatedNavigationTarget is sometimes not fired for new tabs opened from within a page. The subclass tracks blank new tabs in tabIds and calls synthesizeNavigationTargetEvent() in onCommittedHandler() when a client-redirect transition is detected.
| Constant | Value | Meaning |
|---|---|---|
vAPI.unsetTabId | 0 | Tab ID not yet assigned |
vAPI.noTabId | -1 | Definitively not a real tab |
vAPI.isBehindTheSceneTabId(id) | id < 0 | True for synthetic/behind-the-scene requests |
Sources: platform/common/vapi-background.js263-656 platform/chromium/vapi-background-ext.js32-79
vAPI.Net ClassvAPI.Net is the base class for the network interception layer. It provides a normalized interface over browser.webRequest for request inspection and modification, and a request-suspension mechanism used during early startup. The base class is defined in platform/common/vapi-background.js and subclassed by both platform extension files.
Diagram: vAPI.Net class hierarchy
The Firefox subclass resolves the canonical name of each requested hostname via browser.dns.resolve() with the canonical_name flag. Resolved entries are cached in a fixed-size ring buffer (dnsList array, dnsDict Map, dnsWritePtr pointer). If a CNAME is found, uncloakURL() rewrites the URL with the canonical hostname and the base class filtering engine re-evaluates it.
Configurable options set via setOptions():
| Option | Default | Effect |
|---|---|---|
cnameUncloakEnabled | true | Master switch for CNAME resolution |
cnameIgnore1stParty | true | Skip if CNAME and original share the same eTLD+1 |
cnameIgnoreExceptions | true | Do not reclassify already-excepted requests |
cnameIgnoreRootDocument | true | Skip CNAME rewrite for the root document's own origin |
cnameReplayFullURL | false | Include the full path in the rewritten URL |
dnsCacheTTL | 600 | DNS cache entry TTL in seconds |
dnsResolveEnabled | true | Enable DNS lookups at all |
Both platform subclasses implement suspendOneRequest() / unsuspendAllRequests(), but differently:
| Platform | suspendOneRequest | unsuspendAllRequests |
|---|---|---|
| Firefox | Pushes a { details, promise, resolve } entry onto pendingRequests; returns the promise to the webRequest listener | Resolves all pending promises, re-running each through onBeforeSuspendableRequest |
| Chromium | Returns { cancel: true } immediately (except for main_frame); records affected tabs in unprocessedTabs | Calls vAPI.tabs.reload() on each affected tab |
Sources: platform/firefox/vapi-background-ext.js51-350 platform/chromium/vapi-background-ext.js83-171
vAPI.browserActionWraps browser.browserAction for toolbar button management. Conditionally defined: if webext.browserAction is absent, vAPI.browserAction is undefined (the case on Firefox for Android).
| Method | Description |
|---|---|
setTitle(details) | Set the button tooltip |
setIcon(details) | Set the button icon |
setBadgeText(details) | Set the badge text overlay |
setBadgeTextColor(details) | Set badge text color |
setBadgeBackgroundColor(details) | Set badge background color |
vAPI.setIcon(tabId, details) is the higher-level function used by the rest of the codebase. The parts bitmask argument controls which fields to update:
| Bit | Field updated |
|---|---|
0b0001 | Icon image (on/off state) |
0b0010 | Badge text |
0b0100 | Badge background color |
0b1000 | Hide badge (empty string for badge text) |
On Chromium, icon pixels are pre-fetched into ImageData objects at startup to avoid per-call image decoding inside the browser, since Chromium does not cache icon ImageData between setIcon calls.
vAPI.setDefaultIcon(flavor, text) sets the global (non-tab-specific) icon and badge when no per-tab state applies.
Sources: platform/common/vapi-background.js696-917
vAPI.messagingThe port-based IPC hub for the background page. Background code registers named channel listeners; content scripts and UI pages connect via browser.runtime.connect() and send messages over the resulting ports.
Diagram: vAPI.messaging connection and dispatch flow
| Property | Type | Description |
|---|---|---|
ports | Map<string, portDetails> | Active port connections keyed by port name |
listeners | Map<string, {fn, privileged}> | Registered channel handlers |
defaultHandler | Function | null | Fallback for unregistered channels |
PRIVILEGED_ORIGIN | string | vAPI.getURL('').slice(0,-1) — the extension's own origin |
UNHANDLED | string | Sentinel returned when a handler cannot process a message |
onPortConnect() checks port.sender.origin first (preferred; not spoofable by a compromised renderer), falling back to port.sender.url. If the origin matches PRIVILEGED_ORIGIN the port is marked privileged. Listeners registered with privileged: true will only respond to messages from privileged ports.
vAPI.messaging.listen({
name: 'channelName',
listener: function(msg, sender, callback) { ... },
privileged: true
});
onFrameworkMessage() handles two built-in message types before the normal channel dispatch:
msg.what | Action |
|---|---|
localStorage | Proxies vAPI.localStorage calls from UI pages to the background's browser.storage.local |
userCSS | Calls vAPI.tabs.insertCSS / removeCSS to inject or remove CSS in a page frame |
Sources: platform/common/vapi-background.js941-1056
vAPI.scriptletsInjectorDefined differently per platform. Both versions produce a self-invoking function string that is executed as a content script. The function:
self.uBO_scriptletsInjecteddocument.location.hostname matches the expected hostname<script> element, sets its text content to the scriptlet code, appends it to the document, then removes itDiagram: Scriptlet injection strategies by platform
Firefox uses self.wrappedJSObject to verify that execution of the injected script actually reached the page world (Firefox separates extension and page JavaScript contexts). If the sentinel check fails, a Blob URL injection is attempted as a fallback. Chromium has no cross-world boundary, so direct injection is sufficient.
Sources: platform/firefox/vapi-background-ext.js354-425 platform/chromium/vapi-background-ext.js212-250
vAPI.windowsConditionally defined when webext.windows is available. Provides get(), create(), and update() as promise-returning wrappers around browser.windows.*. Used by vAPI.Tabs.create() when opening UI panels (popup, logger, dashboard) in standalone windows.
Sources: platform/common/vapi-background.js661-691
| Component | vapi-background.js | chromium/vapi-background-ext.js | firefox/vapi-background-ext.js |
|---|---|---|---|
vAPI.Tabs | Base class with 5 abstract hooks | Subclass: synthesizes missing navigation target events | Not overridden |
vAPI.Net | Base class with suspension API | Subclass: type normalization from URL extensions and content-type headers; cancel+reload suspension | Subclass: DNS/CNAME uncloaking with 512-entry ring buffer; promise-based suspension |
vAPI.scriptletsInjector | Not defined | Defined: direct textNode injection | Defined: wrappedJSObject sentinel + Blob URL fallback |
vAPI.prefetching | Not defined | Defined: injects X-DNS-Prefetch-Control: off response header | Not needed (controlled via privacy.network.networkPredictionEnabled) |
Sources: platform/chromium/vapi-background-ext.js1-251 platform/firefox/vapi-background-ext.js1-427 platform/common/vapi-background.js1-165
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.