Electron WebRequestFilter Object:webRequest 事件的 URL 与资源类型双重过滤规则
WebRequestFilter 是 Electron session.webRequest 各事件监听器中用于圈定“哪些请求才需要被拦截/观察”的过滤对象。本文围绕 WebRequestFilter Object 的结构定义展开,结合 Electron 源码 shell/browser/api/electron_api_web_request.cc 的解析与匹配实现,以及 spec/api-web-request-spec.ts 中的真实测试用例,完整讲解 urls、excludeUrls、types 三个属性的语义、默认行为、取值范围与底层过滤机制,帮助你在实现请求拦截、头部改写、请求取消等场景时写出精确且可验证的过滤条件。
WebRequestFilter 对象结构总览
WebRequestFilter 不是通过 require('electron') 直接导出的类,而是作为 WebRequest 各监听方法(onBeforeRequest、onBeforeSendHeaders、onHeadersReceived 等)的可选参数出现,其类型定义见 docs/api/structures/web-request-filter.md:
| 属性 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
urls |
string[] |
是 | 采用 URL patterns 语法的 URL 模式数组,只处理匹配这些模式的请求;使用 <all_urls> 匹配所有 URL |
excludeUrls |
string[] |
否 | 同样是 URL patterns 数组,匹配这些模式的请求会被排除,即使同时命中 urls |
types |
string[] |
否 | 资源类型数组,只处理匹配这些类型的请求;不指定时匹配所有类型。可取 mainFrame、subFrame、stylesheet、script、image、font、object、xhr、ping、cspReport、media 或 webSocket |
urls 中可以使用的一些合法模式示例(源自 docs/api/web-request.md 的官方示例):
'<all_urls>'
'http://foo:1234/'
'http://foo.com/'
'http://foo:1234/bar'
'*://*/*'
'*://example.com/*'
'*://example.com/foo/*'
'http://*.foo:1234/'
'file://foo:1234/bar'
'http://foo:*/'
'*://www.foo.com/'
模式遵循 WebExtensions Match Patterns 语法,支持 scheme://host/path 三段通配(* 可通配 host 或 path 前缀)。
urls:决定监听器生效范围的白名单
urls 是 filter 中唯一的必填属性。从源码的解析入口 WebRequest::SetListener(见 electron_api_web_request.cc)可以看到,如果传入了 filter 字典但读不到 urls 属性,会直接抛出类型错误:
if (gin::ConvertFromV8(args->isolate(), arg, &dict)) {
if (!dict.Get("urls", &filter_include_patterns)) {
args->ThrowTypeError("Parameter 'filter' must have property 'urls'.");
return;
}
...
}
因此调用形如 ses.webRequest.onBeforeRequest({}, listener) 会直接报 Parameter 'filter' must have property 'urls'.。
不传 filter 时的行为
如果调用时只传 listener、不传 filter(例如 ses.webRequest.onBeforeRequest(cancel)),源码会构造一个隐式 filter,并把 <all_urls> 作为 urls 填入(electron_api_web_request.cc):
} else {
// If no filter is defined, create one with <all_urls> so it matches all
// requests
dict = gin::Dictionary::CreateEmpty(args->isolate());
filter_include_patterns.insert("<all_urls>");
dict.Set("urls", filter_include_patterns);
}
这意味着“不传 filter”与“显式传 { urls: ['<all_urls>'] }”语义完全等价,监听器会对该 Session 内的所有请求生效。
空数组 urls 的弃用行为
早期版本中 urls: [] 被解释为“包含所有 URL”,这一隐含语义已被废弃。源码中现在的处理是:发出弃用警告并把该数组替换为 <all_urls>(electron_api_web_request.cc):
if (filter_include_patterns.empty()) {
util::EmitDeprecationWarning(
"The urls array in WebRequestFilter is empty, which is deprecated. "
"Please use '<all_urls>' to match all URLs.");
filter_include_patterns.insert("<all_urls>");
}
这一行为变更在 docs/breaking-changes.md 中有专门记录:以前空 urls 数组等价于匹配全部 URL,现在要明确表达“匹配所有 URL”的意图,应写成 { urls: ['<all_urls>'] }。
// 旧写法(已弃用,会触发弃用警告)
const deprecatedFilter = { urls: [] };
// 推荐写法
const newFilter = { urls: ['<all_urls>'] };
excludeUrls:命中 urls 后的二次排除
excludeUrls 是可选属性,同样采用 URL patterns 语法。它的语义是“反向白名单”:一个请求要真正命中 filter,必须先匹配 urls,再不匹配 excludeUrls 中的任何一项。源码中 excludeUrls 与 urls 一并读入,并以 is_match_pattern = false 加入过滤器的排除集合(electron_api_web_request.cc):
dict.Get("excludeUrls", &filter_exclude_patterns);
dict.Get("types", &filter_types);
最终匹配逻辑在 RequestFilter::MatchesRequest 中实现(electron_api_web_request.cc):
bool WebRequest::RequestFilter::MatchesRequest(
const extensions::WebRequestInfo* info) const {
// Matches URL and type, and does not match exclude URL.
return MatchesURL(info->url, include_url_patterns_) &&
!MatchesURL(info->url, exclude_url_patterns_) &&
MatchesType(info->web_request_type);
}
可以看到三者是与关系:urls 命中 ∧ excludeUrls 不命中 ∧ 类型匹配,缺一不可。MatchesURL 对空集合返回 false(electron_api_web_request.cc),因此不写 excludeUrls(空排除集合)等价于不做排除。
spec/api-web-request-spec.ts 中有多个用例直接验证了 urls 与 excludeUrls 的组合语义(见 api-web-request-spec.ts):
it('can filter URLs with overlapping patterns of urls and excludeUrls', async () => {
// If filter matches both urls and excludeUrls, it should be excluded.
const filter = { urls: [defaultURL + 'filter/*'], excludeUrls: [defaultURL + 'filter/test'] };
ses.webRequest.onBeforeRequest(filter, cancel);
const { data } = await ajax(`${defaultURL}filter/test`);
expect(data).to.equal('/filter/test'); // 命中 excludeUrls,请求正常放行
});
测试还覆盖了两条边界:excludeUrls 传空数组 [] 时不产生任何排除效果(excludeUrls: [],api-web-request-spec.ts);以及多个排除模式并存时每个模式独立生效(api-web-request-spec.ts)。
典型实战场景是“拦截整个域名,但放行静态资源或某个子路径”:
const filter = {
urls: ['https://*.example.com/*'],
excludeUrls: ['https://cdn.example.com/assets/*']
}
session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
callback({ cancel: true }) // 只拦截未被排除的请求
})
types:按资源类型收窄监听范围
types 是第三个可选属性,用于只关心特定资源类型的请求。官方文档列出的合法取值为 11 种:mainFrame、subFrame、stylesheet、script、image、font、object、xhr、ping、cspReport、media、webSocket。
字符串到内部枚举的映射
源码维护了一张字符串 → extensions::WebRequestResourceType 的固定映射表(electron_api_web_request.cc),11 个合法名称与 Chromium 扩展系统内部的资源类型枚举一一对应:
static constexpr auto ResourceTypes =
base::MakeFixedFlatMap<std::string_view, extensions::WebRequestResourceType>({
{"cspReport", extensions::WebRequestResourceType::CSP_REPORT},
{"font", extensions::WebRequestResourceType::FONT},
{"image", extensions::WebRequestResourceType::IMAGE},
{"mainFrame", extensions::WebRequestResourceType::MAIN_FRAME},
{"media", extensions::WebRequestResourceType::MEDIA},
{"object", extensions::WebRequestResourceType::OBJECT},
{"ping", extensions::WebRequestResourceType::PING},
{"script", extensions::WebRequestResourceType::SCRIPT},
{"stylesheet", extensions::WebRequestResourceType::STYLESHEET},
{"subFrame", extensions::WebRequestResourceType::SUB_FRAME},
{"webSocket", extensions::WebRequestResourceType::WEB_SOCKET},
{"xhr", extensions::WebRequestResourceType::XHR},
});
解析时对每个字符串调用 ParseResourceType(electron_api_web_request.cc);该函数对未知名返回 OTHER,而 SetListener 中遇到 OTHER 会抛出类型错误(electron_api_web_request.cc):
for (const std::string& filter_type : filter_types) {
auto type = ParseResourceType(filter_type);
if (type != extensions::WebRequestResourceType::OTHER) {
filter.AddType(type);
} else {
args->ThrowTypeError("Invalid type " + filter_type);
return;
}
}
注意一个容易踩坑的点:details.resourceType 在事件回调中可以取到 other(未分类资源),但 other 不是 types 过滤器里的合法值——写 types: ['other'] 会报 Invalid type other。
不指定 types = 匹配所有类型
类型匹配的实现在 RequestFilter::MatchesType(electron_api_web_request.cc):
bool WebRequest::RequestFilter::MatchesType(
extensions::WebRequestResourceType type) const {
return types_.empty() || types_.contains(type);
}
即 types 缺省(空集合)时对所有类型放行,显式给出 types 后则只放行集合内的类型。测试用例 api-web-request-spec.ts 验证了该行为:types: ['xhr'] 时 ajax(XHR)请求被取消、页面导航不受影响;换成 types: ['stylesheet'] 后同一 XHR 请求又能正常通过。
it('can filter URLs and types', async () => {
const filter1: Electron.WebRequestFilter = { urls: [defaultURL + 'filter/*'], types: ['xhr'] };
ses.webRequest.onBeforeRequest(filter1, cancel);
const { data } = await ajax(`${defaultURL}nofilter/test`); // 导航不受影响
expect(data).to.equal('/nofilter/test');
await expect(ajax(`${defaultURL}filter/test`)).to.eventually.be.rejected(); // XHR 被取消
const filter2: Electron.WebRequestFilter = { urls: [defaultURL + 'filter/*'], types: ['stylesheet'] };
ses.webRequest.onBeforeRequest(filter2, cancel);
// 换成 stylesheet 类型后,同一个 XHR 请求恢复放行
expect((await ajax(`${defaultURL}filter/test`)).data).to.equal('/filter/test');
});
filter 在各 webRequest 事件中的用法
WebRequestFilter 作为可选首参贯穿 WebRequest 类 的全部 8 个事件方法:onBeforeRequest、onBeforeSendHeaders、onSendHeaders、onHeadersReceived、onResponseStarted、onBeforeRedirect、onCompleted、onErrorOccurred。几个关键使用约定:
- 同一事件只保留最后一个 listener:源码中监听器以事件为 key 存储在
simple_listeners_/response_listeners_映射里,重复注册直接覆盖(electron_api_web_request.cc); - 传
null作为 listener 即取消订阅(if (listener.is_null()) listeners->erase(event);); - filter 随 listener 一起存储:
(*listeners)[event] = {std::move(filter), std::move(listener)};,每次注册 listener 时可以换一套 filter,不需要拆成多次调用。
完整示例——只为指定 URL 范围内的请求改写 User-Agent:
const { session } = require('electron')
const filter = {
urls: ['https://*.github.com/*', '*://electron.github.io/*']
}
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['User-Agent'] = 'MyAgent'
callback({ requestHeaders: details.requestHeaders })
})
带 types 收窄的示例——只在 XHR 请求上打日志,避免刷屏:
session.defaultSession.webRequest.onCompleted({
urls: ['*://api.example.com/*'],
types: ['xhr']
}, (details) => {
console.log(details.method, details.url, details.statusCode)
})
实现细节:模式解析、匹配与拦截状态
URLPattern 的解析与报错
urls 与 excludeUrls 中的每条字符串都通过 Chromium 的 extensions::common::URLPattern 解析(electron_api_web_request.cc),并显式使用 SCHEME_ALL 标志:
for (const std::string& filter_pattern : filter_patterns) {
URLPattern pattern(URLPattern::SCHEME_ALL);
const URLPattern::ParseResult result = pattern.Parse(filter_pattern);
if (result == URLPattern::ParseResult::kSuccess) {
filter->AddUrlPattern(std::move(pattern), is_match_pattern);
} else {
const char* error_type = URLPattern::GetParseResultString(result);
args->ThrowTypeError("Invalid url pattern " + filter_pattern + ": " + error_type);
return;
}
}
有两点值得注意:其一,非法模式会在注册监听器时同步抛出 TypeError(携带具体解析错误原因),而不是在请求到来时才静默失效,这便于尽早发现配置笔误;其二,SCHEME_ALL 标志意味着 *://... 这类模式可以匹配任意 scheme,从源码结构看,这也是测试中 protocol.registerSchemesAsPrivileged 注册的自定义协议请求同样能被 http://*/* 这类过滤器正确处理的底层原因(见 api-web-request-spec.ts 中 allowExtensions 相关用例)。
typeMask:过滤条件如何反馈到网络栈
除逐事件匹配外,filter 还会在注册/注销时汇总成“拦截状态”回写给浏览器上下文。RequestFilter::TypeMask 把类型集合压缩成按位掩码,缺省 types 返回全类型掩码 kAllResourceTypes(electron_api_web_request.cc 对应 shell/browser/api/electron_api_web_request.cc):
uint32_t WebRequest::RequestFilter::TypeMask() const {
if (types_.empty())
return kAllResourceTypes;
uint32_t mask = 0;
for (auto type : types_)
mask |= 1u << static_cast<int>(type);
return mask;
}
UpdateInterceptState 把带回调的 response 类监听(如 onBeforeRequest)汇总为 blocking 掩码、无回调的 simple 监听(如 onCompleted)汇总为 observers 掩码,再调用 browser_context_->intercept_state()->SetListenerTypes(blocking, observers)(electron_api_web_request.cc)。从源码结构看,这意味着 Chromium 网络栈只在确有监听器覆盖的资源类型上启用扩展拦截路径——types 不仅省去了逐请求匹配的开销,也让与监听器无关类型的高频请求(图片、字体等)尽量绕过 JS 回调链路。
每个事件的匹配入口
无论哪个事件,进入 JS 回调前都会先做一次 filter 判定,未命中直接短路返回。以 onBeforeRequest 为例(electron_api_web_request.cc):
const auto iter = response_listeners_.find(ResponseEvent::kOnBeforeRequest);
if (iter == std::end(response_listeners_))
return net::OK;
const auto& info = iter->second;
if (!info.filter.MatchesRequest(request_info))
return net::OK;
simple 类事件走同样的 HandleSimpleEvent 模板函数,匹配失败同样直接返回(electron_api_web_request.cc)。这正是 excludeUrls / types 能显著减少无谓 JS 调用的实现保证。
小结与易错点清单
结合 WebRequestFilter Object 的定义与 electron_api_web_request.cc 的实现,可以提炼出以下要点:
urls必填且必须非空:filter 缺urls属性直接抛TypeError;urls: []会触发弃用警告并被改写为<all_urls>,请显式写urls: ['<all_urls>'](行为变更记录见 docs/breaking-changes.md)。- 匹配语义是三层与条件:
urls命中 ∧excludeUrls不命中 ∧ 类型匹配,逻辑见RequestFilter::MatchesRequest;excludeUrls为空或省略时不产生排除。 types缺省即全类型;11 个合法取值见ResourceTypes映射表,other不是合法过滤值,写错会抛Invalid type xxx。- 非法 URL 模式在注册时同步报错(
Invalid url pattern xxx: ...),错误信息来自URLPattern::Parse的解析结果。 - 每个事件只保留最后一个 listener,且 filter 与 listener 绑定;传
null注销监听器会同时清掉对应的 filter,UpdateInterceptState也会据此更新网络栈的拦截掩码。
上述行为均可以由 spec/api-web-request-spec.ts 中的 webRequest.onBeforeRequest 用例复现验证,覆盖了“无 filter 全匹配”“<all_urls>”“URL 过滤”“urls/excludeUrls 重叠排除”“多排除模式”“空 excludeUrls”“urls+types 组合”等场景,是验证自定义过滤条件是否符合预期的直接参照。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00