密码自动填充不生效?按这七处依次排查

2026-11-05 · 更新于 2026-11-05 ·

密码自动填充不生效?按这七处依次排查

「为什么这一站的账号没弹出来?」是我在 Issue 里被问得最多的一句话。第一次听到时我默认是匹配算法坏了,后来把失败案例逐个复现下来,结论不太给人面子:绝大多数「填不上」不是识别错了,而是你正好站在某一扇门外面——而每一扇门的开法都不一样。

这篇把这条链路从上到下拆成七处判据。每一处都给「怎么确认」和「在哪一档改」,你可以照着往下走,不必先猜。

一、先确认当前是不是解锁态

密码数据是逐字段密文存在 chrome.storage.local 里的,主密码经 PBKDF2(60 万次迭代)派生密钥后才能解密。这意味着扩展在锁定态手里根本没有明文可交出去。

所以第一步不是检查站点,而是看会话:工具栏图标在解锁后会从挂锁换成钥匙,侧边栏顶部也会显示剩余有效期。如果你刚锁屏回来、或者会话到期,那一切填充入口都是灰的——这不是故障,是这套加密方案的必然代价。

顺手说一句副作用:解锁那一下不需要重新加密整库,销毁和重建的只是会话密钥材料与解密快照。所以「解锁很慢」通常不是密码条数太多,而是磁盘读得慢。

二、你用的是哪一档填充入口

这是「文档和我看到的不一样」的头号原因。入口一共三档:

档位 聚焦登录框时发生什么
sidepanel 自动弹出浏览器侧边栏
inline 输入框里出现钥匙图标,点击展开页面内下拉面板
manual 什么都不自动发生,需要你点悬浮按钮或工具栏图标

关键差异在于你和「别人」的默认值可能不是同一个:全新安装的默认是 inline;而从早期版本升级上来的存量用户,扩展在升级钩子里把历史默认值冻结成了 sidepanel(并保留「自动打开侧边栏」)。这么做是因为配置读取用 { ...默认值, ...存储值 } 合并,如果什么都不写,存量用户会在某次升级后被新默认值静默改掉手感——对手感敏感的人会觉得「插件突然变了」。

再往上一层:这三档是全局设置,但站点规则可以按域名逐条覆盖。所以同一个浏览器里,github.com 走侧边栏、某个内部系统走内联,是完全可能的。设置面板会写明「本站已被站点规则覆盖为哪一档」,如果你在某一站点看不到入口,先去看这一行,而不是去翻全局开关。清掉唯一那条覆盖会直接移除规则,不会把你堵在「改不回去」的状态里。

三、候选列表里到底有没有这条账号

如果面板出现了,但列表里没有你要的那条,问题在匹配档位。

条目存的网址和当前页面的主机名要能对上。默认档 off 是精确主机匹配——mail.qq.com 的页面只会拿出自 mail.qq.com 的条目,躺在 qq.com 上的那一条不会现身。三档是包含关系:

放宽之前值得先看两个边界,它们解释了很多人「为什么这条不出现」:

  1. evil-qq.com 在 sameMainDomain 档下也不会匹配 mail.qq.com。同主域判定不靠 endsWith 那种字符串后缀裁剪——正是那种写法会把 evil-qq.com 算成 qq.com 的自家人。
  2. a.github.io 和 b.github.io 也不算同主域。github.io 这一类多租户后缀被收在一张内置拒绝表里。这里要说清它的性质:这是一张拒绝表,不是完整的 Public Suffix List。完整 PSL 得引外部数据,而这个模块同时待在侧边栏首屏和 content script 的闭包里,所以表内只放高频、可确证的条目;没被收录的后缀会退回「末两段」口径——也就是说,表外的新后缀仍可能被当成同主域放行。漏收的代价是「和没这张表时一样」,而不是一个更安全的默认值,这一点值得记住。

也就是说:「这条账号在库里的网址那一栏写成了什么」比「这个网站是不是我的」更决定结果。如果你确实需要整个子域家族共享,那就显式写成 *.qq.com 并知道它会连带 fat / uat 一起放开。

四、本地开发页:端口与「这一页其实是哪个环境」

localhost 是最特殊的一格:环回地址没有主域可言,所以它多走一层端口判定,规则是三条:

另外,当前页面本身没有端口时这一轴直接放行。这套「默认按端口隔开、但要留一条不限端口的出路」的设计,是为了既不让两个项目的账号互相串,又不逼你把同一账号录两遍。

