豆备 DOUBAK
← 开发日志

一按「开始抓取」就报错:那个 API 在这里根本不存在

每份档案里都写着「这是哪份代码抓的」。那个数字曾经有三份互不认识的副本,全都写死成 0.0.1;而修好它的那一版,一按「开始抓取」就抛错。

扩展这周升到 0.9.0。听上去只是改个数字,实际上是把一个已经错了很久、 而且错法是永久的的东西收了口。

那个数字不是给人看的

每份档案的 manifest.json 里有一个 producer.version, 每个数据段的 WARC 头里也有一份。它只回答一个问题:这些字节是哪份代码抓下来的。 抓取是整条链路上唯一不可逆的一步——解析可以重跑,站点可以重建, 但没人能请一千个用户回去重抓一遍。所以写进档案的东西错了就是永久错。

而这个数字当时有三份副本,互相不认识:

  • manifest.json / package.json——权威的那份,Chrome 与应用商店认它
  • crawl/runner.js 开工路径里写死的 '0.0.1'
  • bundle/bundle-writer.js 的默认参数,同样写死 '0.0.1'

更糟的是崩溃恢复那条路径根本没传过这个字段,全靠上面那个默认值兜着。 而 MV3 的后台随时会被浏览器回收,一场几小时的抓取必然跨越好几次—— 也就是说那不是异常路径,那是常态。恢复之后写下的每一个段,头里都躺着那个字面量。

注意已经导出的八份档案一律自称 0.0.1,这部分追不回来了。 一个对不上的版本号比没有更糟:它看起来像证据

修法很短:读不到就抛,绝不编一个

版本号只从 manifest.json 来,读不到就抛错。这条听起来很硬, 但对着「写进不可逆的档案」这个用途,猜一个数字比停下来更坏。 BundleWriterproducer 也从「有默认值」改成必传,缺就抛。

然后一按「开始抓取」就炸了

第一版是 chrome.runtime.getManifest().version。在面板里试是好的, 测试也全绿。装上之后按下按钮,直接抛「拿不到 manifest.json 里的版本号」, 全量增量都一样——那句「宁可抛错也不编一个」如约抛了错,只是抛在了每一次抓取上。

原因是真正写档案的那条路径不跑在面板里。面板是普通扩展页面, 扩展 API 全都有;而抓取跑在一个 offscreen document 里,那里 只暴露 chrome.runtime 的消息 API(Chrome 的原话是 “only the chrome.runtime messaging APIs are exposed to the offscreen document”)。 getManifest 不在其中。

三个上下文的能力不一样,而出问题的那个恰恰是测试进不去的那个node:test 里压根没有 chrome,那一行从来没被执行过。

表早就写着,但没有任何东西执行它

最难受的是,offscreen.js 开头就有一张「这里能用哪些 chrome API」的表, 规则写得清清楚楚,写了好几个月。表是对的,只是没有任何东西执行它

所以现在有一条测试顺着 offscreen 的 import 图走一遍,把每一个 chrome.<命名空间>.<成员> 的调用点对照白名单查过去。 这类事只能静态地查——读代码,而不是跑代码,因为会出错的那个上下文跑不起来。

它还得防自己空转:那条测试要求 import 图里至少收得到 20 个模块, 否则哪天正则被改坏、一个模块都没扫到,它会自动变绿。 第一版就差点这样——它只收到了一个模块,而且照样通过。

版本号本身则退回去直接 fetch manifest.json 这个文件。 这反而比问 chrome 更贴合「只有一个来源」:那就是同一个文件; 而读扩展自己的资源在三个上下文里都成立,不需要任何扩展 API。

守着它的那条测试,第一版也挡不住它要防的 bug

新加的测试原本写的是「src/ 里不许出现当前版本号」。看着挺对, 但真实发生过的是写死了一个过期的版本:manifest 涨到 0.9.0 之后, 那三份 '0.0.1' 与它不再相等——按那条判据反而全都合规。

改成不比对具体数值,只问「代码里还有没有形如 x.y.z 的字符串字面量」。 注释不算,讲清这次的来龙去脉恰恰需要把 '0.0.1' 写出来。 然后反向验证一遍:给 runner 加一个 ?? '0.0.1' 的兜底,立刻红。

测试写完要拿它防的那个 bug 试一次,否则你只知道它是绿的, 不知道它能不能变红——这一条这周吃了两次亏。

← 更新:两个整整齐齐的零 更早:双击 index.html,那就是你的豆瓣 →