Cơ chế hoạt động của Nvirya AdGuard

Để hiểu rõ những gì Nvirya AdGuard có thể và không thể làm, trước hết cần phân biệt vị trí của một userscript trong kiến trúc trình duyệt so với tiện ích mở rộng trình duyệt (browser extension) và bộ lọc VPN/DNS.

So sánh các công nghệ lọc nội dung#

Mỗi công cụ lọc nội dung hoạt động ở một tầng khác nhau trên thiết bị và trình duyệt của bạn:

Công nghệKhả năng điển hìnhGiới hạn quan trọng
Userscript (Nvirya AdGuard)Chạy JavaScript trong trang (@run-at document-start), chèn CSS ẩn phần tử, quan sát và chỉnh sửa cây DOM, đồng thời bọc các API JavaScript (fetch, XMLHttpRequest, sendBeacon, window.open) nơi được hỗ trợPhụ thuộc vào thời điểm tiêm script, khả năng của trình quản lý userscript, API trình duyệt, Chính sách bảo mật nội dung (CSP) và cơ chế cô lập sandbox của WebKit; không thể chặn lưu lượng mạng cấp trình duyệt bên ngoài JavaScript
Tiện ích mở rộng trình duyệt (Browser extension)Truy cập được các API đặc quyền của trình duyệt (như declarativeNetRequest hoặc webRequest) để hủy hoặc chuyển hướng yêu cầu mạng trước khi vào trang, kết hợp với content script và CSSPhụ thuộc vào sự hỗ trợ của nhà sản xuất trình duyệt (bị hạn chế hoặc không có trên nhiều trình duyệt di động), chính sách kho tiện ích, giới hạn Manifest và quyền truy cập tên miền
Bộ lọc VPN hoặc DNS (Shadowrocket, Surge, Loon, AdGuard DNS)Chặn hoặc chuyển hướng phân giải tên miền và gói tin mạng trên toàn hệ điều hành hoặc giao diện mạng trước khi kết nối tới trình duyệtKhông thể kiểm tra hay chỉnh sửa cây DOM của trang, không thể thu gọn khung quảng cáo trống, mở khóa cuộn trang, nhấn nút bỏ qua quảng cáo video, hoặc phân biệt quảng cáo dùng chung tên miền với nội dung chính

Vì Nvirya AdGuard là một userscript, thế mạnh cốt lõi của nó là dọn dẹp DOM trực tiếp trong trang, ẩn phần tử tương tác theo yêu cầu và bảo vệ điều hướng JavaScript, trong khi phải tuân theo các rào cản tự nhiên của trình duyệt đối với lưu lượng mạng tầng thấp.

Kiến trúc khởi động 3 giai đoạn#

Để giảm thiểu độ trễ khi tải trang, Nvirya AdGuard v11.7.0 chia quá trình khởi tạo (init()) thành 3 giai đoạn tuần tự:

  1. Giai đoạn 1 — Lõi bảo vệ tức thì (document-start):
    • Tiêm script bảo vệ ngữ cảnh chính (injectMainWorldShield) khi trang web không nằm trong danh sách cho phép hoặc tạm dừng.
    • Cài đặt các lớp bọc API JavaScript cho fetch, XMLHttpRequest, navigator.sendBeacon, window.open, document.write, HTMLScriptElement.prototype.src và HTMLImageElement.prototype.src.
    • Cài đặt bộ chặn sự kiện pha bắt giữ (capture-phase) đối với bẫy nhấp chuột vô hình, chuyển hướng liên kết ngoài, ghi bộ nhớ tạm (clipboard) trái phép và giả mạo trạng thái hiển thị tab.
    • Loại bỏ các tham số truy vấn theo dõi đã biết (như utm_*, fbclid, gclid, msclkid) khỏi thanh địa chỉ qua history.replaceState (cleanTrackingQuery), ngoại trừ tên miền YouTube.
    • Chèn CSS ẩn quảng cáo (installCosmeticCSS) và quy tắc tùy chỉnh của trang (installSiteRulesCSS) ngay khi document.documentElement hoặc document.head xuất hiện.
  2. Giai đoạn 2 — Xử lý DOM trì hoãn (DOMContentLoaded hoặc khi document.body sẵn sàng):
    • Gắn Nút thao tác nhanh (FAB) vào bên trong một vùng chứa Shadow DOM mở ở khung chính (window.self === window.top). Lưu ý rằng việc khởi tạo toàn bộ DOM của bảng điều khiển (ensureSheetBuilt) được trì hoãn tối đa 3 giây hoặc cho đến khi trình duyệt rảnh để giữ tốc độ hiển thị trang ban đầu mượt mà.
    • Khởi động MutationObserver theo lô (startObserver) và chạy initialScan() (hoặc initYouTubeShield() trên các tên miền YouTube).
    • Theo dõi thay đổi lịch sử điều hướng SPA (history.pushState, history.replaceState và popstate).
  3. Giai đoạn 3 — Tác vụ nền khi rảnh (requestIdleCallback hoặc sau 2.000 ms):
    • Tải số liệu thống kê tích lũy và lịch sử hoạt động 14 ngày từ bộ nhớ lưu trữ.
    • Lên lịch quét dự phòng anti-adblock có giới hạn (600 ms, 1.800 ms và 4.200 ms).
    • Đăng ký các lệnh menu cho trình quản lý userscript (GM_registerMenuCommand).
    • Kiểm tra bản cập nhật script (mỗi 30 phút) và đồng bộ danh sách tên miền từ xa (mỗi 24 giờ, nếu đã cấu hình).

