Chrome 正在试验把真实 HTML 绘入 canvas 的 API。提案规定了哪些内容不得进入像素、哪些仍可读取,本文逐项解读。
Chrome 正在试验一个新 API,让 <canvas> 不再只能画线条、矩形和文字串,而是可以直接绘制带样式的真实 HTML。这立刻带来一个隐私问题:画布的像素可以被读回,而读取画布像素正是 Canvas 指纹的基础。如果画布里现在能装下一个渲染好的页面,脚本能从中读出什么?提案作者给出了一份明确的清单,列出绝对不能被绘制的内容。本文逐项解读这份清单及其含义。请注意:这是一个实验,不是已发布的功能。
核心要点
- HTML-in-Canvas 是 Chrome Origin Trial(源试用) 中的提案,不是已发布的 Web 标准。按 Chrome 官方文章,试用期延长到 Chrome 160,且方法名已经改过(例如在 Chrome 155 中)。
- 核心隐私规则叫“允许回读的渲染”(read-back-allowed rendering):绘制元素时,不得泄露页面代码本来无法观察到的敏感信息。
- 说明文档将跨源内容、系统颜色与主题、拼写标记、已访问链接状态、待处理的自动填充以及子像素文字抗锯齿排除在绘制结果之外。
- 同时它有意保留了它认为“本来就能检测”的信息,例如滚动条与表单控件外观、光标闪烁频率和
forced-colors。 - 这不是一种现在就存在的指纹技术,而是一个试图避免制造新指纹面的设计。
这个 API 做什么
今天的画布只能绘制基本图元。你可以用 fillText() 画一串文字,却无法交给它一个带布局、折行和表单控件的 <div>。HTML-in-Canvas 提案在 W3C 的 WICG 仓库中开发,新增了对元素做快照并绘入 2D、WebGL 或 WebGPU 画布的能力,2D 场景下的方法叫 drawElementImage()。Chrome 的源试用变更文章指出,试用延长到 Chrome 160,部分方法和属性名在 Chrome 155 中改过,所以你看到的任何代码都只是暂定写法。该功能在 Chrome Platform Status 上也有条目。
它的动机主要是创意与实用:给真实 HTML 加特效、在 3D 场景里放文字、在画布中保留可访问的内容。隐私是设计的约束,而不是目标。
为什么画布是隐私敏感面
画布不只是输出。脚本可以调用 toDataURL() 或 getImageData(),把每个像素当作数据取回。Canvas 指纹正是这样工作的:绘制固定场景、读回、做哈希。哈希之所以不同,是因为 GPU、驱动、字体光栅化器和操作系统各不相同。更完整的背景见我们的浏览器指纹指南。
现在设想画布里能装下一个渲染好的页面。浏览器会绘制一些页面脚本无法直接看到的东西:某个链接是否访问过、操作系统主题是什么颜色、拼写检查有没有给某个词画下划线。如果这些泄露进绘制出的像素,脚本就能把它们读出来,一个看似无害的绘制调用就变成了窥探私有状态的窗口。
“允许回读”规则
说明文档用一条原则处理这个问题,称为 read-back-allowed rendering。按其原文,绘制元素及相关的 paint 事件“不得泄露页面作者代码本来无法观察到的、与安全或隐私相关的敏感信息”。由于 WebGL 和 WebGPU 总是可以回读像素,安全的做法就是从一开始就不把敏感信息画进输出。文档把它表述为防止通过像素回读、计时攻击和失效事件造成泄露。文档并未直接提到“指纹”一词;它是一条安全与隐私规则,下文的指纹视角是我们自己的解读。
哪些内容被排除在像素之外
说明文档列出了必须排除在绘制之外的内容:
- 跨源数据:iframe 和图片等嵌入内容、
background-image这类 URL 引用、被跨源数据污染的画布,以及 SVG 引用。同源 iframe 仍会绘制,但其中的跨源内容不会。 - 系统颜色、主题或偏好设置。
- 拼写和语法标记。
- 已访问链接信息。
- 待处理的表单自动填充信息(JavaScript 本来看不到的部分)。
- 子像素文字抗锯齿。
- 字幕和视频控件。
- 输入法(IME)弹窗及有辨识度的输入法文字格式。
其中多数是显而易见的泄露隐患(已访问链接和自动填充是经典的历史嗅探目标)。对指纹来说,最有意思的是子像素抗锯齿这一项。平台如何平滑文字边缘取决于操作系统、显示器和设置,这也是同一段文字在不同机器上看起来略有差异的原因之一。画布上普通的 fillText() 已经暴露了不少这类渲染行为,它是字体与文字渲染指纹的核心素材。把它从新路径中排除,意味着新 API 不会增加一种更高分辨率的读取方式。
这个对比很重要:提案并没有修复现有的 Canvas 指纹,它只是拒绝把这个攻击面做大。
有意保留的内容
说明文档还列出了在这一语境下它不认为敏感的信息:
- 页内查找文本与文本片段(text fragment)标记;
- 滚动条与表单元素外观,文档称这些在 Blink 和 WebKit 中已可通过
foreignObject检测; - 光标闪烁频率;
forced-colors,文档指出 JavaScript 已能通过媒体查询和系统颜色获得这一信息。
逻辑是一致的:如果页面代码本来就能观察到某个值,把它画出来并不增加新信息。这是站得住脚的规则,但也暴露了边界。forced-colors 之类的信号本来就在 CSS 媒体查询指纹收集的范围内,所以无论如何追踪者都能拿到。提案并没有声称要减少它们。
GPU 路径
GPU 一侧同样存在回读问题,文档明确说 WebGL 和 WebGPU 总是可以回读像素。因此这些排除规则也适用于把元素快照绘入这两类上下文的变体:目前 WebGL 用 texElementSubImage2D(),WebGPU 用 drawElementImageToTexture(),两个名字都是 Chrome 155 改过的。想了解这些上下文本来就泄露什么,可以看我们的 WebGL 指纹深度解析和 WebGPU 指纹。
客观的局限
- 这是提案与实验。Chrome 的试用可能改变或结束,隐私评审也可能修改排除清单。
- 在我们引用的页面上,没有找到其他引擎的承诺,因此我们不对 Firefox 或 Safari 的立场做任何断言。
- 写在设计文档里的规则,质量取决于实现。计时、失效通知、特殊元素类型等边缘情况恰恰是这类规则容易漏的地方,这也是说明文档明确点名计时攻击的原因。
- 这里的任何内容都不会改变 Canvas 指纹今天能做到的事。
这对你意味着什么
对访问者:目前什么都不会变。Origin Trial 只在注册了试用的网站上、且只在试用期内的 Chrome 版本中生效,普通用户无需也无法自行开启。如果你想不管怎样都降低 Canvas 指纹风险,请阅读 Canvas 噪声与随机化为何可能适得其反,并优先选择“一致”而不是“随机”。
对开发者:如果你试用这个 API,请假定名称是暂定的,并且不要指望能在画布里捕获敏感状态——这是有意为之的设计。
查看你自己的 Canvas 哈希
BrowserInsight 的指纹检测使用标准 2D Canvas API 计算 Canvas 哈希,并未使用 HTML-in-Canvas。借此可以看到,脚本读取一个普通画布时,今天就能从你的浏览器拿到什么。

