uBlock Origin 的 Public Suffix List 模块:从 WAT 源码到 WASM 加速的编译与加载全解
2026/9/5 19:22:13 网站建设 项目流程

uBlock Origin 的 Public Suffix List 模块:从 WAT 源码到 WASM 加速的编译与加载全解

【免费下载链接】uBlockuBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean.项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock

本文以 wasm 目录的 README 为核心,完整讲解 uBlock Origin 中 Public Suffix List(PSL,公共后缀列表)查询模块的 WASM 加速组件:如何用wat2wasmpublicsuffixlist.wat编译为publicsuffixlist.wasm、WAT 源码的模块结构与内存布局约定,以及该 WASM 模块在扩展运行时中的可选加载链路与降级策略。读完之后,你能够独立复现该.wasm文件的构建过程,并理解它在 uBlock Origin 域名解析体系中扮演的角色。

这个 wasm 目录解决什么问题

目录 src/lib/publicsuffixlist/wasm/ 下只有三类文件:一份说明文档README.md、WebAssembly 文本格式源码 publicsuffixlist.wat 与它的二进制编译产物 publicsuffixlist.wasm。

README 开头就明确了它的定位:

For code reviewers

Allwasmfiles in that directory were created by compiling the correspondingwatfile using the command ...

也就是说,该文档主要面向代码审查者,回答一个具体问题:目录里的.wasm二进制文件是怎么来的、用什么工具链构建、如何复现。其对应的宿主实现是 publicsuffixlist.js,文件头注明这是 Raymond Hill 的 publicsuffixlist.js 库的移植,用于高效处理 Mozilla 基金会维护的 Public Suffix List(用于按getDomaingetPublicSuffixsuffixInPSL等 API 解析可注册域名)。

复现编译:wat2wasm工具链

README 给出的完整编译步骤如下(以publicsuffixlist.wat/publicsuffixlist.wasm为例):

wat2wasm publicsuffixlist.wat -o publicsuffixlist.wasm

配套的前提条件与替代方案,原文档逐条列出了:

  1. 命令必须在当前目录内执行,即src/lib/publicsuffixlist/wasm/目录下运行,输入输出文件均为相对该目录的路径;
  2. wat2wasm工具的获取:从 WebAssembly 官方项目 wabt 的发行版(releases)下载;publicsuffixlist.wat 文件头部的注释块(How to compile部分,见 第 15-21 行)同样内嵌了这条编译命令,保证了源码与文档的一致;
  3. 在线编译替代方案:WebAssembly 官方提供 wat2wasm 在线 demo,操作方式是把整个wat文件的内容粘贴进 WAT 编辑区,点击 "Download" 按钮即可下载编译出的.wasm文件——适合不想本地安装 wabt 的场景;
  4. 延伸阅读:README 在 "See also" 一节还提到,对感兴趣的人可以用 WasmExplorer 这类在线工具查看 WASM 编译出的机器码,便于反汇编级别地审查这份小模块。

值得注意的细节:publicsuffixlist.wat全文约 320 行,是一份完全手写、带中文式逐行注释(每条指令旁标注对应 JS 伪代码)的 WebAssembly Text 格式源码,并非由 C/Rust 等语言转译而来。这正契合 uBlock Origin "小而精" 的实现风格——审查者可以直接阅读 WAT 源码本身,而不必理解任何上游语言。

WAT 源码结构:一个只导出单一函数的模块

publicsuffixlist.wat 的模块结构极其精简,可以拆解为三个部分。

1. 导入宿主内存