顺带是本地开发最常踩的一个真实场景:前端跑在 localhost,接口打到 dev.example.com。这时页面主机名是 localhost,而你的账号存的是远端环境那一格。这个扩展的做法是让本地页声明「我其实是哪个环境的前端」,声明之后直接复用该环境已存的账号,不必为每台开发机再录一份。绑定只对这个 host + 端口生效,且关掉浏览器就没了。

五、content script 到底有没有注进这一页

如果你按快捷键后看到「页面未就绪」这类提示,那一次是真的没送出去。后台在投递前会先向顶层 frame 发一个 PING 探活,收不到回应就不猜、直接把话交给用户,提示你刷新标签页。

这一步存在的理由很实际:扩展安装或更新之前就已经打开的标签页里,没有注入内容脚本。Chrome 不会替你把脚本补进旧文档,所以那些标签页看起来「插件没反应」,刷新即好。

还有一个更隐蔽的坑:从取数到真正投递之间,页面可能发生跳转。所以投递的目标绑的是那一份文档,不是 frameId——否则同一个 frameId 上换了一个新文档,凭证会被塞进一个用户没打算登录的页面里。

六、那一格输入框在 iframe 里吗

不少登录框是嵌在 iframe 里的(社交登录、支付、单点登录跳转)。这一处最容易被说错,所以把边界讲准:

所以如果你在 iframe 里看不到入口,先分清是「同主域的 frame」还是「第三方 frame」。后者不要指望这条路径能填进去:把凭证发给一个来源无法确认的文档,是密码管理器最不该做的事。

七、「填上了」不等于「登录了」,以及自动保存为什么不弹

最后一处最容易造成误会,因为它其实是两个问题。

填充与提交是分开的。 快捷键 Ctrl+Shift+F 默认只做填充与勾选协议,不点登录按钮;侧边栏里的「填充并登录」才会把这三步串起来。想让快捷键也代为提交,需要显式开启「自动触发登录」。默认保守是有意的:自动提交意味着按错一次就可能把一个半成品表单提交出去,或者在不该登录的环境里登录成功。

自动保存不弹,通常是那一格不被认为是密码框。 判定用的是双条件:input[type="password"] 或带 data-aph-password 标记的输入框。后者由内容脚本自己打,MutationObserver 会盯着动态新增的字段补标记——因为大量登录页是先渲染一个空壳、等你聚焦后才把密码框插进 DOM,只认 type="password" 会漏掉这一整类页面。

如果字段认到了但还是不弹保存提示,再看这三层上限:密码库有条目总量上限,收藏数达到上限时会按 LRU 让位,每条密码的历史快照只保留配置份数(默认 3 份)。它们是「不再记录新东西」,不是「坏了」。

排查顺序

现象 先看第几处
所有站点都没反应、入口是灰的 一(会话锁定)
别的站点有、这一站没有 二(入口档位 / 站点规则覆盖)
面板弹了但列表里没有那条 三(匹配档位与主域边界)
本地开发填不到远端账号 四(端口隔离 / 环境绑定)
提示页面未就绪 五(刷新标签页)
只有嵌在第三方 iframe 里的登录框没入口 六(跨主域边界)
填进去了但停在登录页 / 不提示保存 七(提交与字段判定)

这张表里唯一一处「不该被修好」的是第六处:同主域校验与「接收者绑文档」这两道闸是刻意的,放宽它们等于把明文交给一个来源无法确认的文档。其余六处都在用户能自己拨的开关上,这也是为什么我把默认值一律做成保守的那一档。

源码与讨论:GitHub 仓库。已上架 Chrome 应用商店,完全免费,GPL-3.0 开源。


本文涉及的关键文件:utils/types.ts(FillMode / FillEntry 与站点规则覆盖单位)、utils/storage/configManager.ts(新装默认与 freezeLegacyFillDefaults)、utils/domain.ts(resolveMatchTier、环回与端口隔离、公共后缀边界)、entrypoints/background/quickFillHandler.ts(PING 探活与绑文档投递)、utils/frameFill.ts(可填文档集合与同主域判定、逐文档并行下发)、entrypoints/content/LoginAutoSave.ts(data-aph-password 双条件与 MutationObserver)、entrypoints/content/floatingButtons/ 与 entrypoints/content/inlineDropdown/(closed shadow DOM)。