迁移之前我们先来看看在manifest v3中,如何处置webRequestBlocking权限的吧。
理清v3 webRequestBlocking
我们知道在manifest v2中拦截,修改,取消请求需要使用 asyncBlocking,blocking 选项的,使用这些选项的前提又必须设置webRequestBlocking权限。其他选项(requestHeaders、responseHeaders或extraHeaders)都是不需要的,所以在manifest v3中依旧可以直接使用来获取请求的相关内容。
至此,我们可以知道manifest v3并没有对webRequest做限制,只是限制了webRequestBlocking权限,所以webRequest权限相关api可以正常使用。
配置webRequestBlocking权限相关选项(asyncBlocking,blocking)回调是不会执行的,并且会抛出错误。如果未配置asyncBlocking,blocking选项,即使webRequest相关API回调设置了返回值也会被忽略。
js
console.log("manifest v3");
chrome.webRequest.onBeforeRequest.addListener(
(details) => {
console.log("这里不会回调", details)
return { cancel: true }; // 未配置`asyncBlocking`,`blocking`选项,即使`webRequest`相关API回调设置了返回值也会被忽略
},
{ urls: ["<all_urls>"] },
["blocking"] // 配置webRequestBlocking权限相关选项回调是不会执行的,并且会抛出错误。
);
并且加载到扩展管理中可以直接查看错误来源 
插件迁移
manifest.json
- manifest_version 更新版本
js
"manifest_version": 2,
=>
"manifest_version": 3,
- background
js
"background": {
"scripts": ["background.js"],
"persistent": true
},
=>
"background": {
"service_worker": "background.js"
}
- permissions, v2中主机权限和api权限都在一起,v3进行的分离
js
"permissions": [
"tabs",
"storage",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*"
],
=>
"permissions": [
"tabs",
"storage",
"declarativeNetRequest"
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
- 统一action,browser_action 和 page_action 在 MV3 中被统一为 action API
js
"browser_action": {
"default_title": "添加/修改HTTP请求头",
"default_icon": {
"16": "./icons/cloud16_off.png",
"32": "./icons/cloud32_off.png",
"48": "./icons/cloud48_off.png",
"128": "./icons/cloud128_off.png"
},
"default_popup": "popup.html"
},
=>
"action": {
"default_title": "添加/修改HTTP请求头",
"default_icon": {
"16": "./icons/cloud16_off.png",
"32": "./icons/cloud32_off.png",
"48": "./icons/cloud48_off.png",
"128": "./icons/cloud128_off.png"
},
"default_popup": "popup.html"
},
api迁移
整体介绍
由于在manifest v3中废弃了webRequestBlocking权限,所以做请求修改就必须使用新增的declarativeNetRequest权限。
chrome.declarativeNetRequest API 用于通过指定声明性规则来屏蔽或修改网络请求。但是又提供了相关getDynamicRules, updateDynamicRules等API让其可以编程式的修改请求,而不仅仅通过静态配置表。
不管是静态规则表,还是动态规则都是通过设置规则集来进行修改网络请求的,所以我们先来看看规则集如何编写。
规则由四部分组成
js
interface Rule {
id: number; // 唯一ID (1-999999)
priority?: number; // 优先级 (1-999999,默认1)
condition: RuleCondition; // 匹配条件
action: RuleAction; // 执行动作
}
- priority 如果同时匹配到多个规则,那么将会匹配到
priority最大的。如果priority相同,按操作排序(allow或allowAllRequests>block>upgradeScheme>redirect)。如果有多个扩展程序规则匹配同一网址,并且规则属于同一类型,Chrome 会选择来自最近安装的扩展程序的规则。 - condition
js
interface RuleCondition {
// ========== URL 过滤(二选一)相关 ==========
urlFilter?: string; // 简单URL过滤 可通过isUrlFilterCaseSensitive设置是否区分大小写,默认不区分,为false
模式匹配令牌介绍
- * :通配符:匹配任意数量的字符。
- | :左/右锚点:如果用于模式的任一端,则分别指定网址的开头/结尾。
- || :域名锚点:如果用于模式的开头,则指定网址的(子)网域的开头。
- ^ :分隔符字符:匹配除字母、数字或以下字符以外的任何内容:`_`、`-`、`.` 或 `%`。这也匹配网址的末尾。
// 每条规则在编译后的大小必须小于 2KB
regexFilter?: string; // 正则表达式过滤 可通过isUrlFilterCaseSensitive设置是否区分大小写,默认不区分,为false
isUrlFilterCaseSensitive?: boolean; // URL过滤器是否区分大小写
// ========== 域名匹配相关 ==========
domains?: string[]; // 匹配的域名 Chrome 101弃用
excludedDomains?: string[]; // 排除的域名 Chrome 101弃用
initiatorDomains?: string[]; // 匹配发起请求的页面域名
excludedInitiatorDomains?: string[]; // 排除发起请求的页面域名
// ========== 资源类型相关 ==========
resourceTypes?: ResourceType[]; // 匹配资源类型
excludedResourceTypes?: ResourceType[]; // 排除资源类型
// ========== 域名类型相关 ==========
// 发起方和接收方属于同域名
domainType?: "firstParty" | "thirdParty"; // 第一方或第三方
// ========== 请求方法相关 ==========
requestMethods?: RequestMethod[]; // 匹配请求方法
excludedRequestMethods?: RequestMethod[]; // 排除请求方法
// ========== 标签页相关 ==========
tabIds ?: number[]; // 匹配标签页ID
excludedTabIds ?: number[]; // 排除标签页ID
// ========== 响应头相关 ==========
responseHeaders ?: HeaderInfo[]; // 匹配响应头
excludedResponseHeaders ?: HeaderInfo[]; // 排除响应头
// ========== 请求头相关 ==========
requestHeaders ?: HeaderInfo[]; // 匹配请求头
excludedRequestHeaders ?: HeaderInfo[]; // 排除请求头
}
// 资源类型枚举
type ResourceType =
| "main_frame" // 主框架
| "sub_frame" // 子框架(iframe)
| "stylesheet" // CSS样式表
| "script" // JavaScript脚本
| "image" // 图片
| "font" // 字体
| "object" // 对象(插件)
| "xmlhttprequest" // XMLHttpRequest
| "ping" // Beacon/ping请求
| "csp_report" // CSP报告
| "media" // 媒体资源
| "websocket" // WebSocket连接
| "other"; // 其他类型
// 请求方法枚举
RequestMethod {
CONNECT = "connect",
DELETE = "delete",
GET = "get",
HEAD = "head",
OPTIONS = "options",
PATCH = "patch",
POST = "post",
PUT = "put",
OTHER = "other",
}
请求头
interface HeaderInfo {
excludedValues?: string[];
// 头部的名称。只有在未指定 `values` 和 `excludedValues` 时,此条件才会仅根据名称进行匹配。
header: string;
// 如果指定了此条件,则当标头的值与此列表中的至少一个模式匹配时,此条件即为匹配。此功能支持不区分大小写的标头值匹配以及以下构造:
*:匹配任意数量的字符。
?:匹配零个或一个字符。
values?: string[];
}
js
// 安全规则是指操作为 `block`、`allow`、`allowAllRequests` 或 `upgradeScheme` 的规则
type RuleAction =
| BlockAction
| RedirectAction
| AllowAction
| UpgradeSchemeAction
| ModifyHeadersAction
| AllowAllRequestsAction;
interface BlockAction {
type: "block";
}
interface ModifyHeadersAction {
type: "modifyHeaders";
requestHeaders?: HeaderOperation[];
responseHeaders?: HeaderOperation[];
}
interface HeaderOperation {
header: string;
// append子对特定头字段修改 https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest?hl=zh-cn#header_modification
operation: "set" | "remove" | "append";
value?: string;
}
interface RedirectAction {
type: "redirect";
redirect: {
// 重定向到扩展资源
extensionPath?: string;
// 重定向到URL
url?: string;
// 正则替换重定向
regexSubstitution?: string;
// 转换重定向
transform?: {
scheme?: "http" | "https";
host?: string;
port?: string;
path?: string;
query?: string;
fragment?: string;
username?: string;
password?: string;
};
};
}
interface AllowAction {
type: "allow";
}
interface UpgradeSchemeAction {
type: "upgradeScheme";
}
interface AllowAllRequestsAction {
type: "allowAllRequests";
}
规则集类型
注意:不同类型的规则集的规则ID是可以重复的,因为有规则集ID作为区分,静态自己设置,动态规则集ID为_dynamic,会话规则集ID为_session
动态
(version >= 121 安全规则MAX_NUMBER_OF_DYNAMIC_RULES最多30000条。MAX_NUMBER_OF_UNSAFE_DYNAMIC_RULES非安全和安全最多5000条(即非安全规则最多5000条) version <= 120 动态,会话加起来最多5000)
可在浏览器会话和扩展程序升级期间保持不变,并且在扩展程序使用期间通过 JavaScript 进行管理。
getDynamicRules
获取动态规则。
js
chrome.declarativeNetRequest.getDynamicRules(
{
ruleIds?: [规则ID],
},
(res) => {
console.log("获取动态规则集", res);
}
);

updateDynamicRules
js
// 更新动态规则
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [ /* 规则数组 */ ],
removeRuleIds: [ /* 要移除的规则ID数组 */ ]
}, callback);
会话
(version >= 121 MAX_NUMBER_OF_SESSION_RULES最多5000条。 version <= 120 动态,会话加起来最多5000)
在浏览器关闭时以及安装新版本的扩展程序时清除。在使用扩展程序时,会话规则通过 JavaScript 进行管理。
getSessionRules
js
// 获取会话规则
chrome.declarativeNetRequest.getSessionRules(
{
ruleIds?: [ /* 规则ID数组 */]
},
callback
);
updateSessionRules
js
// 更新会话规则
chrome.declarativeNetRequest.updateSessionRules({
addRules: [ /* 规则数组 */ ],
removeRuleIds: [ /* 要移除的规则ID数组 */ ]
});

