AdGuard 发布世界首个基于 Manifest V3 的广告拦截程序
Manifest V3,即 Chrome 扩展程序的新 API 已经实打实构成了威胁。目前,十几个广告拦截扩展,包括 AdGuard 浏览器插件在内,很快就能知道谁还能继续运行。
Manifest V3 的浪潮一直在逐步但不可阻挡地形成。2018 年,Google 首次宣布描述新 API 的文件后,开发者社区瞬间被批评的浪潮吞没。我们自然也是加入了这个行列。我们发布了几篇描述 Manifest V3 实施的潜在消极后果的文章,并且说道:“希望事情不会变得那么糟糕”。
尽管公众哗然,2020 年末 Manifest V3 和 Chrome 88 Beta 正式上线了。从 2022 年一月起,开发者再无法在 Chrome Web Store 里上传基于 Manifest V2 的插件。启动的最后阶段即将到来:从 2023 年一月开始,Manifest V2 上的所有扩展都将停止工作,即使是那些早先被添加到 Chrome Web Store 的扩展。
如果有用户使用 Windows、Mac,或是 Android 版的 AdGuard,你不用担心。适用于这些平台的软件不受浏览器的任何限制。
幸运的是,我们已经准备就绪了。
实验性的 AdGuard MV3 浏览器扩展
2021 年中叶,我们开始着手于,在严格 Manifest V3 的限制下,依然能够有效地拦截广告的新扩展程序的样板。本任务并不简单。当时,新 API 还比较粗糙。有一些方面经过最后确定,没有按照预期运作。但经过千难万阻我们还是克服了困难,并证明了广告拦截程序即使在 Manifest V3 之后也能生存。
在开发样板的过程中,我们遇到不少由新 API 功能引起的严重问题。我们解决了一些问题,不过还有一些问题我们不得不接受。下面我们要一个个细节讲这些问题。
与此同时,如果有用户想试用新插件,可以从 Chrome Web Store 安装它。
这里有一个简短的视频,简单介绍了它的工作原理。
规则限制
如有用户不了解什么叫过滤规则和广告拦截程序运作的基本原理,或是只是想要复习一下旧知识,欢迎阅读这篇知识库文章。
Manifest V3 把包含在扩展过滤器中的所有规则分为静态(内置)和动态规则,其数量受到了极大的限制。
对于静态规则,Chrome 设置了每个扩展的最低保证限制为 30,000 条规则,单个用户安装的所有扩展的总限制为 330,000 条规则(这也考虑到了每个扩展的 1,000 条 RegExp 规则限制)。一个扩展可能会得到所有允许的规则量,或者可能有多个,那么也许一些扩展会达不到限制的要求。
如果这种情况发生在我们的扩展上(这可能在任何时候发生,例如在更新、服务工作重启、我们或第三方拦截程序中的过滤器设置改变之后),它将显示一个信息,即浏览器已经改变了运作过滤器的设置,只留下基本的广告过滤器启用。在最坏的情况下,即使是基本的过滤器也可能无法启用:那么用户基本上就无法再受到 AdGuard 保护。
上述的全部场景都被我们预想到的,并显示在一个单独的屏幕上,准确地描述浏览器禁用了什么,启用了什么。
对于动态规则,用户可以在其中添加自己的规则或过滤器,不过有一个小小的限制,即 5000 条,包括 1000 条 RegExp 规则的限制。如果超过了这个限制,AdGuard MV3 将只能应用前 5000 条规则,其余的将保持未启用状态。
限额用尽和规则截止的信息将这样被显示:
Manifest V3 的局限性不仅不利于过滤质量和用户体验,而且也不利于过滤器开发社区。以前,任何人都曾经可以为自己创建一个过滤器。随着时间的推移,一些过滤器可能会变得火爆流行过滤器,并被列入拦截程序的推荐名单内。这一点现在更难实现。毕竟,拦截程序必须使用预定义的过滤器(不超过 50 个),而且我们必须对用户可用的过滤器进行严格筛选。当然,用户仍然可以手动设置自己的过滤器。但别忘了,所有自定义过滤器的限制为 5000 条规则。
对问题的进一步描述包括很多细节,大部分是开发人员可以理解的。如果对技术方面不感兴趣的话,可以随意跳过这部分。
声明性规则
Manifest V3 被宣布之前,过滤引擎是由扩展程序从服务器上下载的过滤器动态建立的。接下来,构成过滤器的规则被应用在页面加载的不同阶段。
例如,规则可以在浏览器发送请求之前被运作:首先触发 onBeforeRequest
事件,浏览器询问如何处理具体的请求,扩展将通过阻止或重定向来动态响应。修饰规则是在稍后,当页面被加载和 DOM(文档对象模型)出现时,应用的。
现在,有 Manifest V3 后,onBeforeRequest
不再可以被应用。Chrome 建议使用 declarativeNetRequest API
,浏览器获得修改请求的权利。该扩展只声明了一套声明性规则,浏览器将根据这些规则修改或阻止网络请求。
声明性规则的语法
声明性规则的语法与现代广告拦截程序常用的语法有很大不同。许多社区成员可能会放弃在 Manifest V3 中工作,以避免花时间创建 Chrome 专用规则。
每一个规则必须包括以下:
-
id
– 规则的标识符,用于将声明性规则与文本规则联系起来。 -
priority
– 规则的优先权,决定该规则将如何应用于查询。 -
action
– 规则的运作。
有三个种类,包括block
– 拦截请求。redirect
或upgradeScheme
– 重定向请求。allow
或allowAllRequests
– 允许请求。
-
condition
– 适用该规则的条件。
规则的例子:
{
"id": 1,
"priority": 1,
"action": { "type" : "block" },
"condition": {
"urlFilter": "abc",
"domains": ["example.org"],
"resourceTypes": ["script"]
}
}
这条规则将阻止所有对地址中含有以 abc
子串且来自域名为 example.org
的网站的脚本请求。
这种情况给我们产生了强烈的似曾相识的感觉,当我们开发 Safari 的广告拦截扩展时,我们不得不想办法将我们的规则语法转换成浏览器开发者强加的语法。这一次,我们使用一个类似的解决方案,将我们的语法变成声明性的 Chrome 规则。
为了转换静态和动态规则,我们在 @adguard/tsurlfilter 库中添加了一个模块。该库运行过滤器规则并将其转换为声明性规则,然后将其合并为规则集并存储在 .json
文件中。该库在文本规则和 JSON 规则之间建立了一个映射表,这样它们之间的联系就不会丢失。
几个关于转换器工作原理的例子:
The rule ||example.com^$script
is converted to
||example.com^$script
规则转换成
{
"id": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "||example.com^",
"resourceTypes": [
"script"
],
"isUrlFilterCaseSensitive": false
}
}
The rule @@||example.com^$script$domain=example.org
is converted to
@@||example.com^$script$domain=example.org
规则转换成
{
"priority": 1,
"id": 23,
"action": {
"type": "allow"
},
"condition": {
"urlFilter": "||example.com^$script",
"initiatorDomains": [
"example.org"
],
"isUrlFilterCaseSensitive": false
}
}
大部分数据转换正确,不过一些功能由于各种限制而失去了一些功能:
$removeparam
不支持排除项(~)和正则表达式(RegExp)。- 对于正则表达式,Chrome 使用其自身对此类表达式的实现方式,因此有一部分标准功能无法被支持。例如,这些正则表达式包含反向引用(backreferences)、向前负向匹配(negative lookaheads)和占有量词(possessive quantifiers)。
negative lookahead
经常用于过滤器中。快速搜索显示,目前 AdGuard 过滤器中有 43 条规则含有这种表达。乍一看,这不是很多,但请记住,这些规则中的大多数应该在许多不同的域上工作,所以我说仅这一限制就削弱了 1000 多个网站的广告拦截。- 正则表达式在 Chrome 浏览器内还会对消耗的内存量进行验证。由于我们不太确定在这种情况下使用的是哪种实现方式,所以一些正则表达式可能会有一些问题。
- 不支持 Cookie 规则。
- 还有很多问题,我们在这里没有提到,以免给大家带来不必要的困惑。
声明性规则的问题非常明显。它们的语法严重限制了我们的扩展可以做什么。而且,不幸的是,我们对此无能为力,只能希望 Chrome 浏览器的开发者随着时间的推移对其进行改进。
规则集
根据新的 API,声明性规则必须被组合成规则集(rulesets)。
在 Manifest V3 中整合规则集的一个例子:
{
"name": "AdGuard AdBlocker MV3",
"version": "1",
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
},
…
}
规则集在 manifest.json
文件中具体被指定,并且在扩展已安装或更新后才加载。这也是个大问题。有时候,过滤规则破坏了一个或几个网站的排版或性能。面对如此之多的规则,从统计学上看,几乎不可能完全避免此类事件。更重要的是,网站在不断变化,早先没有问题的规则现在可能会造成问题。但这没关系:我们有一个简单的解决方案来解决这个问题。
假设一个这样的规则进入过滤器中,因此需要快速禁用。Manifest V2 扩展为此使用了 $badfilter
修改器。过滤器的开发者将添加一个带有指定修改器的规则,扩展将得到更新,新规则将禁用引用的规则,生活将变得更好。正如用户所理解的那样,这个"技巧"在 Manifest V3 中不起作用。
现在,用户可能需要等待几天才能有过滤器的更新:在 Chrome 商店中添加新版本的扩展是不够的,用户必须等待它经过审查。遗憾的是,没有其他方法可以为用户提供更新的过滤器。关于启用和禁用个别规则的能力,可能还有一线生机。毕竟,有可能允许扩展程序快速更新过滤器。
统计数据和过滤记录
基于 Manifest V2 的 AdGuard 浏览器扩展,具有显示用户浏览器发送的所有请求,以及关于它们的详细信息的过滤日志。尤其是,用户可以看到哪个过滤规则被用来阻止某个规则。
由于 Chrome 现在会自己屏蔽查询,只与在开发者模式下解压和安装的扩展分享统计数据,因此我们无法以通常的方式实现过滤日志。但我们可以想出一个有趣的替代方案,我们计划在最后的扩展中这么做。
因此,当用户打开过滤器日志时,它将运行引擎,该引擎根据 Manifest V2 规则工作。它不会对查询做任何事情,只是显示哪些规则可能已经被应用。通过将 Chrome 的统计数据与旧引擎的结果进行比较,我们将得到查询处理的大致情况。
当前版本的原型并没有实现过滤日志。相反,过滤器开发人员将不得不使用 Chrome 浏览器开发人员推荐的机制。重点是,声明性的 NetRequest 确实允许用户获得关于哪个规则被触发的信息。但有一个细微的差别:为了做到这一点,我们需要以"未打包"的形式安装扩展。也就是说,用户需要克隆我们的资源库,"抓取"扩展,将浏览器切换到开发者模式,只有这样用户才能使用工具来调试过滤器。
服务工作原理
有了 Manifest V2,背景页(background page)已经成为过去。这个扩展在安装后或打开浏览器时运行一次,然后在后台运行。在 Manifest V3 中,这个页面被所谓的服务工作者所取代,它经常被浏览器打断。
当浏览器停止服务工作时,扩展进入“休眠模式”,声明性规则运作,但是通过动态方式负载的修饰规则不运作。为了使扩展功能发挥作用,必须有东西来唤醒服务工作者:可以是加载一个页面,也可以是被发送到 Service worker 的消息。
当 Service worker 返回到工作状态时,扩展开始从资源库中读取过滤规则并进行处理,然后能够快速找到它们。在这段时间内,即 1.5-2 秒(初始安装时约 3 秒),该扩展并没有从外观上屏蔽广告,但广告请求被浏览器本身阻止了。然后引擎启动,广告消失。
结论
虽然 Manifest V3 具有上述的限制,AdGuard MV3 依然能够有效地保护用户免受广告和跟踪器地打扰:
- 预防性地阻止对跟踪器的请求
- 隐藏横幅广告、社交小工具和其他恼人的元素
- 阻止视频共享平台上的广告,包括 YouTube
这个实验性的插件可能不像它的前辈那样好用有效,但大多数用户不会感觉到很大的区别。唯一会被注意到的是,由于延迟应用修饰规则而导致广告不会马上消失。
我们制作这个原型的目的是为了测试新的方法并获得用户的宝贵反馈意见。因此,请大家积极尝试新的扩展,并告诉我们可以改进的地方。像往常一样,我们开放了原型的源代码,用户可以在 Github 上找到它。如果有用户对代码有任何问题或想提出什么建议,请通过 GitHub 上的 Issue 联系我们。
通过今天发布的基于 Manifest V3 的实验性扩展,在广告拦截程序开发者中可以算是先驱者,我们可以说,我们即将战胜谷歌的限制。前路漫漫,但可以说,即使在取消 Manifest V2 之后,Google Chrome 浏览器用户也能通过 AdGuard 浏览器扩展保护自己免受广告和跟踪器侵扰。