强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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_frameiframe
stylesheetCSS 文件
scriptJavaScript 文件
image图片
font字体
xmlhttprequestXMLHttpRequest / fetch
websocketWebSocket 连接
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,000Chrome 全局限制

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 扩展阅读