uBlock Origin过滤引擎模块化设计:解耦与扩展性实现
你是否曾好奇,为什么uBlock Origin(uBO)作为一款轻量级广告拦截工具,却能高效处理千万级过滤规则?秘密就藏在其精心设计的模块化架构中。本文将深入剖析uBO过滤引擎的模块化设计理念,展示其如何通过解耦实现高性能与灵活扩展,让普通用户也能理解复杂软件的架构精髓。
模块化设计的核心原则
uBO的过滤引擎采用关注点分离设计模式,将复杂的内容拦截逻辑拆解为相互独立的功能模块。这种架构带来三大优势:
- 低耦合:模块间通过明确定义的接口通信,修改一个模块不影响其他功能
- 高内聚:每个模块专注单一职责,如静态过滤、动态规则、视觉美化等
- 可扩展:新增过滤功能只需添加新模块,无需重构现有代码
核心设计体现在src/js/filtering-engines.js中,该文件集中管理各类过滤引擎实例:
export {
permanentFirewall, // 持久化主机规则过滤
sessionFirewall, // 会话级主机规则过滤
permanentURLFiltering, // 持久化URL过滤
sessionURLFiltering, // 会话级URL过滤
permanentSwitches, // 持久化开关规则
sessionSwitches // 会话级开关规则
};
过滤引擎的模块划分
uBO将过滤功能拆解为四大核心模块,每个模块对应独立的源码文件,便于维护和扩展:
1. 静态网络过滤模块
负责处理传统广告过滤规则(如||example.com^),通过高效的前缀树算法实现规则匹配。核心实现位于src/js/static-net-filtering.js,其内部使用双向字典树(BidiTrie)和哈希树(HNTrie)优化匹配性能:
import BidiTrieContainer from './biditrie.js';
import HNTrieContainer from './hntrie.js';
// 规则存储容器
const origHNTrieContainer = new HNTrieContainer();
const destHNTrieContainer = new HNTrieContainer();
const bidiTrie = new BidiTrieContainer(bidiTrieMatchExtra);
2. 动态规则过滤模块
处理用户临时添加的规则或会话级过滤设置,如"允许当前网站"功能。核心逻辑在src/js/dynamic-net-filtering.js中实现,采用位图(bitmap)存储状态以节省内存:
// 支持的动态规则类型
const supportedDynamicTypes = Object.create(null);
Object.assign(supportedDynamicTypes, {
'3p': true,
'image': true,
'inline-script': true,
'1p-script': true,
'3p-script': true,
'3p-frame': true
});
3. 视觉美化过滤模块
处理CSS隐藏规则(如example.com##.ad-banner),负责从页面中移除广告元素。实现位于src/js/cosmetic-filtering.js,使用高效的选择器缓存机制:
const SelectorCacheEntry = class {
constructor() {
this.reset();
}
reset() {
this.cosmetic = new Set(); // CSS选择器集合
this.cosmeticHashes = new Set(); // 选择器哈希值
this.disableSurveyor = false; // 禁用检测器标志
this.net = new Map(); // 网络请求选择器
this.entryId = SelectorCacheEntry.entryId++;
return this;
}
// ...
};
4. 脚本注入过滤模块
通过注入短小脚本阻断广告脚本执行(如example.com#%#//scriptlet('abort-on-property-read', 'adTracker'))。核心引擎在src/js/scriptlet-filtering.js中实现,支持多种脚本注入策略:
export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine {
constructor() {
super();
this.warOrigin = vAPI.getURL('/web_accessible_resources');
this.scriptletCache = new MRUCache(32); // 最近使用缓存
// ...
}
// ...
}
模块间通信与解耦机制
uBO通过事件总线和依赖注入实现模块解耦,主要通信方式有:
1. 基于事件的异步通信
模块间通过src/js/broadcast.js发送/接收事件,避免直接引用:
// 发送过滤行为变更事件
onBroadcast(msg => {
switch (msg.what) {
case 'filteringBehaviorChanged':
contentScriptRegisterer.flush(msg.hostname);
break;
// ...
}
});
2. 规则存储与共享
静态规则和动态规则通过src/js/storage.js统一管理,各模块通过接口获取规则而不直接访问存储:
// 规则存储接口
export const storage = {
get: async (keys) => { /* ... */ },
set: async (values) => { /* ... */ },
remove: async (keys) => { /* ... */ },
// ...
};
3. 过滤上下文传递
通过src/js/filtering-context.js封装请求上下文,模块间通过上下文对象共享必要信息:
export class FilteringContext {
constructor() {
this.tabId = -1;
this.frameId = 0;
this.url = '';
this.originUrl = '';
this.hostname = '';
// ...
}
// ...
}
扩展性实现:自定义过滤规则
uBO的模块化设计使添加新过滤功能变得简单。以自定义"移除URL参数"功能为例,只需:
- 添加新的规则解析器到src/js/static-filtering-parser.js
- 实现参数移除逻辑到src/js/redirect-engine.js
- 注册新规则类型到过滤引擎src/js/filtering-engines.js
无需修改现有过滤逻辑,体现了"开闭原则"——对扩展开放,对修改关闭。
性能优化的模块化实践
uBO通过分层缓存进一步提升性能,每个模块维护独立缓存:
- 选择器缓存:src/js/cosmetic-filtering.js中的
selectorCache存储CSS选择器结果 - 规则编译缓存:src/js/static-filtering-io.js缓存编译后的规则二进制表示
- URL匹配缓存:src/js/urlskip.js存储无需过滤的URL模式
// 选择器缓存实现
this.selectorCache = new Map();
this.selectorCachePruneDelay = 10; // 10分钟自动清理
this.selectorCacheCountMax = 50; // 最大缓存条目
模块协作流程图
以下是uBO处理网络请求的模块协作流程:
总结与启示
uBlock Origin的模块化设计为高性能浏览器扩展树立了典范:
- 单一职责:每个模块专注一类过滤功能
- 明确接口:模块间通过定义良好的API通信
- 可替换性:如静态过滤可替换为不同算法实现
- 可测试性:独立模块便于单元测试
这种架构不仅使uBO能高效处理数百万过滤规则,也使其保持代码库的可维护性。对于开发者而言,理解这种模块化思维,将大大提升复杂应用的设计能力。
深入学习资源
- 官方文档:docs/README.md
- 过滤规则语法:src/js/static-filtering-parser.js
- 性能基准测试:docs/tests/hnbigset-benchmark.html
- 贡献指南:CONTRIBUTING.md
通过这些资源,你可以进一步探索uBO的模块化设计奥秘,甚至参与到这个优秀开源项目的开发中。
提示:在浏览器中安装uBO后,通过"高级设置"→"开发者工具"可查看实时过滤日志,直观理解各模块工作流程。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



