首页
/ Electron WebRequestFilter Object:webRequest 事件的 URL 与资源类型双重过滤规则

Electron WebRequestFilter Object:webRequest 事件的 URL 与资源类型双重过滤规则

2026-09-06 17:32:32作者:凌朦慧Richard

WebRequestFilter 是 Electron session.webRequest 各事件监听器中用于圈定“哪些请求才需要被拦截/观察”的过滤对象。本文围绕 WebRequestFilter Object 的结构定义展开,结合 Electron 源码 shell/browser/api/electron_api_web_request.cc 的解析与匹配实现,以及 spec/api-web-request-spec.ts 中的真实测试用例,完整讲解 urlsexcludeUrlstypes 三个属性的语义、默认行为、取值范围与底层过滤机制,帮助你在实现请求拦截、头部改写、请求取消等场景时写出精确且可验证的过滤条件。

WebRequestFilter 对象结构总览

WebRequestFilter 不是通过 require('electron') 直接导出的类,而是作为 WebRequest 各监听方法(onBeforeRequestonBeforeSendHeadersonHeadersReceived 等)的可选参数出现,其类型定义见 docs/api/structures/web-request-filter.md

属性 类型 是否必填 说明
urls string[] 采用 URL patterns 语法的 URL 模式数组,只处理匹配这些模式的请求;使用 <all_urls> 匹配所有 URL
excludeUrls string[] 同样是 URL patterns 数组,匹配这些模式的请求会被排除,即使同时命中 urls
types string[] 资源类型数组,只处理匹配这些类型的请求;不指定时匹配所有类型。可取 mainFramesubFramestylesheetscriptimagefontobjectxhrpingcspReportmediawebSocket

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:决定监听器生效范围的白名单

urlsfilter 中唯一的必填属性。从源码的解析入口 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 中的任何一项。源码中 excludeUrlsurls 一并读入,并以 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 对空集合返回 falseelectron_api_web_request.cc),因此不写 excludeUrls(空排除集合)等价于不做排除。

spec/api-web-request-spec.ts 中有多个用例直接验证了 urlsexcludeUrls 的组合语义(见 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 种:mainFramesubFramestylesheetscriptimagefontobjectxhrpingcspReportmediawebSocket

字符串到内部枚举的映射

源码维护了一张字符串 → 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},
    });

解析时对每个字符串调用 ParseResourceTypeelectron_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::MatchesTypeelectron_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 个事件方法:onBeforeRequestonBeforeSendHeadersonSendHeadersonHeadersReceivedonResponseStartedonBeforeRedirectonCompletedonErrorOccurred。几个关键使用约定:

  • 同一事件只保留最后一个 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 的解析与报错

urlsexcludeUrls 中的每条字符串都通过 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.tsallowExtensions 相关用例)。

typeMask:过滤条件如何反馈到网络栈

除逐事件匹配外,filter 还会在注册/注销时汇总成“拦截状态”回写给浏览器上下文。RequestFilter::TypeMask 把类型集合压缩成按位掩码,缺省 types 返回全类型掩码 kAllResourceTypeselectron_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 的实现,可以提炼出以下要点:

  1. urls 必填且必须非空:filter 缺 urls 属性直接抛 TypeErrorurls: [] 会触发弃用警告并被改写为 <all_urls>,请显式写 urls: ['<all_urls>'](行为变更记录见 docs/breaking-changes.md)。
  2. 匹配语义是三层与条件urls 命中 ∧ excludeUrls 不命中 ∧ 类型匹配,逻辑见 RequestFilter::MatchesRequestexcludeUrls 为空或省略时不产生排除。
  3. types 缺省即全类型;11 个合法取值见 ResourceTypes 映射表,other 不是合法过滤值,写错会抛 Invalid type xxx
  4. 非法 URL 模式在注册时同步报错Invalid url pattern xxx: ...),错误信息来自 URLPattern::Parse 的解析结果。
  5. 每个事件只保留最后一个 listener,且 filter 与 listener 绑定;传 null 注销监听器会同时清掉对应的 filter,UpdateInterceptState 也会据此更新网络栈的拦截掩码。

上述行为均可以由 spec/api-web-request-spec.ts 中的 webRequest.onBeforeRequest 用例复现验证,覆盖了“无 filter 全匹配”“<all_urls>”“URL 过滤”“urls/excludeUrls 重叠排除”“多排除模式”“空 excludeUrls”“urls+types 组合”等场景,是验证自定义过滤条件是否符合预期的直接参照。

登录后查看全文
热门项目推荐
相关项目推荐