Lọc cấp độ DOM so với Lọc cấp độ mạng#

Lọc cấp độ DOM#

Lọc cấp độ DOM xử lý các phần tử HTML sau khi chúng được tạo hoặc chèn vào cây tài liệu:

  • Chèn CSS ẩn phần tử (Cosmetic CSS): Thẻ <style data-nvirya-cosmetic> ẩn các bộ chọn quảng cáo phổ biến ([id^="google_ads_"], ins.adsbygoogle, [id^="div-gpt-ad"], [data-ad-slot], các iframe quảng cáo đã biết và [data-nvirya-hidden]) cùng các quy tắc giới hạn theo tên miền cho YouTube, Google Search, Bing Search, Reddit, X/Twitter, Quora và Facebook di động.
  • Quan sát biến đổi DOM theo lô: Thay vì quét toàn bộ trang sau mỗi thay đổi, startObserver() gom các nút mới thêm và thay đổi thuộc tính (src, href, style, class, id, data-src, data-srcset) vào hàng đợi giới hạn (MAX_QUEUE = 800) và xử lý theo từng lát thời gian (processInChunks, tối đa 8 ms mỗi khung hình) thông qua requestIdleCallback hoặc requestAnimationFrame. Trên Google Search và Bing Search, các đợt biến đổi được trì hoãn gom nhóm 150 ms (RE_SEARCH_DEBOUNCE_HOST) để tránh giật cục khi gõ phím tìm kiếm.
  • Dọn lớp phủ, nền tối và khôi phục cuộn trang: Khi xóa một hộp thoại quảng cáo (removeTrackedAdNode), script ghi nhận ngữ cảnh lớp phủ xung quanh (captureOverlayRemovalContext), xóa các lớp nền làm mờ toàn màn hình đi kèm (adjacentBackdropNodes), thu gọn các thanh quảng cáo bám dính trống ở cuối trang (cleanupBottomStickyAdWrappers) và khôi phục overflow: auto cùng position: static trên <html> và <body> (unlockPageScroll) trừ khi trang đang mở một hộp thoại hợp lệ (hasVisibleLegitimateModal).

Bọc API mạng trong JavaScript (và giới hạn của chúng)#

Userscript không thể can thiệp vào tầng mạng nội bộ của trình duyệt. Thay vào đó, Nvirya AdGuard chặn yêu cầu ở tầng API JavaScript trong cả ngữ cảnh userscript (installNetworkGuard) và ngữ cảnh trang chính (mainWorldShield):

  • Window.fetch (Fetch API): Khi trang gọi fetch(url) tới một địa chỉ khớp với isAdUrl(u) (hoặc RE_TRACKER khi bật Lọc kết nối sâu), Nvirya AdGuard không ném lỗi mạng dễ làm kích hoạt anti-adblock. Thay vào đó, createFakeFetchResponse trả về một đối tượng Response 200 OK giả lập phù hợp với kiểu dữ liệu mong đợi ({"status":"ok","success":true,"data":[]} cho JSON, /* Nvirya Stub OK */ window.canRunAds = true; cho JavaScript, hoặc chuỗi rỗng cho văn bản thuần).
  • XMLHttpRequest: Khi xhr.open(method, url) nhắm tới URL quảng cáo hoặc trình theo dõi bị chặn, script chuyển hướng cuộc gọi nội bộ sang URI data:text/plain, vô hại và ghi đè readyState (4), status (200), statusText ("OK"), responseText cùng response khi gọi xhr.send(), đồng thời phát các sự kiện readystatechange, load và loadend giả lập.
  • Navigator.sendBeacon: Các gói tin beacon bị chặn sẽ trả về true ngay lập tức mà không gửi dữ liệu qua mạng.
  • Thuộc tính động <script> và <img>: Khi gán .src hoặc setAttribute('src', ...) trên HTMLScriptElement trỏ tới tên miền quảng cáo, script đổi type = "javascript/blocked" và phát sự kiện load giả lập (triggerFakeScriptLoad). Khi gán .src trên HTMLImageElement trỏ tới URL quảng cáo, script giả lập một ảnh 32×32 đã tải xong (complete = true, naturalWidth = 32, naturalHeight = 32).