静态
(MAX_NUMBER_OF_ENABLED_STATIC_RULESETS 一次最多启用50个规则集(最多规则集为MAX_NUMBER_OF_STATIC_RULESETS100个),GUARANTEED_MINIMUM_STATIC_RULES最多30000条规则。在 Chrome 120 之前,扩展程序最多只能有 50 个静态规则集,并且一次只能启用其中的 10 个)
在安装或升级扩展程序时进行打包、安装和更新。静态规则存储在 JSON 格式的规则文件中,并在清单文件中列出。
js
{
...
"declarative_net_request" : {
"rule_resources" : [{
"id": "ruleset_1",
"enabled": true,
"path": "rules_1.json" // 无需配置可访问文件
},
]
}
}
getAvailableStaticRuleCount
开发文档中说上限为30000,但是测试发现可以是330000个。
获取还可以添加多少静态规则个数。

getDisabledRuleIds
获取指定规则集已停用的规则
js
chrome.declarativeNetRequest.getDisabledRuleIds(
{ rulesetId: "静态规则集ID" },
(res) => {
console.log("获取停用的静态规则", res);
}
);
// 停用1,2
chrome.declarativeNetRequest.updateStaticRules({
disableRuleIds: [1, 2],
// enableRuleIds: [需要启用的规则ID],
rulesetId: "ruleset_1",
});
chrome.declarativeNetRequest.getDisabledRuleIds(
{ rulesetId: "ruleset_1" },
(res) => {
console.log("获取指定静态规则集已停用的规则", res);
}
);

