navigator.getBattery() 无需权限提示即可读取电量与充电状态。Firefox 已移除,WebKit 从未实现,这是给新 API 的一课。
有那么几年,任何网站都可以问你的笔记本电脑或手机一个略显私密的问题——“你还剩多少电,现在在充电吗?”——而且能立刻得到答案,不需要任何权限弹窗。Battery Status API 最初的设计初衷相当正当:让页面在用户设备快没电时,调暗动画或推迟一次后台同步。但研究者证明它也能被当作一种短时追踪信号来读取,不到几年,一家主流浏览器就将其移除,另一家则从未实现过。这是“善意的 Web API 因隐私代价浮出水面而被回滚”最清晰的真实案例之一。
核心要点
navigator.getBattery()曾向任何脚本暴露level、charging、chargingTime和dischargingTime,全程零权限提示——这种组合在硬件相关 API 中相当罕见。- 2015 年的一项学术研究(“The Leaking Battery”,作者 Olejnik、Acar、Castelluccia 与 Diaz) 证明这些读数能在短时间窗口内跨来源重新识别同一浏览器;而在按完整精度上报读数的浏览器上,脚本还能反推出电池的实际容量——这是一个寿命长得多的信号。
- Firefox 在 52 版(2017 年 3 月)移除了网页内容对该 API 的访问权限,理由是隐私风险;Safari/WebKit 从未实现过它,认为这块指纹暴露面不值得。
- 基于 Chromium 的浏览器至今仍在提供该 API——Chrome、Edge、Opera 和三星浏览器都支持
navigator.getBattery()。 - BrowserInsight 的指纹检测会显示你自己的浏览器目前还暴露了哪些这类低调信号。
navigator.getBattery() 曾暴露什么
该 API 在 navigator 上挂载了单个返回 Promise 的方法。在支持它的浏览器上,读取只需一行代码,且不需要任何用户交互:
navigator.getBattery().then((battery) => {
console.log(battery.level); // 0 到 1,例如 0.73
console.log(battery.charging); // true / false
console.log(battery.chargingTime); // 充满所需秒数,或 Infinity
console.log(battery.dischargingTime);// 耗尽所需秒数,或 Infinity
});
四个值在起作用:一个小数形式的电量 level、一个布尔型的 charging 状态,以及两个随浏览器持续观测电池行为而不断重新计算的时间估算值。该 API 还会触发 levelchange、chargingchange、chargingtimechange 和 dischargingtimechange 事件,所以脚本甚至不需要轮询——只要挂上监听,就能实时看着访客的电量一点点掉下去。
它的访问门槛很低。这个 API 仅限安全上下文(即需要 HTTPS),也不向 Web Worker 暴露;但在一个普通的 HTTPS 页面里,没有权限提示,不要求用户手势,界面上也不会有任何迹象表明页面刚刚读取过什么。更关键的是,直接嵌进页面的第三方统计或广告脚本——这是最常见的部署方式——拥有和站点自身代码完全相同的访问权限。(现行规范后来补上了名为 battery 的权限策略特性,默认允许列表是 ["self"],至少把跨源 iframe 挡在了门外,除非嵌入方主动放行。)
The Leaking Battery 研究
这里的隐私问题并不在于单次读数本身有多大识别力——某一刻电量正好是 73% 的人多得是。问题在于这四个值组合起来、持续刷新,构成了一个短暂但精确的状态向量,让两个不同的网站(或同一网站上的两个不同追踪器)即便在用户已屏蔽 Cookie、开了隐私窗口或在标签页之间来回切换的情况下,也能拿来比对匹配。在短至几秒、长至一两分钟的窗口内,匹配的 level 加上高度接近的 chargingTime/dischargingTime 估算值,就足以把两次原本被认为互不相关的访问关联回同一台设备。
最引人关注的发现,出在这些数值有没有做四舍五入上——答案是没有。在运行于 Linux 上的 Firefox 中,level 是以完整的双精度浮点直接交给脚本的,数据几乎原样透传自操作系统的电源管理守护进程,于是页面拿到的不是干净的 0.73,而是一长串小数。这一点之所以要命,是因为 level 本身就是“当前电量 ÷ 电池满容量”的比值:小数位足够多,脚本就能从这个比值反推出容量本身——那是物理硬件的属性,而不是当前电量,从一次浏览会话到下一次几乎不会变化。
研究者指出,这一手恰恰对最难察觉的那批用户下手最狠。电池容量会随使用而衰减,偏离出厂标称值的方式又因每一块电池而异,所以一块用旧了的电池反推出的数字比新电池更“特别”,也就更具识别力。他们给出的建议并不是删掉这个 API,而是把上报的读数做粗——这对实际功能毫无损失:一个想在电量 20% 时调暗动画的页面,根本不需要精确到小数点后好几位。
为何 Firefox 移除了它、WebKit 又为何从未实现
Firefox 是最早的实现者之一:带前缀的 navigator.mozBattery 从 2012 年的 Firefox 11 起就默认开启,基于 Promise 的 navigator.getBattery() 则在 Firefox 43 落地——Chrome 早一年就已在 Chrome 38 中提供了 getBattery()。指纹研究发布后,Mozilla 的第一反应正是研究者建议的那个小改动:把读数的精度砍掉。更大的判断随后才到来。2017 年 3 月发布的 Firefox 52 干脆移除了网页内容调用 navigator.getBattery() 的能力,认定其隐私代价已不再值得为多数网站保留这点边际效用。该功能仍可被 Firefox 自身的特权代码使用,所以这次改动通常被描述为收回网页内容的访问权限,而不是彻底删除这个 API。
苹果的 WebKit 引擎从一开始就走了更保守的路线:它从未向 Safari 提供过 navigator.getBattery(),这与 WebKit 一贯的立场一致——只要判断某个 API 相对其收益带来了明显的指纹风险,就拒绝实现。
基于 Chromium 的浏览器是例外。caniuse.com 的兼容性数据表目前仍显示该 API 在 Chrome、Edge、Opera 和三星浏览器中受支持——按撰写时 caniuse 统计的全球浏览器使用份额计算,约占 78%——而 Firefox 以及包括 iOS 上 Safari 在内的所有 Safari 变体均报告不支持。这种“Chromium:支持,Firefox:已移除,WebKit:从未实现”的三方分裂意味着,navigator.getBattery 是否存在这件事本身,就是一种粗略、难以伪造的引擎检测信号,与 Network Information API 仅 Chromium 独有的分布如出一辙。
这给新 Web API 上的一课
Battery Status API 的回滚之所以是个有用的案例研究,是因为它公开发生、时间线相当短,而且原因跟任何实现者的恶意都毫无关系——该 API 完全按规范上线,只是在独立研究者仔细审视“持续、免权限地读取一个数值型传感器,跨来源组合起来能做到什么”之后,风险才浮出水面。W3C 目前的工作草案现已直接体现了这一教训,明确写道用户代理“不应暴露电池状态信息的高精度读数,因为那会引入新的指纹向量”,并且“可以混淆所暴露的值”,让脚本无法确定自己看到的究竟是不是真实硬件数据。
这正是值得推广的规律:任何持续报告硬件真实数值读数的 API——电池、传感器、性能计数器——都不能只问“单次读数会不会有识别力”,还要问“让两个不同脚本观察这个值随时间的变化,能不能认出是同一个浏览器”。分桶与四舍五入——Network Information API 的设计者对 downlink 和 rtt 采用的正是同一种缓解手段——如今已成为这类 API 的默认预期,而非事后补丁。
这对你今天意味着什么
如果你用的是 Firefox 或 Safari,这个特定信号早已关闭——两款浏览器都不暴露它,无需任何配置。如果你用的是基于 Chromium 的浏览器,navigator.getBattery() 依然可以被你访问的任何页面调用,不过它对综合浏览器指纹的贡献远小于 Canvas 或 WebGL,因为电量和充电状态会持续变化,而不像那些信号那样在会话间保持稳定。对付这类次要信号,同样的常规防御手段就够用了:限制页面可执行内容的脚本/内容拦截扩展,或是一款默认设置就更严格的隐私向浏览器。
它还有一层反向用途,这也是 BrowserInsight 的指纹检测在该 API 存在时会去读它、而不是直接忽略的原因。这四个值彼此之间必须自洽:一台设备如果同时声称自己没在充电、电量又不满,却报出“距离充满 0 秒”和“距离耗尽无穷久”,那它描述的是一块物理上不可能存在的电池。真实硬件不会给出这种组合,被脚本伪造的浏览器环境才会。于是,当年让这个 API 变成追踪风险的那几个读数,如今又成了一项成本极低的一致性校验——这也再次说明,一个对外撒谎的浏览器,往往不是败在某一个值上,而是败在指纹自相矛盾上。
常见问题
Battery Status API 现在还能在哪些浏览器上用?
还能。Chrome、Edge、Opera 和三星浏览器仍然实现了 navigator.getBattery()。Firefox 在 52 版(2017 年)移除了访问权限,Safari/WebKit 则从未实现过它。
网站能精确知道我还剩多少电吗?
在仍然支持该 API 的浏览器上,可以——level 会以 0 到 1 之间的小数形式报出你的电量,不需要任何权限提示。在 Firefox 和 Safari 上,这个 API 根本不存在,自然无从调用。
Battery Status API 在实践中真的被大规模用于追踪过吗?
没有证据表明它在浏览器采取行动之前曾被广泛滥用为主要追踪手段——促成这次回应的是学术研究揭示出的风险,而非有据可查的大规模利用案例。浏览器是主动回滚的,而非事后补救,这也是它成为一个有用案例研究的部分原因。
屏蔽 JavaScript 能挡住这个信号吗?
能。由于调用 navigator.getBattery() 需要执行脚本,禁用 JavaScript 或使用拦截脚本的扩展,就能在仍然保留该 API 的浏览器上阻止任何页面读取它。
总结
Battery Status API 是 Web 平台历史上一个不起眼、几乎被遗忘的片段,但它清楚地说明了一个更大的道理:一个免权限的 API 并不需要暴露你的姓名或位置就能成为隐私问题——一个持续更新的数值型传感器,只要能被足够多的来源读取,就足以起到同样的作用。Firefox 的移除与 WebKit 的拒绝实现,是浏览器早期将指纹暴露面当作一等设计约束、而非边缘情况来对待的具体例证——如今这种立场已经直接体现在 Network Information 这类新 API 从一开始的规范方式中。运行 BrowserInsight 的指纹检测,看看你自己的浏览器还暴露着哪些这类低调信号。
推荐阅读:


