How Nvirya AdGuard Works
Understanding what Nvirya AdGuard can and cannot do starts with understanding where a userscript sits in your browser's architecture compared to a browser extension or a VPN/DNS filter.
Comparing Filtering Technologies#
Different content-filtering tools operate at different layers of your device and browser stack:
| Technology | Typical Capabilities | Important Limitations |
|---|---|---|
| Userscript (Nvirya AdGuard) | Runs page-level JavaScript (@run-at document-start), injects cosmetic CSS, observes and modifies the page DOM, and wraps JavaScript APIs (fetch, XMLHttpRequest, sendBeacon, window.open) where supported | Depends on injection timing, userscript manager capabilities, browser APIs, Content Security Policy (CSP), and WebKit sandbox isolation; cannot block browser-level network traffic outside JavaScript |
| Browser extension | Can access privileged browser APIs (such as declarativeNetRequest or webRequest) to cancel or redirect network requests before they enter the page context, alongside content scripts and CSS | Depends on browser vendor support (limited or unavailable on many mobile browsers), extension store policies, manifest version restrictions, and granted host permissions |
| VPN or DNS filtering (Shadowrocket, Surge, Loon, AdGuard DNS) | Blocks or redirects domain resolutions and network packets across the entire operating system or network interface before connections reach the browser | Cannot inspect or modify the page DOM, collapse empty ad frames, unlock page scrolling, click video skip buttons, or distinguish ads served from the same domain as primary content |
Because Nvirya AdGuard is a userscript, it excels at in-page DOM cleanup, interactive element hiding, and JavaScript navigation guards, while facing inherent browser constraints when dealing with low-level network traffic.
Three-Phase Startup Architecture#
To reduce page-load overhead, Nvirya AdGuard v11.7.0 divides its initialization (init()) into three sequential phases:
- Phase 1 — Critical Core (
document-start):- Injects the main-world protection script (
injectMainWorldShield) when the site is not allowlisted or paused. - Installs JavaScript API wrappers for
fetch,XMLHttpRequest,navigator.sendBeacon,window.open,document.write,HTMLScriptElement.prototype.src, andHTMLImageElement.prototype.src. - Installs capture-phase event guards for invisible click traps, external link redirects, clipboard writes, and tab-visibility spoofing.
- Strips known tracking query parameters (such as
utm_*,fbclid,gclid,msclkid) from the address bar viahistory.replaceState(cleanTrackingQuery), excluding YouTube domains. - Injects cosmetic CSS (
installCosmeticCSS) and saved per-site rules (installSiteRulesCSS) as soon asdocument.documentElementordocument.headexists.
- Injects the main-world protection script (
- Phase 2 — Deferred DOM (
DOMContentLoadedordocument.bodyready):- Mounts the floating Quick Action Button (FAB) inside an open Shadow DOM container in the top frame (
window.self === window.top). Note that the full bottom-sheet control panel DOM (ensureSheetBuilt) is deferred for up to 3 seconds or until the browser is idle to keep initial page rendering responsive. - Starts the batched MutationObserver (
startObserver) and runsinitialScan()(orinitYouTubeShield()on YouTube domains). - Hooks single-page application (SPA) history changes (
history.pushState,history.replaceState, andpopstate).
- Mounts the floating Quick Action Button (FAB) inside an open Shadow DOM container in the top frame (
- Phase 3 — Idle Background Tasks (
requestIdleCallbackor 2,000 ms fallback):- Loads lifetime statistics and 14-day weekly history from storage.
- Schedules bounded anti-adblock fallback scans (
600 ms,1,800 ms, and4,200 ms). - Registers userscript manager menu commands (
GM_registerMenuCommand). - Checks for script updates (every 30 minutes) and synchronizes remote domain lists (every 24 hours, if configured).
DOM-Level Filtering vs. Network-Level Filtering#
DOM-Level Filtering#
DOM-level filtering targets HTML elements after they are created or inserted into the document tree:
- Cosmetic CSS Injection: A
<style data-nvirya-cosmetic>sheet hides common ad selectors ([id^="google_ads_"],ins.adsbygoogle,[id^="div-gpt-ad"],[data-ad-slot], known ad iframes, and[data-nvirya-hidden]) along with hostname-scoped rules for YouTube, Google Search, Bing Search, Reddit, X/Twitter, Quora, and Facebook mobile. - Batched Mutation Observation: Instead of scanning every element on the page on every change,
startObserver()collects added nodes and attribute changes (src,href,style,class,id,data-src,data-srcset) up to a bounded queue (MAX_QUEUE = 800) and processes them in time-sliced chunks (processInChunks, up to8 msper slice) viarequestIdleCallbackorrequestAnimationFrame. On Google Search and Bing Search, mutations are debounced by150 ms(RE_SEARCH_DEBOUNCE_HOST) to avoid layout thrashing during live typing and search result updates. - Overlay, Backdrop, and Scroll-Lock Recovery: When the script removes an ad modal (
removeTrackedAdNode), it captures the surrounding overlay context (captureOverlayRemovalContext), removes paired full-screen dimmers/backdrops (adjacentBackdropNodes), collapses empty bottom sticky ad bars (cleanupBottomStickyAdWrappers), and restoresoverflow: autoandposition: staticon<html>and<body>(unlockPageScroll) unless a legitimate modal dialog is open (hasVisibleLegitimateModal).
JavaScript Network Hooks (and Their Limits)#
A userscript cannot access the browser's privileged network stack. Instead, Nvirya AdGuard intercepts requests at the JavaScript API layer in both the userscript context (installNetworkGuard) and the main page context (mainWorldShield):
- Window.fetch (Fetch API): When a script calls
fetch(url)for a URL matchingisAdUrl(u)(orRE_TRACKERwhen Deep Network Filter is enabled), Nvirya AdGuard does not throw a network error that might trigger anti-adblock scripts. Instead,createFakeFetchResponseresolves with a synthetic200 OKResponseobject whose body matches the expected content type ({"status":"ok","success":true,"data":[]}for JSON,/* Nvirya Stub OK */ window.canRunAds = true;for JavaScript, or an empty string for plain text). - XMLHttpRequest: When
xhr.open(method, url)targets a blocked ad or tracker URL, the script redirects the internal open call to an inertdata:text/plain,URI and overridesreadyState(4),status(200),statusText("OK"),responseText, andresponseduringxhr.send(), dispatching syntheticreadystatechange,load, andloadendevents. - Navigator.sendBeacon: Blocked beacon payloads return
trueimmediately without transmitting data over the network. - Dynamic
<script>and<img>Properties: Setting.srcorsetAttribute('src', ...)on anHTMLScriptElementpointing to a known ad domain setstype = "javascript/blocked"and dispatches a syntheticloadevent (triggerFakeScriptLoad). Setting.srcon anHTMLImageElementpointing to an ad URL spoofs a loaded32×32image (complete = true,naturalWidth = 32,naturalHeight = 32) so layout scripts checking image dimensions do not detect a broken load.
What JavaScript hooks cannot intercept:
- Declarative HTML subresources parsed by the browser before the userscript's hooks execute (such as static
<img src="...">or<iframe src="...">tags already fetched by the browser's preload scanner, though DOM filtering still hides or removes the resulting elements). - CSS network requests (such as
background-image: url(...)or@font-faceloads). - Server-side HTTP redirects (
301,302,307,308headers) or network traffic inside cross-origin iframes where the userscript manager does not inject scripts. - Main-world scripts on pages with strict Content Security Policy (CSP) or WebKit sandbox limits that block inline
<script>injection (injectMainWorldShield).
Why Streamed Media Ads Differ from Ordinary DOM Ads#
A traditional banner or pop-up ad is a separate DOM element (<iframe>, <ins>, <div>, or <script>) loaded from a distinct third-party advertising domain. Removing the element or returning an empty response for the ad script usually eliminates the advertisement cleanly.
Video advertisements on platforms such as YouTube work differently:
- Shared Media Infrastructure: Video segments and player control payloads are often delivered over the same core API endpoints (
/youtubei/v1/player,/youtubei/v1/next) and media CDNs (googlevideo.com,youtube.com/videoplayback) that deliver the actual video you want to watch. - Risk of Playback Breakage: If a script blindly blocks
googlevideo.comor drops/youtubei/v1/playerresponses, the video player will stall, show a black screen, or fail with a playback error. - Coordinated Player State: Because Nvirya AdGuard must allow legitimate media streams through (
RE_YOUTUBE_CDN_WHITELIST), it handles video ads through two coordinated layers insideinitYouTubeShield():- Response JSON Pruning (
installYouTubeJsonPruner): InterceptsytInitialPlayerResponse,Response.prototype.json(),Response.prototype.text(), andXMLHttpRequestplayer responses to empty outadPlacements,playerAds,adSlots,adBreakHeartbeatParams, andadBreakRendererbefore the player processes them. - Player-State Acceleration (
scheduleVideoAdScan): When an ad still enters the HTML5<video>element (detected via.ad-showingor.ad-interruptingon#movie_player), the script clicks any active skip button, mutes the video, raisesplaybackRateto16, and seeks toduration - 0.05seconds once media metadata is available, restoring your original volume and playback rate afterward.
- Response JSON Pruning (
For more details on how this affects mobile video playback, see Known Limitations and Troubleshooting.