getEnabledRulesets
获取当前开启的静态规则集
js
chrome.declarativeNetRequest.getEnabledRulesets((res) => {
console.log("当前开启的静态规则集id", res);
});
updateEnabledRulesets
更新静态规则集
js
chrome.declarativeNetRequest.updateEnabledRulesets(
{
disableRulesetIds: [需要禁用的规则集ID],
enableRulesetIds: [需要启用的规则集ID],
}
);
updateStaticRules
更新指定静态规则集中的规则
js
chrome.declarativeNetRequest.updateStaticRules(
{
disableRuleIds: [需要禁用的规则ID],
enableRuleIds: [需要启用的规则ID],
rulesetId: [静态规则集ID], // 必须
}
);
setExtensionActionOptions
在扩展图标上显示匹配的规则数
js
// 在扩展图标上显示匹配的规则数
chrome.declarativeNetRequest.setExtensionActionOptions({
displayActionCountAsBadgeText: true
});

测试API
isRegexSupported
检查给定的正则表达式是否会作为 regexFilter 规则条件受到支持
js
const regexOptions = {
regex: "^https?://([^/]+\\.)?example\\.com/.*",
isCaseSensitive: false, // 指定的 `regex` 是否区分大小写。默认值为 true
requireCapturing: true // 指定的 `regex` 是否需要捕获。只有指定了 `regexSubstition` 操作的重定向规则才需要捕获。默认值为 false
};
chrome.declarativeNetRequest.isRegexSupported(regexOptions, (result) => {
if (result.isSupported) {
console.log("正则表达式受支持");
} else {
console.log("正则表达式可能过于复杂或不受支持");
}
});
testMatchOutcome
检查扩展程序的任何 declarativeNetRequest 规则是否会与假设的请求匹配。注意:此方法仅适用于未打包的扩展程序,因为此方法仅用于扩展程序开发期间。
js
interface TestMatchRequestDetails {
/** 请求发起的url. */
initiator?: string;
/** http请求方法,默认为get */
method?: `${RequestMethod}`;
/**
* 响应头对象
* @since Chrome 129
*/
responseHeaders?: { [name: string]: unknown };
/** 请求发生的标签页的 ID。无需与实际标签页 ID 对应。默认值为 -1,表示请求与任何标签页无关。*/
tabId?: number;
/** 请求资源类型 */
type: `${ResourceType}`;
/** 请求的url. */
url: string;
}
js
chrome.declarativeNetRequest.testMatchOutcome(
{
url: "https://www.baidu.com",
type: "main_frame",
},
(res) => {
console.log("测试匹配结果", res);
}
);

