已知限制
Nvirya AdGuard 运行在浏览器 JavaScript 环境与用户脚本管理器的安全约束之内。本页记录了经 v11.7.0 源码及移动端/桌面端环境验证的已知技术限制。
A. YouTube 移动端广告跳过延迟#
为什么 YouTube 广告有时会短暂出现一下才被跳过或关闭?#
在 YouTube(youtube.com、m.youtube.com、youtu.be 与 youtube-nocookie.com)上,Nvirya AdGuard 将所有 YouTube 专属逻辑隔离在 initYouTubeShield() 中,且仅在开启视频保护(config.playerProtection)时运行。
脚本采用两阶段机制处理 YouTube 视频广告:
- 播放器 JSON 元数据裁剪(
installYouTubeJsonPruner与mainWorldShield):
当 YouTube 加载初始播放器数据(window.ytInitialPlayerResponse)或通过 Window.fetch / XMLHttpRequest 请求 /youtubei/v1/player、/youtubei/v1/watch、/youtubei/v1/next 时,脚本会检查 JSON 对象并移除 adPlacements、playerAds、adSlots、adBreakHeartbeatParams 与 adBreakRenderer 字段,同时拦截独立的广告统计端点(RE_YOUTUBE_AD_ENDPOINT,如 /youtubei/v1/player/ad_break、/api/stats/ads、/pagead/ 和 doubleclick.net)。
- DOM 播放器状态回退处理(
scheduleVideoAdScan与handleAdVideo):
如果 YouTube 通过绕过初始 JSON 裁剪的路径下发广告(例如在移动端 Safari/WebKit 上初始页面数据在脚本注入完成前已被解析,或 YouTube 更新了播放器响应结构),广告就会开始加载到主 <video> 元素中。此时,#movie_player 或 .html5-video-player 会带上 .ad-showing 或 .ad-interrupting 类名。
当 hasAdPlayerMarker(video) 检测到 .ad-showing 或 .ad-interrupting 时,scheduleVideoAdScan()(基于 requestAnimationFrame 运行,具有 150 ms 最小节流间隔与 500 ms 活动轮询定时器,由 timeupdate、loadedmetadata、durationchange、play、ended、yt-navigate-finish 及 DOM 变动触发)会在 handleAdVideo(video) 中执行以下操作:
- 点击可见的跳过按钮:匹配
.ytp-skip-ad-button、.ytp-ad-skip-button、.ytp-ad-skip-button-modern、.ytp-ad-SkipButton、ytm-skip-ad-renderer button、.ytp-ad-overlay-close-button或.videoAdUiSkipButton。 - 保存您当前的音量与倍速设置:将
muted、volume和playbackRate记录到activeAdVideos中。 - 静音并加速播放广告:设置
video.muted = true、video.volume = 0且video.playbackRate = 16。 - 跳转到广告流末尾:一旦
video.duration为大于0.5秒的有限数值(isFinite(duration) && duration > 0.5),脚本会通过video.currentTime(或回退至video.fastSeek())跳转到Math.max(0, duration - 0.05)。 - 恢复播放器状态(
restoreAdVideo):一旦.ad-showing/.ad-interrupting状态清除,立即恢复您原有的muted、volume与playbackRate。
为什么脚本不直接拦截媒体流请求#
Nvirya AdGuard 明确将媒体分发端点列入白名单(RE_YOUTUBE_CDN_WHITELIST,包括 googlevideo.com、youtube.com/videoplayback、ytimg.com 和 youtube.com/s/player)。粗暴拦截与视频播放关联的媒体流请求会导致 HTML5 播放器卡死、无限缓冲或显示播放错误界面。由于回退机制必须等待播放器挂载 .ad-showing 状态并报告有效的 video.duration(> 0.5 s)后才能跳转至末尾,因此在广告被跳过前可能会出现短暂的画面过渡。
区分正常回退行为与故障#
| 观察到的现象 | 技术含义 | 建议操作 |
|---|---|---|
| 广告短暂闪过、处于静音状态并立即跳到末尾 | 广告进入 <video> 元素时 handleAdVideo 状态回退机制的预期表现 | 无需任何操作 |
| 广告以正常速度有声播放或持续停留 | YouTube 播放器结构或广告类名发生变化,或视频保护已关闭 | 确认设置中的视频保护已开启,并参阅 故障排除指南 |
| 视频无限转圈缓冲或显示播放错误 | 页内裁剪、YouTube 播放器更新或外部网络/DNS 过滤器之间可能存在冲突 | 按照 故障排除指南 中的步骤排查 |
B. VPN、DNS 过滤器、Shadowrocket、Surge 与 Loon#
许多移动端和桌面端用户会同时运行 Nvirya AdGuard 与网络层过滤工具,例如 Shadowrocket、Surge、Loon、AdGuard DNS、NextDNS 或带有广告拦截功能的 VPN。
网络层拦截与用户脚本的交互原理#
当网站加载广告脚本或诱饵请求(bait request)时:
- 当 Nvirya AdGuard 单独处理请求时: 其页内钩子(
mainWorldShield与installNetworkGuard)会拦截发往广告域名的fetch和XMLHttpRequest调用并返回合成的200 OK响应,或拦截<script>与<img>元素以伪造onload事件并填充模拟全局对象(window.googletag、window.OBR、window.Criteo、window._taboola、window.apstag、window.COMSCORE、window.adsbygoogle)。 - 当 VPN 或 DNS 过滤器优先拦截该域名时: 请求在到达浏览器之前就已在 DNS 或 TCP/TLS 层被拒绝(
ERR_NAME_NOT_RESOLVED或ERR_CONNECTION_REFUSED)。- 对于
fetch()请求,Nvirya AdGuard 针对常见广告域名(doubleclick、googlesyndication、googleadservices、pagead、outbrain、taboola、criteo等)提供了.catch()回退处理,可将网络拒绝转换为合成的200 OK响应。 - 然而,如果网络层过滤器在 Nvirya AdGuard 主世界钩子运行之前拦截了原生 HTML
<script>、<img>或<iframe>标签——或者 WebKit 沙盒隔离阻止了主世界注入——浏览器就会触发原生error事件,用户脚本无法始终拦截或覆盖该错误。
- 对于
实用建议#
- 不要假定所有反广告拦截提示都是由 VPN 或 DNS 过滤器引起的。 网站使用多种检测手段,某些反广告拦截墙无论 DNS 如何设置都会触发。
- 不要全局关闭您的 VPN 或 DNS 防护。 如果某个特定网站因检测到 DNS 层拦截而拒绝加载内容(且仅暂停 Nvirya AdGuard 仍无法解决),建议仅在代理/DNS 工具中针对该特定网站或误判域名添加放行规则,而不是关闭整个系统的网络安全防护。
C. Android CPU 与电池消耗#
在复杂的现代网站上运行任何用户脚本都会增加额外的 JavaScript 执行开销。在 Android 设备上,性能与电量消耗取决于:
- 页面复杂度与 DOM 规模: 无限滚动信息流、实时体育/金融行情以及大型单页应用每分钟会产生数千次 DOM 变动。
- 浏览器内核与 WebView 实现: 独立 Android 浏览器(如 Firefox for Android、Cromite/Kiwi)与基于 Android System WebView 的浏览器(如 XBrowser)在 JavaScript JIT 编译速度、垃圾回收及脚本注入开销方面存在明显差异。
- 已启用的脚本功能: 在频繁修改属性的页面上,DOM 变动检查、Shadow DOM 扫描与视频播放器事件监听会消耗更多 CPU。
v11.7.0 内置的性能保护机制#
Nvirya AdGuard 包含多项专门限制 CPU 开销的架构优化:
- 严格的 YouTube 引擎隔离(
initYouTubeShield): YouTube 播放器监听器、JSON 裁剪器与专属 CSS 规则绝不会在非 YouTube 域名上初始化。 - 搜索引擎 DOM 变动防抖(
RE_SEARCH_DEBOUNCE_HOST): 在 Google 搜索与 Bing 搜索上,DOM 变动批次会进行150 ms防抖,并跳过框架内部属性(jsaction、data-ved、jscontroller)。 - 时间分片分块处理(
processInChunks): 排队的 DOM 节点(上限MAX_QUEUE = 800)与点击陷阱检查(上限1,400)通过requestIdleCallback或requestAnimationFrame分块执行,每帧最大时间预算为8 ms。 - 点击陷阱快速跳过(
isInvisibleTrap): 常见内联文本标签(<p>、<span>、<h1>–<h6>、<li>、<td>、<tr>、<code>等)及位于主文章容器(article、main、#content)内的节点会在调用window.getComputedStyle()或getBoundingClientRect()之前立即跳过。
如何降低开销或排查性能回退#
如果某个特定网站在 Android 上出现发热、卡顿或耗电过快:
- 点按快捷悬浮按钮(或按
Alt+A),在概览标签页中选择本次会话暂停(或允许此网站)。 - 如果不需要在该网站上自动跳过视频广告,可在设置标签页中关闭视频保护。
- 如果您在设置中开启了深度网络过滤(
strictMode),请注意它会启用单信号修饰性隐藏(signals >= 1 && config.strictMode),比默认配置检查和隐藏更多元素。 - 如果暂停 Nvirya AdGuard 后页面卡顿立即消失,请通过发送反馈提交该网站域名,以便维护者分析并优化该站点的变动模式。
D. iOS Safari 与 WebKit 限制#
iOS 与 iPadOS 浏览器(包括搭配 Stay 使用的 Safari)均运行在 Apple 的 WebKit 内核之上,其安全与沙盒隔离边界比桌面浏览器和 Android Chromium/Gecko 浏览器更加严格:
- 隔离的 JavaScript 世界(Isolated Worlds): iOS Safari 上的用户脚本通常运行在隔离的内容脚本上下文中,而非页面的主 JavaScript 世界。虽然 DOM 修改(如隐藏元素或插入
<style>标签)在两个世界之间共享,但window上的全局变量(如window.fetch、window.open或window.googletag)是隔离的,除非能将<script>标签直接注入页面 DOM(injectMainWorldShield)。 - 内容安全策略 (CSP) 与注入时机: 即使
injectMainWorldShield()向document.documentElement插入了<script>元素,网站的 内容安全策略 (CSP) 或早期内联脚本仍可能阻止或早于注入脚本执行。此外,WebKit 的扩展沙盒模型、脚本注入时机以及缺乏特权网络 API 同样限制了用户脚本在 iOS 上的拦截能力。 绕过反广告拦截显示不支持(unsupported)的含义:
在 v11.7.0 中,checkBypassSupport() 会检测当前设备是否为 iOS/iPadOS(isAppleDevice()),并检查当前上下文中 typeof window.__NVIRYA_MAIN_SHIELD__ !== 'undefined' 是否可用。
- 当
checkBypassSupport()在 iOS/iPadOS 上返回false时:- 在 关于 → 系统功能 下,绕过反广告拦截 会显示
不支持徽章。 - 在 设置 标签页下,绕过反广告拦截 开关会处于禁用(置灰)状态,并显示说明:“受 iOS WebKit 沙盒限制,无法使用”。
- 在 关于 → 系统功能 下,绕过反广告拦截 会显示
- 该状态明确表示主世界全局属性模拟(
__NVIRYA_MAIN_SHIELD__)在当前隔离的 WebKit 环境中不可用。标准修饰性 CSS 过滤、隐藏页面元素、清理残留框架与解除页面滚动锁定依然正常工作。