(module (memory (import "imports" "memory") 1)

模块在 第 29 行 声明:内存不是模块自建的,而是从imports对象导入,初始 1 页(64 KiB)。这与 JS 侧的实例化逻辑严格对应——publicsuffixlist.js 第 566-569 行 中,WebAssembly.instantiate(module, { imports: { memory } })传入的正是 JS 侧预先创建、并按需memory.growWebAssembly.Memory,即 WASM 与 JS 共享同一块线性内存,wasm代码直接读写 JS 数组视图中的树形数据结构。

2. 唯一导出函数getPublicSuffixPos

(func (export "getPublicSuffixPos") (result i32) ;; result = match index, -1 = miss ...

整个模块只导出 第 61-62 行 的getPublicSuffixPos(),返回i32:命中的后缀在主机名缓冲区中的位置偏移,未命中返回-1。JS 侧在启用 WASM 时正是取instance.exports.getPublicSuffixPos替换纯 JS 版本(见 publicsuffixlist.js 第 588-589 行)。

函数声明了十余个局部变量($iNode$iLabel$cursorPos$l/$r等,见 第 63-81 行),其算法骨架与 publicsuffixlist.js 中的getPublicSuffixPosJS逐行对应:

  • 标签遍历循环(WAT 第 101 行起的block $labelLookupDone loop $labelLookup):从LABEL_INDICES_SLOT(偏移 256) 处读取当前标签的[长度, 起始位置]字节对,逐个标签自右向左匹配;
  • 二进制搜索内层循环(WAT 第 132 行起的block $binarySearchDone loop $binarySearch):对当前节点的子节点数组做二分查找,按"长度差 → 逐字节比较"的字典序收敛;注释中保留了const iCandidateNode = iCandidates + iCandidate + (iCandidate << 1)这样的 JS 原始表达式,说明每个子节点在数组中占 3 个 i32 槽位(12 字节);
  • PSL 算法规则映射:WAT 第 248-267 行实现"规则 2——若无匹配规则,生效规则为*"(检查首个候选是否为0x2A并写入SUFFIX_NOT_FOUND_SLOT);第 279-294 行实现"规则 5——例外规则去掉最左标签";第 295-304 行用flags & 0x01记录is_publicsuffix命中位置,作为最终返回的cursorPos

3. 与 JS 共享的缓冲区布局约定

WAT 文件头注释(第 32-49 行)与 JS 源码(publicsuffixlist.js 第 47-71 行)给出了完全一致的内存契约:

Node: + u8: length of char data + u8: flags => bit 0: is_publicsuffix, bit 1: is_exception + u16: length of array of children + u32: char data or offset to char data + u32: offset to array of children = 12 bytes

以及固定槽位(注释列出了 i32/i8 双视角下同一地址的不同偏移):

常量i32 偏移对应字节偏移用途
HOSTNAME_SLOT00主机名(小写)字符数据区
RULES_PTR_SLOT100400规则树根节点指针
CHARDATA_PTR_SLOT101404长标签(>4 字符)字符数据区指针
LABEL_INDICES_SLOT256256标签索引表(最多 128 个标签的 [len, beg] 字节对)
SUFFIX_NOT_FOUND_SLOT399*兜底命中标志位

WAT 中对这些偏移的使用可以在指令中直接看到:如 第 84-92 行i32.const 404 / i32.const 400分别对应CHARDATA_PTR_SLOTRULES_PTR_SLOT的字节偏移。这种"JS 与 WASM 共用一套魔数"的设计,使得 WASM 只是热点函数(getPublicSuffixPos)的语言替换,数据编码零转换、零拷贝。

运行时:WASM 是可选项,JS 是兜底

publicsuffixlist.js 第 543-544 行 的注释点明了架构基调:

The WASM module is entirely optional, the JS implementation will be used should the WASM module be unavailable for whatever reason.

.wasm只是对getPublicSuffixPosJS的透明加速;任何环节失败(不支持 WebAssembly、CPU 非小端、fetch 失败等)都会静默回落到纯 JS 路径,功能不受影响。enableWASM的启用流程(第 546-604 行)包含几道前置检查:

  • typeof WebAssembly !== 'object'直接返回 false;
  • 端序探测:写入Uint32Array [1]后检查首字节是否为 1(第 553-556 行)。注释解释了原因——WASM 代码依赖 JS 侧的原生uint32数组视图,只有原生小端 CPU 上两者布局才一致;
  • 成功后把 WASM 内存增长到覆盖现有数据,把pslBuffer32内容拷入 WASM 内存,并将getPublicSuffixPos切换为instance.exports.getPublicSuffixPos

在 uBlock Origin 主程序中,这一调用发生在后台的 PSL 加载链路 storage.js 的µb.loadPublicSuffixList:

// WASM is nice but not critical if ( vAPI.canWASM && this.hiddenSettings.disableWebAssembly !== true ) { const wasmModuleFetcher = function(path) { return fetch( `${path}.wasm`, { mode: 'same-origin' }).then( WebAssembly.compileStreaming ).catch(reason => { ubolog(reason); }); }; let result = false; try { result = await psl.enableWASM(wasmModuleFetcher, './lib/publicsuffixlist/wasm/' ); } catch(reason) { ubolog(reason); } ... }

可见三个工程细节:

  1. 双重开关:只有浏览器能力检测vAPI.canWASM通过、且用户没有在隐藏设置里打开disableWebAssembly时,才会尝试加载 WASM;
  2. 流式编译:fetcher 用fetch(..., { mode: 'same-origin' }).then(WebAssembly.compileStreaming),直接以 Response 流交给 WebAssembly 编译器,请求的正是本文档所在目录下的publicsuffixlist.wasm(路径拼接为./lib/publicsuffixlist/wasm/publicsuffixlist.wasm);
  3. 日志留痕:成功时打印WASM PSL ready ... ms after launch,方便在控制台观察 WASM 何时就位。

WASM 就绪后,PSL 本体仍按正常流程加载:优先从缓存读取序列化快照(selfie/<assetKey>,经psl.fromSelfie还原),否则从 PSL 资产取文本并用punycode.toASCII转换后psl.parse编译入库,同时把toSelfie()结果写回缓存(见 storage.js 第 1273-1294 行)。因为数据布局完全一致,这套流程对 WASM 是否启用无感。

如何验证与审查这份 WASM

  • 单元测试:npm 平台的测试 platform/npm/tests/wasm.js 覆盖了 README 隐含的两条路径——在WebAssembly可用时enableWASM()应兑现为true(第 36-40 行),在被显式置为undefined的环境(模拟不支持 WASM 的运行时)中应兑现为false(第 43-52 行),验证了"可选加速、优雅降级"这一契约;
  • 构建一致性审查:按前文的wat2wasm publicsuffixlist.wat -o publicsuffixlist.wasm.wat重新编译一份,与仓库中提交的publicsuffixlist.wasm对比,即可确认二进制产物确实由文本源码生成,这是 README "For code reviewers" 一节的原始意图;
  • 反汇编审查:如 README "See also" 所述,可用 WasmExplorer 类工具将publicsuffixlist.wasm反汇编,与 publicsuffixlist.wat 的指令序列逐条对照,确认编译产物没有偏离源码。

小结

这份 wasm 目录的 README 篇幅虽小,却把 uBlock Origin 一个典型的"微加速"组件交代得完整:.wat是手写源码、.wasmwat2wasm的直接产物、模块只导出一个依赖共享内存的查询函数、JS 永远是可用的兜底实现。对维护者而言,审查这份 WASM 不需要任何编译工具链之外的知识——读 300 多行带注释的 WAT 即可覆盖全部逻辑;对使用者而言,disableWebAssembly隐藏设置与运行时自动降级保证了即使 WASM 加载失败,PSL 域名解析功能依然完整。

【免费下载链接】uBlockuBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean.项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询