
引言:MV3时代网络请求管理的变革
Chrome扩展的 Manifest V3(MV3)自发布以来,最为开发者所关注的变化之一,就是网络请求拦截机制的全面重构。在 Manifest V2 时代,
1 | webRequest |
API 的阻塞模式(blocking mode)赋予了扩展几乎无限的网络控制能力——拦截、修改、重定向、屏蔽任何请求。然而,这种能力也带来了严重的性能和隐私问题:每一个网络请求都需要经过扩展的 JavaScript 运行环境,导致页面加载延迟增加,同时也使得扩展能够监控用户的全部浏览活动。
MV3 中,Google 推出了
1 | declarativeNetRequest |
API 作为替代方案。与传统的
1 | webRequest |
不同,declarativeNetRequest 采用声明式规则(declarative rules),将请求匹配和处理的逻辑下放到浏览器内核层面执行,而非在扩展的 Service Worker 中逐条处理。这意味着:
- 性能大幅提升:规则匹配在浏览器底层 C++ 引擎中完成,无需经过 JavaScript 事件循环
- 更加安全:扩展无法动态读取请求内容,限制了数据泄露风险
- 后台休眠友好:Service Worker 无需保持唤醒状态来处理网络请求
- 规则数量有限:静态规则 30000 条上限,动态规则 5000 条上限,会话规则 5000 条上限
本文将深入讲解 declarativeNetRequest API 的核心概念、规则结构、动态规则管理、静态规则配置、条件匹配、以及常见实战场景,帮助你掌握 MV3 时代的网络请求管理技术。
一、declarativeNetRequest 核心概念
1.1 规则(Rule)的结构
declarativeNetRequest 中的每一条规则由三个主要部分构成:条件(condition)、操作(action)和优先级(priority)。理解规则结构是使用该 API 的基础。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 {
"id": 1, // 规则ID,需在当前规则集合中唯一
"priority": 1, // 优先级,数字越大越高
"condition": {
"urlFilter": "*://*.example.com/*",
"resourceTypes": ["main_frame", "sub_frame", "script", "image"],
"requestMethods": ["get", "post"],
"domainType": "thirdParty",
"tabIds": [],
"excludedTabIds": [],
"domains": [],
"excludedDomains": [],
"initiatorDomains": [],
"excludedInitiatorDomains": [],
"requestDomains": [],
"excludedRequestDomains": [],
"regexFilter": ""
},
"action": {
"type": "block", // block, redirect, allow, upgradeScheme, modifyHeaders, allowAllRequests
"redirect": {}, // 重定向目标
"requestHeaders": [], // 修改请求头
"responseHeaders": [] // 修改响应头
}
}
1.2 规则类型
declarativeNetRequest 支持六种不同的操作类型:
| 操作类型 | 说明 | 使用场景 | ||
|---|---|---|---|---|
|
完全阻止请求 | 广告拦截、跟踪器屏蔽、内容过滤 | ||
|
将请求重定向到另一个URL | 强制HTTPS、重定向到本地资源、URL重写 | ||
|
允许请求通过(覆盖低优先级规则) | 白名单机制、例外规则 | ||
|
将HTTP升级到HTTPS | 安全浏览增强 | ||
|
修改请求或响应头 | 自定义User-Agent、添加安全头、CORS调整 | ||
|
允许资源类型的所有请求(仅限main_frame) | 为整个页面设置白名单 |
1.3 规则集合的层级
declarativeNetRequest 的规则分为三个层级:
- 静态规则集(Static Rulesets):在 manifest.json 中声明的规则 JSON 文件,不可变,最多 30000 条规则
- 动态规则(Dynamic Rules):通过 API 在运行时添加/删除的规则,存储在浏览器中持久化,最多 5000 条
- 会话规则(Session Rules):通过 API 在运行时添加的临时规则,浏览器关闭后丢失,最多 5000 条
二、静态规则集的配置与使用
2.1 manifest.json 配置
要使用静态规则集,首先需要在 manifest.json 中声明
1 | declarativeNetRequest |
权限,并配置规则集文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 {
"manifest_version": 3,
"name": "网络请求管理器",
"version": "1.0.0",
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestFeedback",
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"*://*/*"
],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules/block_ads.json"
},
{
"id": "ruleset_2",
"enabled": false,
"path": "rules/redirect_https.json"
},
{
"id": "ruleset_3",
"enabled": true,
"path": "rules/security_headers.json"
}
]
}
}
2.2 编写规则 JSON 文件
以下是一个广告拦截规则集的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 // rules/block_ads.json
[
{
"id": 1,
"priority": 1,
"condition": {
"urlFilter": "||doubleclick.net^",
"resourceTypes": ["script", "image", "sub_frame"]
},
"action": {
"type": "block"
}
},
{
"id": 2,
"priority": 1,
"condition": {
"urlFilter": "||googleadservices.com^",
"resourceTypes": ["script", "image", "sub_frame", "xmlhttprequest"]
},
"action": {
"type": "block"
}
},
{
"id": 3,
"priority": 1,
"condition": {
"urlFilter": "||googlesyndication.com^",
"resourceTypes": ["script", "image", "sub_frame"]
},
"action": {
"type": "block"
}
}
]
注意:上面的
1 | urlFilter |
使用了类似 uBlock Origin 的过滤语法。双竖线
1 | || |
表示域名的开头,
1 | ^ |
表示分隔符(任何非字母数字字符或行尾)。
2.3 启用/禁用静态规则集
在扩展运行时,可以通过 API 动态启用或禁用静态规则集:
1
2
3
4
5
6
7
8
9
10 // 启用规则集
chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: ["ruleset_2"],
disableRulesetIds: ["ruleset_1"]
});
// 检查当前启用的规则集
chrome.declarativeNetRequest.getEnabledRulesets((rulesetIds) => {
console.log("已启用的规则集:", rulesetIds);
});
三、动态规则管理
3.1 添加动态规则
动态规则允许扩展在运行时根据情况添加自定义规则,常用于实现用户自定义的过滤规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 // 添加动态规则
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
"id": 1001,
"priority": 100,
"condition": {
"urlFilter": "||tracker.example.com^",
"resourceTypes": ["script", "image", "xmlhttprequest"]
},
"action": {
"type": "block"
}
},
{
"id": 1002,
"priority": 200,
"condition": {
"regexFilter": "^https?://.*\\.analytics\\..*/collect",
"resourceTypes": ["xmlhttprequest"]
},
"action": {
"type": "block"
}
}
],
removeRuleIds: [] // 可选:同时删除旧规则
}, () => {
if (chrome.runtime.lastError) {
console.error("添加规则失败:", chrome.runtime.lastError);
} else {
console.log("规则添加成功");
}
});
3.2 获取和删除规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 // 获取所有动态规则
chrome.declarativeNetRequest.getDynamicRules((rules) => {
console.log("当前动态规则数量:", rules.length);
rules.forEach(rule => {
console.log(`规则 #${rule.id}: ${rule.action.type} ${rule.condition.urlFilter || rule.condition.regexFilter}`);
});
});
// 删除指定的动态规则
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [],
removeRuleIds: [1001, 1002] // 删除ID为1001和1002的规则
});
// 清空所有动态规则
chrome.declarativeNetRequest.getDynamicRules((rules) => {
const ids = rules.map(r => r.id);
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [],
removeRuleIds: ids
});
});
3.3 会话规则
会话规则适用于临时性场景,例如用户在当前浏览会话中临时禁用某个网站的拦截:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 // 为当前标签页临时添加白名单规则(会话级别)
chrome.declarativeNetRequest.updateSessionRules({
addRules: [
{
"id": 9001,
"priority": 999, // 高优先级,覆盖其他规则
"condition": {
"requestDomains": ["example.com"],
"resourceTypes": ["main_frame", "sub_frame"]
},
"action": {
"type": "allowAllRequests"
}
}
],
removeRuleIds: []
});
// 获取会话规则
chrome.declarativeNetRequest.getSessionRules((rules) => {
console.log("会话规则:", rules);
});
// 清除所有会话规则
chrome.declarativeNetRequest.getSessionRules((rules) => {
chrome.declarativeNetRequest.updateSessionRules({
addRules: [],
removeRuleIds: rules.map(r => r.id)
});
});
四、URL过滤语法详解
4.1 urlFilter 模式
1 | urlFilter |
支持三种匹配模式,语法简洁但功能强大:
| 模式 | 示例 | 匹配说明 | ||
|---|---|---|---|---|
| 前缀匹配 |
|
匹配以 example.com 开头的域名及其子域名 | ||
| 后缀匹配 |
|
匹配以 .js 结尾的URL | ||
| 子串匹配 |
|
匹配URL中包含 analytics 的请求 | ||
| 锚定匹配 |
|
匹配以指定字符串开头的URL | ||
| 尾部锚定 |
|
匹配以指定字符串结尾的URL |
4.2 regexFilter 正则匹配
对于更复杂的匹配需求,可以使用
1 | regexFilter |
:
1
2
3
4
5
6
7
8
9
10
11 {
"id": 2001,
"priority": 100,
"condition": {
"regexFilter": "^https?://[^/]+\\.(com|cn|org)/tracking/[0-9]+/pixel\\.(gif|png)$",
"resourceTypes": ["image"]
},
"action": {
"type": "block"
}
}
使用正则表达式时需要注意:
- 正则表达式长度限制为 1800 个字符
- 不支持
1re2
语法中不支持的某些高级特性(如反向引用)
- 建议先使用
1isRegexSupported
方法验证正则是否有效
1
2
3
4
5
6
7
8
9
10 // 验证正则表达式是否被支持
chrome.declarativeNetRequest.isRegexSupported({
regex: "^https?://[^/]+\\.com/.*"
}, (result) => {
if (result.isSupported) {
console.log("正则表达式可用");
} else {
console.error("正则表达式不支持:", result.reason);
}
});
五、请求头修改实战
5.1 修改请求头
declarativeNetRequest 支持通过
1 | modifyHeaders |
操作修改请求头或响应头,这在很多场景中非常实用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 // 自定义 User-Agent
{
"id": 3001,
"priority": 100,
"condition": {
"urlFilter": "||example.com^",
"resourceTypes": ["main_frame", "sub_frame", "script", "xmlhttprequest"]
},
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "user-agent",
"operation": "set",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
]
}
}
5.2 移除安全敏感头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 // 移除 Referer 头(隐私保护)
{
"id": 3002,
"priority": 100,
"condition": {
"urlFilter": "||third-party-tracker.com^",
"domainType": "thirdParty",
"resourceTypes": ["image", "script", "xmlhttprequest"]
},
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "referer",
"operation": "remove"
},
{
"header": "cookie",
"operation": "remove"
}
]
}
}
5.3 修改响应头增强安全性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 // 添加安全响应头
{
"id": 3003,
"priority": 100,
"condition": {
"resourceTypes": ["main_frame", "sub_frame"],
"urlFilter": "||example.com^"
},
"action": {
"type": "modifyHeaders",
"responseHeaders": [
{
"header": "x-frame-options",
"operation": "set",
"value": "DENY"
},
{
"header": "x-content-type-options",
"operation": "set",
"value": "nosniff"
},
{
"header": "referrer-policy",
"operation": "set",
"value": "strict-origin-when-cross-origin"
}
]
}
}
六、综合实战案例:构建一个完整的隐私保护扩展
现在让我们将以上知识整合起来,构建一个完整的隐私保护扩展。该扩展将具备以下功能:
- 屏蔽第三方跟踪器
- 强制HTTPS连接
- 移除追踪相关的请求头
- 提供用户自定义白名单功能
6.1 项目结构
1
2
3
4
5
6
7
8
9
10
11 privacy-guard/
├── manifest.json
├── background.js
├── popup.html
├── popup.js
├── rules/
│ ├── block_trackers.json
│ ├── upgrade_https.json
│ └── remove_headers.json
└── icons/
└── icon128.png
6.2 规则文件
1
2
3
4
5
6
7
8
9
10
11 // rules/block_trackers.json
[
{"id": 1, "priority": 1, "condition": {"urlFilter": "||google-analytics.com^", "resourceTypes": ["script", "image", "xmlhttprequest"]}, "action": {"type": "block"}},
{"id": 2, "priority": 1, "condition": {"urlFilter": "||facebook.net/tr^", "resourceTypes": ["script", "image", "xmlhttprequest"]}, "action": {"type": "block"}},
{"id": 3, "priority": 1, "condition": {"urlFilter": "||amazon-adsystem.com^", "resourceTypes": ["script", "image", "sub_frame"]}, "action": {"type": "block"}}
]
// rules/remove_headers.json
[
{"id": 100, "priority": 1, "condition": {"domainType": "thirdParty", "resourceTypes": ["image", "script", "xmlhttprequest"]}, "action": {"type": "modifyHeaders", "requestHeaders": [{"header": "referer", "operation": "remove"}, {"header": "cookie", "operation": "remove"}]}}
]
6.3 background.js 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 // background.js
chrome.runtime.onInstalled.addListener(async () => {
// 初始化:确保规则集正确加载
const enabledSets = await chrome.declarativeNetRequest.getEnabledRulesets();
console.log("已启用规则集:", enabledSets);
});
// 监听来自 popup 的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.type) {
case "addWhitelist":
addWhitelistRule(message.domain);
break;
case "removeWhitelist":
removeWhitelistRule(message.domain);
break;
case "getWhitelist":
getWhitelistRules().then(sendResponse);
return true;
case "addCustomBlock":
addCustomBlockRule(message.urlPattern, message.resourceTypes);
break;
case "getStats":
getRuleStats().then(sendResponse);
return true;
}
});
async function addWhitelistRule(domain) {
// 查找是否已有该域名的白名单规则
const rules = await chrome.declarativeNetRequest.getDynamicRules();
const existingRule = rules.find(r => r.condition?.requestDomains?.[0] === domain);
if (existingRule) {
return; // 已存在,跳过
}
// 生成新ID
const maxId = rules.length > 0 ? Math.max(...rules.map(r => r.id)) : 10000;
const newRule = {
id: maxId + 1,
priority: 1000, // 高优先级,确保覆盖其他规则
condition: {
requestDomains: [domain],
resourceTypes: ["main_frame", "sub_frame", "script", "image", "xmlhttprequest"]
},
action: {
type: "allowAllRequests"
}
};
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [newRule],
removeRuleIds: []
});
}
async function removeWhitelistRule(domain) {
const rules = await chrome.declarativeNetRequest.getDynamicRules();
const ruleToRemove = rules.find(r => r.condition?.requestDomains?.[0] === domain);
if (ruleToRemove) {
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [],
removeRuleIds: [ruleToRemove.id]
});
}
}
async function getWhitelistRules() {
const rules = await chrome.declarativeNetRequest.getDynamicRules();
return rules
.filter(r => r.action.type === "allowAllRequests")
.map(r => r.condition.requestDomains?.[0])
.filter(Boolean);
}
async function getRuleStats() {
const [staticRules, dynamicRules, sessionRules] = await Promise.all([
chrome.declarativeNetRequest.getEnabledRulesets(),
chrome.declarativeNetRequest.getDynamicRules(),
chrome.declarativeNetRequest.getSessionRules()
]);
return {
enabledRulesets: staticRules,
dynamicRuleCount: dynamicRules.length,
sessionRuleCount: sessionRules.length
};
}
6.4 popup.js 用户交互
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 // popup.js
document.addEventListener("DOMContentLoaded", async () => {
// 加载统计信息
const stats = await chrome.runtime.sendMessage({ type: "getStats" });
document.getElementById("dynamic-count").textContent = stats.dynamicRuleCount;
document.getElementById("session-count").textContent = stats.sessionRuleCount;
document.getElementById("rulesets").textContent = stats.enabledRulesets.join(", ");
// 加载白名单列表
const whitelist = await chrome.runtime.sendMessage({ type: "getWhitelist" });
const whitelistEl = document.getElementById("whitelist");
whitelist.forEach(domain => {
const item = document.createElement("div");
item.className = "whitelist-item";
item.innerHTML = `
<span>${domain}</span>
<button class="remove-btn" data-domain="${domain}">移除</button>
`;
whitelistEl.appendChild(item);
});
// 绑定移除按钮事件
whitelistEl.addEventListener("click", async (e) => {
if (e.target.classList.contains("remove-btn")) {
const domain = e.target.dataset.domain;
await chrome.runtime.sendMessage({ type: "removeWhitelist", domain });
e.target.parentElement.remove();
}
});
});
// 添加白名单
document.getElementById("add-whitelist").addEventListener("click", async () => {
const input = document.getElementById("domain-input");
const domain = input.value.trim();
if (domain) {
await chrome.runtime.sendMessage({ type: "addWhitelist", domain });
input.value = "";
location.reload(); // 刷新列表
}
});
七、调试与测试
7.1 获取反馈信息
要调试 declarativeNetRequest 规则是否生效,需要申请
1 | declarativeNetRequestFeedback |
权限:
1
2
3
4
5
6
7
8
9 // 监听网络请求匹配事件
chrome.declarativeNetRequest.onRuleMatchedDebug.addListener((info) => {
console.log("规则匹配:", {
ruleId: info.rule.ruleId,
rulesetId: info.rule.rulesetId,
tabId: info.tabId,
request: info.request
});
});
7.2 在 Chrome 开发者工具中验证
打开 Chrome 的
1 | chrome://extensions |
页面,找到你的扩展,点击”Service Worker”链接打开控制台。你可以在控制台中看到规则匹配的日志输出。
同时,在 Network 面板中,被 declarativeNetRequest 阻止的请求会显示状态为
1 | (blocked:declarativeNetRequest) |
,被重定向的请求则显示
1 | 307 Internal Redirect |
。
7.3 常见问题排查
| 问题现象 | 可能原因 | 解决方案 | ||||
|---|---|---|---|---|---|---|
| 规则不生效 | 权限未正确声明 | 确保 manifest.json 中声明了
权限和
|
||||
| 规则ID冲突 | 动态规则ID与静态规则ID重复 | 动态规则ID建议从 10000 开始,避免与静态规则冲突 | ||||
| 正则表达式无效 | 使用了不支持的语法 | 使用
验证,并避免使用反向引用 |
||||
| 规则数量超限 | 添加的规则超过上限 | 检查动态规则上限 (5000) 和静态规则上限 (30000) | ||||
| 请求未被拦截 | 被更高优先级的 allow 规则覆盖 | 检查是否存在优先级更高的 allow/allowAllRequests 规则 | ||||
| Service Worker 未注册 | background.service_worker 路径错误 | 确认 manifest.json 中的路径相对于扩展根目录正确 |
八、从 webRequest 迁移的注意事项
如果你正在将现有的 MV2 扩展迁移到 MV3,需要注意以下关键差异:
8.1 功能对比
| 功能 | webRequest (MV2) | declarativeNetRequest (MV3) |
|---|---|---|
| 请求拦截 | ✅ 灵活,支持动态修改 | ✅ 声明式,性能更好 |
| 请求体修改 | ✅ 支持 | ❌ 不支持 |
| 响应体修改 | ✅ 支持 | ❌ 不支持 |
| 动态规则 | ✅ 完全灵活 | ⚠️ 有限制(5000条上限) |
| 性能 | ❌ JavaScript 事件循环瓶颈 | ✅ 浏览器原生引擎处理 |
| 隐私 | ❌ 可读取所有请求内容 | ✅ 无法读取请求/响应内容 |
| Service Worker 兼容 | ❌ 需要持久化后台页面 | ✅ 支持休眠,无需常驻 |
8.2 迁移策略
对于大多数 ad-blocker 和隐私保护类扩展,建议采用以下迁移策略:
- 静态规则优先:将不会频繁变动的规则放在静态规则集中,利用 30000 条上限
- 动态规则做补充:用户自定义规则使用动态规则,注意 5000 条上限
- 会话规则做临时:临时性白名单或调试规则使用会话规则
- 考虑多个规则集:按功能模块拆分规则集,通过 API 开关控制
- 优化规则数量:使用通配符和正则合并规则,减少规则总数
总结
declarativeNetRequest API 是 Chrome 扩展 MV3 时代最重要的变革之一。虽然它限制了 webRequest 时代的一些灵活性,但在性能、隐私和安全性方面带来了显著提升。通过本文的学习,你应该已经掌握了:
- declarativeNetRequest 的核心概念和规则结构
- 静态规则集的配置和使用方法
- 动态规则和会话规则的管理技巧
- URL 过滤语法和正则表达式的使用
- 请求头和响应头修改的实战应用
- 构建完整隐私保护扩展的完整流程
- 从 webRequest 迁移的注意事项和策略
随着 Chrome 逐步淘汰 MV2,掌握 declarativeNetRequest 已经成为 Chrome 扩展开发的必备技能。建议你在实际项目中多加练习,逐步将现有的 webRequest 代码迁移到声明式规则体系,以充分利用 MV3 带来的性能优势。
如果你在开发过程中遇到任何问题,可以参考 Chrome 官方文档 或使用
1 | chrome://extensions |
的检查视图功能进行调试。
汤不热吧