我写过一个Chrome扩展,用来一键清空那些堆积如山的旧Gmail——促销邮件、社交动态提醒、2022年的巨型附件。它的工作方式和真人操作一模一样:在Gmail标签页里跑搜索、点“全选”、再点“删除”。没有调用API,没有OAuth授权,一切都在浏览器本地完成。

这个脚本安安稳稳跑了将近一年,直到某天,删除动作彻底失效。没有报错,没有红字提示。脚本能正常圈出2000封促销邮件,页面上也显示“已选择2000封”,然而点击“删除”按钮之后,那些邮件纹丝不动地躺在收件箱里,好像什么都没发生过。

打开网易新闻 查看精彩图片

我花了一个比预期久得多的时间才揪出真凶——不想承认具体是几个小时,但绝对够我喝掉三杯咖啡。

一开始我反复检查自己的“全选”逻辑。没有任何问题:列表行高亮正常,Gmail的横幅也确认选中的数量完全正确。真正的毛病出在删除按钮的触发方式上。和任何一个正常人一样,我之前只是朴素地写了:

deleteButton.click()

在Gmail里,这一行代码什么都不会发生。不是报错,也不是警告,而是静默失效。Gmail的UI构建在Google Closure库之上,那些控件根本不理会简单的合成点击事件。它们需要的是一个真实鼠标产出的完整指针事件序列,就像你用手指或者物理鼠标真正按下去的样子。

所以,不能用一次click()了事,我必须把整个事件链逐个派发:

pointerdownmousedownpointerupmouseupclick。严格按这个顺序执行之后,删除和归档按钮才次次听话。对于菜单项,还得先触发悬停事件——pointerovermouseovermouseentermousemove,再跟上前面那串点击序列。

回过头看,道理很明显:Gmail根本不关心“这个元素有没有被点击”,它在反向重构“有没有一个真人把鼠标滑到按钮上,然后按下去”。你必须把人类手指能制造的全部细微事件都演一遍,它才肯相信。

刚修好这个坑,Gmail又换了家具。我的“标签归类”恢复步骤紧接着就卡死了——页面一直等待,一直等待,直到超时。这就是另一个需要重新逆向的故事了。