Những gì hook JavaScript không thể chặn:

  • Tài nguyên con khai báo tĩnh trong HTML được trình duyệt tải trước khi hook của userscript kịp chạy (chẳng hạn thẻ <img src="..."> hoặc <iframe src="..."> tĩnh được bộ quét tải trước của trình duyệt nạp sớm, dù bộ lọc DOM vẫn sẽ ẩn hoặc xóa phần tử đó sau khi tạo).
  • Yêu cầu mạng từ CSS (như background-image: url(...) hoặc @font-face).
  • Chuyển hướng HTTP từ máy chủ (301, 302, 307, 308) hoặc lưu lượng bên trong các iframe khác nguồn (cross-origin) nơi trình quản lý userscript không tiêm script.
  • Script ngữ cảnh chính trên các trang có Chính sách bảo mật nội dung (CSP) nghiêm ngặt hoặc giới hạn sandbox của WebKit chặn chèn thẻ <script> nội tuyến (injectMainWorldShield).

Vì sao quảng cáo trong luồng phát video khác với quảng cáo DOM thông thường#

Quảng cáo biểu ngữ hoặc cửa sổ bật lên truyền thống là một phần tử DOM tách biệt (<iframe>, <ins>, <div>, hoặc <script>) tải từ tên miền quảng cáo bên thứ ba. Việc xóa phần tử hoặc trả về phản hồi rỗng thường loại bỏ quảng cáo một cách gọn gàng.

Quảng cáo video trên các nền tảng như YouTube hoạt động khác hẳn:

  1. Hạ tầng truyền tải dùng chung: Các phân đoạn video và dữ liệu điều khiển trình phát thường được truyền qua cùng các điểm cuối API cốt lõi (/youtubei/v1/player, /youtubei/v1/next) và máy chủ CDN (googlevideo.com, youtube.com/videoplayback) phục vụ chính video nội dung bạn đang xem.
  2. Nguy cơ gây lỗi phát video: Nếu một script chặn mù quáng googlevideo.com hoặc hủy phản hồi /youtubei/v1/player, trình phát video sẽ bị treo, hiện màn hình đen hoặc báo lỗi phát.
  3. Xử lý phối hợp trạng thái trình phát: Vì Nvirya AdGuard phải cho phép các luồng media hợp lệ đi qua (RE_YOUTUBE_CDN_WHITELIST), script xử lý quảng cáo video qua hai lớp phối hợp bên trong initYouTubeShield():
    • Cắt tỉa JSON phản hồi (installYouTubeJsonPruner): Can thiệp vào ytInitialPlayerResponse, Response.prototype.json(), Response.prototype.text() và phản hồi XMLHttpRequest của trình phát để làm rỗng adPlacements, playerAds, adSlots, adBreakHeartbeatParams và adBreakRenderer trước khi trình phát đọc dữ liệu.
    • Tăng tốc qua trạng thái trình phát (scheduleVideoAdScan): Khi một quảng cáo vẫn lọt vào phần tử <video> HTML5 (phát hiện qua class .ad-showing hoặc .ad-interrupting trên #movie_player), script sẽ tự động nhấn nút bỏ qua nếu có, tắt tiếng video, tăng playbackRate lên 16 và tua tới duration - 0.05 giây ngay khi có dữ liệu thời lượng, sau đó khôi phục âm lượng và tốc độ phát ban đầu của bạn.

Xem thêm chi tiết tại Giới hạn đã biết và Khắc phục sự cố.