Chrome 扩展开发完全指南 / 第 11 章:网络请求(Networking)
第 11 章:网络请求(Networking)
网络请求的拦截和修改是广告拦截器、隐私保护工具、API 调试器等扩展的核心能力。Manifest V3 用声明式的 declarativeNetRequest 替代了 MV2 的 webRequest,本章将全面讲解两种方式以及相关的网络 API。
11.1 MV3 网络请求模型
webRequest vs declarativeNetRequest
| 特性 |
webRequest (MV2) |
declarativeNetRequest (MV3) |
| 工作方式 |
命令式,JS 回调 |
声明式,预定义规则 |
| 能力 |
读取/修改/阻断/重定向 |
阻断/重定向/修改头 |
| 远程代码 |
允许动态规则 |
静态规则 + 动态规则(有限) |
| 性能 |
可能阻塞请求 |
由浏览器原生处理,更高效 |
| 隐私 |
扩展可读取所有请求数据 |
扩展无法读取请求内容 |
| 状态 |
仅 observe 权限仍在 |
MV3 推荐方案 |
11.2 declarativeNetRequest
11.2.1 权限声明
{
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"*://*.example.com/*"
]
}
| 权限 |
说明 |
declarativeNetRequest |
使用规则匹配,但不能指定 host 以外的 URL |
declarativeNetRequestWithHostAccess |
需配合 host_permissions 使用 |
declarativeNetRequestFeedback |
允许监听规则匹配事件(限 Dev Channel) |
11.2.2 静态规则
在 manifest.json 中声明规则文件:
{
"declarative_net_request": {
"rule_resources": [
{
"id": "block_ads",
"enabled": true,
"path": "rules/block-ads.json"
},
{
"id": "redirect_tracker",
"enabled": false,
"path": "rules/redirect-trackers.json"
}
]
}
}
规则文件格式(rules/block-ads.json):
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "||ads.example.com",
"resourceTypes": ["script", "image", "xmlhttprequest", "sub_frame"]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"url": "https://example.com/tracker-pixel.gif"
}
},
"condition": {
"urlFilter": "||tracker.example.com/pixel",
"resourceTypes": ["image"]
}
},
{
"id": 3,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Referer",
"operation": "remove"
}
]
},
"condition": {
"urlFilter": "||api.example.com",
"resourceTypes": ["xmlhttprequest"]
}
},
{
"id": 4,
"priority": 2,
"action": {
"type": "allow"
},
"condition": {
"urlFilter": "||safe-ads.example.com",
"resourceTypes": ["script"]
}
}
]
11.2.3 规则动作类型
| 动作类型 |
说明 |
用途 |
block |
阻断请求 |
广告拦截 |
redirect |
重定向请求 |
替换资源、去跟踪参数 |
allow |
允许请求(覆盖更高优先级的阻断) |
白名单 |
allowAllRequests |
允许所有子请求 |
信任特定页面 |
upgradeScheme |
升级 HTTP 到 HTTPS |
安全升级 |
modifyHeaders |
修改请求/响应头 |
移除跟踪头 |
11.2.4 URL 过滤语法
基本模式: ||example.com/path
| 模式 |
含义 |
匹配 |
example.com |
包含 |
any.example.com/path |
|example.com |
开头匹配 |
example.com/path |
example.com| |
结尾匹配 |
sub.example.com |
|example.com| |
精确匹配 |
仅 example.com |
| ` |
|
example.com` |
11.2.5 规则条件字段
{
"condition": {
"urlFilter": "||example.com",
"regexFilter": "https://api\\.example\\.com/v[0-9]+/.*",
"resourceTypes": ["script", "image", "stylesheet"],
"excludedResourceTypes": ["main_frame"],
"domains": ["example.com"],
"excludedDomains": ["safe.example.com"],
"initiatorDomains": ["myapp.com"],
"tabIds": [1, 2, 3],
"excludedTabIds": [4],
"isUrlFilterCaseSensitive": false,
"requestMethods": ["get", "post"],
"requestHeaders": [
{
"header": "Content-Type",
"values": ["application/json"]
}
]
}
}
| 资源类型 |
说明 |
main_frame |
主页面 |
sub_frame |
iframe |
stylesheet |
CSS 文件 |
script |
JavaScript 文件 |
image |
图片 |
font |
字体 |
xmlhttprequest |
XMLHttpRequest / fetch |
websocket |
WebSocket 连接 |
media |
音视频资源 |
other |
其他资源 |
11.3 动态规则
// 添加动态规则
async function addBlockingRule() {
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
id: 1001,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: '||new-tracker.com',
resourceTypes: ['script', 'xmlhttprequest']
}
}
],
removeRuleIds: [1001] // 先移除同 ID 的旧规则
});
}
// 删除动态规则
async function removeBlockingRule() {
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [1001]
});
}
// 获取当前规则
async function listRules() {
const rules = await chrome.declarativeNetRequest.getDynamicRules();
console.log('动态规则:', rules);
const sessionRules = await chrome.declarativeNetRequest.getSessionRules();
console.log('会话规则:', sessionRules);
}
// 更新静态规则集状态
async function toggleRuleset(rulesetId, enabled) {
const rulesets = await chrome.declarativeNetRequest.getEnabledRulesets();
await chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: enabled ? [rulesetId] : [],
disableRulesetIds: enabled ? [] : [rulesetId]
});
}
会话规则
// 会话规则仅在当前浏览器会话中有效,不会持久化
await chrome.declarativeNetRequest.updateSessionRules({
addRules: [{
id: 5001,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: '||temp-block.com',
resourceTypes: ['script']
}
}],
removeRuleIds: []
});
规则数量限制
| 类型 |
最大数量 |
说明 |
| 静态规则(每扩展) |
30,000 |
在 manifest 中声明 |
| 动态规则 |
30,000 |
通过 API 添加 |
| 会话规则 |
5,000 |
仅当前会话 |
| 所有扩展静态规则总量 |
330,000 |
Chrome 全局限制 |
11.4 修改请求头和响应头
[
{
"id": 100,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{ "header": "X-Custom-Header", "operation": "set", "value": "my-value" },
{ "header": "Referer", "operation": "remove" }
],
"responseHeaders": [
{ "header": "X-Frame-Options", "operation": "remove" },
{ "header": "Access-Control-Allow-Origin", "operation": "set", "value": "*" }
]
},
"condition": {
"urlFilter": "||api.example.com",
"resourceTypes": ["xmlhttprequest"]
}
}
]
| 操作 |
说明 |
set |
设置或覆盖头的值 |
append |
在现有值后追加 |
remove |
移除该头 |
11.5 webNavigation 与网络事件
// 监听导航事件
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
console.log(`即将导航: ${details.url} (标签页: ${details.tabId})`);
});
chrome.webNavigation.onCompleted.addListener((details) => {
if (details.frameId === 0) { // 主框架
console.log(`页面加载完成: ${details.url}`);
}
});
// 监听子框架加载
chrome.webNavigation.onDOMContentLoaded.addListener((details) => {
if (details.frameId !== 0) {
console.log(`iframe 加载: ${details.url}`);
}
});
// 检测 SPA 导航
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log(`SPA URL 变化: ${details.url}`);
});
11.6 Fetch API 使用
Service Worker 中使用 fetch API 发起网络请求:
// 基本请求
async function fetchJSON(url, options = {}) {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// 带重试的请求
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchJSON(url, options);
} catch (error) {
lastError = error;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
throw lastError;
}
// 并发请求
async function fetchMultiple(urls) {
const results = await Promise.allSettled(
urls.map(url => fetchJSON(url))
);
return results.map((result, i) => ({
url: urls[i],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason.message : null
}));
}
// 取消请求(AbortController)
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
11.7 业务场景
场景一:API 请求代理
// Service Worker 中代理 API 请求,自动添加认证头
async function authenticatedFetch(url, options = {}) {
const { apiToken } = await chrome.storage.local.get('apiToken');
if (!apiToken) {
throw new Error('未登录');
}
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${apiToken}`,
'X-Extension-Version': chrome.runtime.getManifest().version
}
});
}
// 监听 Content Script 的 API 请求
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'API_REQUEST') {
authenticatedFetch(message.url, message.options)
.then(res => res.json())
.then(data => sendResponse({ success: true, data }))
.catch(err => sendResponse({ success: false, error: err.message }));
return true;
}
});
场景二:自定义广告拦截
// 动态添加拦截规则
async function addCustomBlockRule(domain) {
const existingRules = await chrome.declarativeNetRequest.getDynamicRules();
const maxId = Math.max(0, ...existingRules.map(r => r.id));
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [{
id: maxId + 1,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: `||${domain}`,
resourceTypes: [
'script', 'image', 'sub_frame',
'xmlhttprequest', 'media'
]
}
}],
removeRuleIds: []
});
}
// 从规则文件批量导入
async function importRulesFromURL(url) {
const response = await fetch(url);
const rules = await response.json();
const maxId = Math.max(
0,
...rules.map(r => r.id)
);
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: rules.map((rule, i) => ({
...rule,
id: maxId + i + 1
})),
removeRuleIds: []
});
}
11.8 注意事项
| 问题 |
说明 |
解决方案 |
| 规则不生效 |
优先级冲突 |
检查 priority 值,高优先级规则先匹配 |
| 无法读取请求内容 |
MV3 限制 |
仅能阻断/重定向,不能读取响应体 |
| 动态规则上限 |
最多 30,000 条 |
合并规则、使用 urlFilter 通配 |
| 跨域请求失败 |
CORS 限制 |
需要 host_permissions 或服务端配置 |
webRequest 只读 |
MV3 中 webRequest 仅能观察 |
使用 declarativeNetRequest 进行修改 |
11.9 扩展阅读