getMatchedRules
返回与扩展程序匹配的所有规则。
js
interface MatchedRulesFilter {
tabId?: number; // 可选:限制特定标签页
timestamp?: number; // 可选:限制时间戳之后
}
interface RulesMatchedInfo {
rule: {
ruleId: number; // 规则ID
rulesetId?: string; // 规则集ID(仅静态规则)
};
request: {
url: string; // 请求URL
originUrl?: string; // 原始URL(重定向前)
method: string; // HTTP方法
tabId: number; // 标签页ID
type: ResourceType; // 资源类型
frameId: number; // 框架ID
documentId?: string; // 文档ID
documentLifecycle?: string; // 文档生命周期
frameType?: string; // 框架类型
parentDocumentId?: string; // 父文档ID
parentFrameId: number; // 父框架ID
requestId: string; // 请求ID
};
timeStamp: number; // 匹配时间戳
}
chrome.declarativeNetRequest.getMatchedRules(
filter?: MatchedRulesFilter,
callback?: (details: { rulesMatchedInfo: RulesMatchedInfo[] }) => void
): Promise<{ rulesMatchedInfo: RulesMatchedInfo[] }>;
onRuleMatchedDebug
匹配到具体规则时触发。
js
chrome.declarativeNetRequest.onRuleMatchedDebug.addListener((matchItem) => {
console.log("匹配到", matchItem);
});

