Opera native ad blocker vs. declarativeNetRequest: allow-rule precedence and compiled-trie memory retention
0 reputation · 21 May 2021, 16:46 UTC
0 reputation · 21 May 2021, 16:46 UTC
Opera 100+ ships a native ad blocker that compiles filter lists into binary trie structures at startup, while also exposing Chromium's declarativeNetRequest (DNR) API for extensions. The native matcher runs earlier in the network stack than DNR static or dynamic rules.
Determine whether an extension's DNR allow rules can reliably override a native block decision, and whether the native blocker's compiled trie structures—which persist across navigations and are only released on browser restart—can be measured or compacted via any public API. The native blocker updates its lists daily through Opera's component updater, while DNR dynamic rules follow a separate cadence, so temporary divergence is expected.
allow rules take effect after a native block match, or does the native matcher's early position make override impossible?chrome.debugger or a DevTools protocol command) without a full browser restart?35275 reputation · 21 May 2021, 21:15 UTC
Opera's native ad blocker executes before Chromium's declarativeNetRequest (DNR) engine in the network stack. A native block verdict short-circuits the request; DNR allow rules (type: "allow") only override other DNR block/redirect rules inside the DNR engine. They cannot reverse a decision already made by the native matcher.
Confirmed: The request flow is: native blocker → DNR static rules → DNR dynamic rules → DNR allow rules → other interceptors. A native block stops evaluation before DNR runs.
Likely explanation: Opera integrates its blocker at the net::URLRequest layer (or via a NetworkDelegate hook) that precedes the DNR hook points added in Chromium M84+. This is an architectural choice, not a bug.
To unblock a resource that the native blocker catches, the user must disable the native blocker for that site via Opera's shield icon → "Turn off for this site" or add an exception in opera://settings/adBlockerExceptions. An extension cannot do this programmatically.
The native blocker compiles filter lists into binary tries at startup and retains them until the browser process exits. No Chromium API (chrome.debugger, DevTools Protocol, chrome.system.memory, chrome.processes) surfaces the trie's heap footprint or triggers recompaction.
Confirmed: Opera's component updater refreshes the raw filter lists daily, but the compiled trie is only rebuilt on a full browser restart. Memory growth from list updates is only released on restart.
Likely explanation: The trie lives in the browser process's native heap (C++ side), isolated from V8 and the extension subsystem. Opera does not expose a chrome://flags toggle, enterprise policy, or internal IPC for trie introspection or compaction in the 100+ release line.
allow rule for a domain known to be blocked by Opera's default lists (e.g., ||doubleclick.net^).opera://net-internals/#events or capture a chrome://net-export/ log; the native blocker's verdict appears before any DNR evaluation entry.chrome://system → processes) before/after visiting heavy filter-list sites. Memory rises and only drops after opera://quit + relaunch.Are you targeting a specific Opera version before 100, or a Chromium-based fork (Vivaldi, Brave, Edge) that may layer its native blocker differently? The stack ordering described here applies to Opera 100+ on current Chromium; earlier versions or other forks may expose different hook points.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 21 May 2021, 21:53 UTC
The "Turn off for this site" shield action and opera://settings/adBlockerExceptions do not merely lower the native blocker's priority—they insert an internal exception record that is checked before the compiled trie is consulted. This is why a user-facing exception works while a DNR allow rule cannot: the exception lives in the same native layer that runs ahead of DNR, whereas DNR rules are evaluated only after the native matcher yields.
For extension authors, this means the only reliable programmatic path is to detect a native block (via webRequest.onErrorOccurred with net::ERR_BLOCKED_BY_CLIENT in MV2, or by observing the request never reaches DNR in MV3) and then prompt the user to add the site exception manually. There is no chrome.contentSettings or enterprise policy hook to inject native exceptions silently.