MV3 的 declarativeNetRequest:四字段规则、匹配优先级与 webRequest 的取舍
参考文档:MDN declarativeNetRequest、Chrome 官方文档。
阻塞式 webRequest 为什么被砍
MV2 的阻塞式 webRequest 要把每个请求回调到扩展进程里跑 JS 判断,扩展因此能看到完整 URL、header 和响应流。MV3 换成了 declarativeNetRequest:扩展只提交规则,请求由浏览器自己评估,不通知扩展单个请求。
请求不再经过扩展进程,后台页也就不再是拦截链路的必需项。代价是规则只能表达「条件 + 固定动作」,不能做任意编程式判断。Firefox 同样支持 DNR,但存在一些约束差异。
一条规则就四个字段
| 字段 | 含义 |
|---|---|
id | ruleset 内唯一,>= 1,必填 |
priority | 规则优先级,>= 1,默认 1,决定哪条规则生效 |
condition | 触发条件 |
action | 匹配时的动作:block / redirect / modifyHeaders / allow / allowAllRequests / upgradeScheme |
redirect 有个边界:如果 action 实际没有改变请求,或者重定向 URL 非法(例如 regexSubstitution 的结果不是合法 URL),就不会重定向,请求照常继续。
拦截来自 example.com、URL 中含 abc 子串的 script 请求:
[
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "abc",
"initiatorDomains": ["example.com"],
"resourceTypes": ["script"]
}
}
]
urlFilter 的四种写法
| urlFilter | 匹配 | 不匹配 |
|---|---|---|
abc | https://abcd.com、https://example.com/abcd | https://ab.com |
abc*d | https://abcd.com、https://example.com/abcxyzd | https://abc.com |
||a.example.com | https://a.example.com/、https://b.a.example.com/xyz | https://example.com/ |
|https* | https://example.com | http://example.com/、http://https.com |
abc 是子串匹配,abc*d 中间的 * 代表任意字符,|| 锚定域名,| 锚定 URL 开头。
static / dynamic / session 三套 ruleset
static:在 manifest 的 declarative_net_request 键里声明,文件打包在扩展内,用 updateEnabledRulesets 启用或禁用。已启用的集合跨浏览器会话持久,但不跨扩展更新;安装或更新时哪些静态规则集启用,由 manifest 键的内容决定。
{
"declarative_net_request": {
"rule_resources": [
{ "id": "ruleset_1", "enabled": true, "path": "rules.json" }
]
}
}
dynamic:用 updateDynamicRules 增删,跨会话、跨扩展更新持久。
session:用 updateSessionRules 增删,不跨浏览器会话持久。
静态规则非法时,只有在测试期才会看到错误和警告;正式安装的扩展里非法静态规则会被静默忽略。所以静态规则必须走一遍测试验证,不能靠肉眼看 JSON。
匹配优先级与歧义
先比 priority,默认 1 最低。priority 分不出胜负时,按 action 排序:allow(其余规则全部忽略)、allowAllRequests(仅 main_frame / sub_frame,且作用于该请求产生的后续子资源加载,含后代 frame)、block、upgradeScheme、redirect、modifyHeaders。
同 priority 同 action 类型时可能出现歧义:多个 block 无歧义;多个 redirect 只有一个生效,但可能形成链式重定向;多个 modifyHeaders 如果改的是不同 header 可以各自生效,改同一个 header 结果不确定。要控制顺序就设不同 priority。
Firefox 还会按规则所属 ruleset 排优先级:session > dynamic > static,这一点跨浏览器不可依赖(见 WECG issue 280)。多个扩展同时命中时,按 block > redirect/upgradeScheme > allow/allowAllRequests 排序;请求未被 block 或 redirect 时,再应用 modifyHeaders。
限额都用常量名表达
MAX_NUMBER_OF_STATIC_RULESETS:静态规则集数量上限。GUARANTEED_MINIMUM_STATIC_RULES:启用静态规则总数上限。MAX_NUMBER_OF_ENABLED_STATIC_RULESETS:启用静态规则集个数上限。- 全局上限会变化,用
getAvailableStaticRuleCount查当前可用数。 MAX_NUMBER_OF_DISABLED_STATIC_RULES:禁用的静态规则另受此限制,但仍计入GUARANTEED_MINIMUM_STATIC_RULES。- 动态 + session:Safari、Chrome <= 119、Firefox <= 127 用
MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES;Chrome 120、Firefox 128 起改用MAX_NUMBER_OF_DYNAMIC_RULES与MAX_NUMBER_OF_SESSION_RULES。 MAX_NUMBER_OF_REGEX_RULES:正则规则单独限额。
测试 API 与权限
testMatchOutcome、getMatchedRules、onRuleMatchedDebug 需要 declarativeNetRequestFeedback 权限。Chrome 只在 unpacked 扩展里可用;Firefox 要把 extensions.dnr.feedback 设为 true(about:config,或 web-ext --pref)。
权限本身二选一:declarativeNetRequest 或 declarativeNetRequestWithHostAccess。前者会出现在权限提示里,后者不出现。declarativeNetRequest 允许在没有 host permissions 的情况下 block 和 upgrade 请求;要 redirect、改 header,或使用 WithHostAccess,则需要 host permissions。除导航请求(main_frame / sub_frame)外,还需要请求发起者(initiator)的 host permissions。
有几类请求不参与匹配:特权浏览器请求、受限域名、来自其他扩展的请求。
和 webRequest 的取舍
DNR 在浏览器内评估,比 webRequest 每个请求都在扩展进程里跑 JS 更高效;请求不再经过扩展进程,也就不再需要 background page;用 declarativeNetRequest 做 block / upgrade 不需要 host permissions;扩展不读取用户的网络请求,隐私更好。
行为上还有几个差异值得记:Chrome 下用 DNR 拦截的图片、iframe 会在 DOM 中自动折叠;DNR 优先于 webRequest 被评估(同步拦截),被 DNR 删掉的 header 对 webRequest 扩展不可见。webRequest 保留的优势是灵活性——可以编程式判断请求,DNR 表达不了的逻辑仍得靠它或别的机制。
另外,content scripts 访问不到页面 JS 作用域:共享 DOM 但不共享 window,需要 window.postMessage,或通过 web_accessible_resources 注入 script 标签来通信。