二分 debug 法 yyds
开篇
周三的时候,我和 Danny 几乎同时发现,我们 Bytebase 的网页标题,也就是显示在浏览器 tab 栏上的那行字,它错了!
准确的说,是它不会随着页面的切换而更新了。比如进来的时候是 Sign In,那么即使登录以后,也还是 Sign In;这时候刷新了一下,因为已经登录过了,它进来就是 Bytebase,那么无论怎么切换页面,都还是 Bytebase。
这么严重的问题一定是牵一发而动全身的,也就是说它是某一个确定的地方引发的 bug,而不是因为不符合预期的输入和数据引发的边界问题。考虑到网页标题和 document.title 之间存在明确的关系,我一开始认为它将会很容易排查和修复,然而事情却并没有那么简单。
第一招 - 全局搜索
太真实了,一年没动过,是啊,这里是一个几乎是框架级别的逻辑,又怎么可能轻易会去改到它呢。(BTW 这个插件叫 GitLens,可以看每一行的 git 历史)
基于简单的排除法,那么就是另一个了,在 useLanguage 里,切换语言的时候,会重新从 route meta 里获取一次 title,然后赋值给 document.title。然而,令人惊讶的是,在切换语言的时候,页面标题却是会正确地更新的。
基于简单的排除法,那么,还得是回去看第一个。于是我又到全局 afterEach 的逻辑里面打了一些日志以及断点,试图在它不符合预期的时候,看看是不是因为第一个分支除了问题导致 fallback 到下面的 else 分支。但却并没有任何的收获,不论是日志还是断点,to.meta.title(to) 的值都非常对,而这行代码也仅仅涉及一个 function call 和一个赋值,简单得让人完全无法相信它会出 bug。
第二招 - 移花接木
既然排除了我们自己的代码的问题,那么问题应该就出在了第三方的代码身上。这时候问题来了,node_module 里有海量的代码,不好找也不好调,要怎么才能找到元凶呢?
这……这看起来也太对了,随着点击导航栏,我们打出来的 log 也是对的。但是浏览器的标题还是不会随着它一起跟新。
第三招 - 斗转星移
这时候 E0 在群里一句话提醒了我,他说「还可以 document.querySelector('title').textContent 来改」。
这次断在了一个奇怪的文件,格式化一下,再看看 call stack,也没有什么地方自报家门表名身份的,文件名 tabTitleInstaller 也看不出是什么。但是,根据我模糊的经验,前面显示 VM**** 这是一个 Chrome 扩展里的 JS。
第四招 - 日取其半
果然问题就不复现了。这时候就可以用二分 debug 大法了,开一半关一半,<del>这样就可以在O(logn)时间内快速定位</del>,很快就定位到了问题来自于 o/slash。
大概扫了一眼它的代码(混淆过的也看不出个所以然),它有一个叫 clean-up-tab-title 的逻辑,我猜测是会对原来的窗口标题进行备份,然后在浏览器历史发生变化的时候将它还原。可能因为这里逻辑太过粗放,或者是和我们的 afterEach 逻辑先后顺序有点误差,导致我们所修改的 document.title 又被它改回去了。仔细一点观察会发现有些时候我们的窗口标题会一闪而过,先变成预期的然后又变回去,点 Environments 的时候非常明显。
既然定位了,那么查代码的事情暂时就告一段落了。在 Danny 的建议之下我写了一篇文档,给出了一个固定的 reproduce route 和相关的录屏。
结尾 - 打完收工
回想一下,为什么用 Object.defineProperty 制作的赋值陷阱并没有被 o/slash 踩中呢?这和 Chrome 扩展的执行机制息息相关。
Chrome 的扩展里提供了两种 JS,一种叫 background script,一种叫 content script,只有 content script 是可以访问 DOM 的。这样很容易让人以为它和网页里的 JS 一样是运行在 UI 线程上的,其实不然,在 Chrome Developers 的相关文档里解释到了它 Work in isolated worlds(https://developer.chrome.com/docs/extensions/mv3/content_scripts/)。
结合 devtools 里显示的 VM****,我猜想它应该是运行在一个独立的 VM 里面。而 DOM 是 VM 封装过的,所以虽然它们都可以透过 document.title 来读写页面的标题,但是我们包装过的 document 和 content script 里访问的 document 却并不是同一个对象。这也就解释了我们的陷阱无法捕获扩展代码的现象。
迟来的作业 —— HashiConf Europe 2022 我的走马观花
Bytebase 加入阿里云 PolarDB 开源数据库社区