BrowserLeaks WebGL 报告:采集阶段与缺失值
从上下文选择到参数、扩展、精度和像素读取,逐步追踪 BrowserLeaks WebGL 字段,并按阶段诊断缺失值。
BotBrowser Team
BrowserLeaks WebGL 报告中的渲染器或图像哈希为空,并不能单独说明原因。公开页面把上下文发现、普通参数读取、可选渲染器元数据、着色器精度检查和渲染像素摘要放在一起。BotBrowser 可以为获授权的 WebGL 检查保持声明的配置一致,但无法解释第三方报告为何缺字段,也不能预测其他服务的判定。CreepJS WebGL 源码分析跟踪的是另一套采集流程及其自身的跨上下文比较。
TL;DR
- BrowserLeaks 按固定顺序尝试 WebGL 上下文名称,然后才采集所选上下文的报告行。
- 普通
getParameter行、可选的WEBGL_debug_renderer_info、着色器精度和后续readPixels哈希是不同路径。 - 缺少未掩码渲染器值,不能证明扩展被拦截、确定 GPU 型号、使用隐私工具或存在自动化。
- 记录页面版本和产生缺失值的阶段;比较前,在相同浏览器版本和来源下重跑该阶段。
- BotBrowser 文档说明了 WebGL 检查的配置控制,并不保证 BrowserLeaks 或其他网站显示某个具体值。
Contents
观看视频
本视频为英文配音。视频把五个采集阶段逐级展开,并在真实 Chrome 中运行受控五阶段夹具:getContext('webgl2') 的原生结果、调试扩展返回的渲染器字符串、着色器精度整数,以及真实的 readPixels 缓冲区。夹具是对公开采集顺序的受控重放,并不测量某个具体用户的设备。
先看采集阶段
BrowserLeaks 的公开 WebGL 页面是一个报告模板,分别展示支持检测、WebGL 指纹、报告哈希、图像哈希和渲染图像。页面和脚本源码能说明采集顺序,但下载这些文件并不等于在浏览器中运行了报告。因此,下面是源码层面的流程图解,不是某个实时配置的运行结果。
脚本按以下顺序尝试上下文名称:webgl2、webgl、moz-webgl、webkit-3d 和 webgl2-compute。它为每个候选创建 canvas 并调用 getContext,保留第一个可用的上下文继续执行。即使两种情形最终都显示了一些报告行,落到后备候选也不等于首个候选成功。
尝试循环里有两个细节,在报告“看起来不对”时值得注意。其一,canvas 固定为 256×128 并复用于绘图阶段,因此报告中的绘制缓冲尺寸描述的是这块 canvas,而不是浏览器窗口。其二,每次尝试都传入 failIfMajorPerformanceCaveat: false,并会跳过 isContextLost() 已返回真的上下文。两者任一不通过的候选会被静默略过,循环继续;这解释了为什么某个后备名称能产出完整报告,而首个名称什么都没有。
页面脚本还会保留每一个成功取得的上下文,而不只是当前选中的那个。上下文选择器由该列表生成,所以选择器里出现多少个名称,就记录了有多少次尝试成功。如果选择器只显示 webgl,说明 webgl2 被尝试过并被拒绝;它不说明原因,也不代表浏览器整体不支持 WebGL2。
| 阶段 | 公开脚本的操作 | 缺少输出能缩小的范围 |
|---|---|---|
| 上下文发现 | 在新 canvas 上尝试候选名称 | 是否取得候选上下文;不能解释后续字段为何缺失 |
| 参数行 | 用报告元素 ID 查找上下文枚举常量,再将该常量传给 getParameter | 哪一个普通查询没有产生可显示的值 |
| 调试渲染器 | 请求 WEBGL_debug_renderer_info,仅在返回扩展对象时读取未掩码常量 | 可选分支是否返回值;不能据此确定 GPU 身份 |
| 精度 | 单独调用着色器精度辅助方法 | 精度辅助方法是否产生可用摘要 |
| 图像哈希 | 绘制另一场景、读取像素缓冲、序列化并计算摘要 | 绘制与读取路径是否完成;不能代表参数采集成功与否 |
| 报告哈希 | 序列化采集到的对象并对该序列化结果取摘要 | 序列化对象在两次采集间是否稳定;不能代表任何单行正确 |
这张表是阅读辅助,不是浏览器故障原因的完整清单。不同阶段可能产生同样的空白,页面也不一定显示具体失败阶段。通用 WebGL 指纹文章讲更广的信号面和文档化配置;BrowserLeaks 自己的报告流水线是这里的对象。
普通参数与可选渲染器数据
读取普通参数时,BrowserLeaks 遍历 .pname 元素,读取每个元素的 ID,在选中的 WebGL 上下文中查找同名属性,再把得到的枚举常量传给 getParameter。因此,报告表格能列出请求项,但每一行只说明某个上下文中的一次查询。它不是完整 GPU 描述;行为空也无法说明查询是否被跳过、返回值是否未被界面显示,或是否有更早的错误。
把元素 ID 用作属性名会带来一个后果:行名来自页面,而查询的枚举常量来自选中的 WebGL 上下文。公开脚本里的 n 是选中的上下文,每个列出的 ID 都按 getParameter(n[id]) 查询。规范里存在、但页面标记里没有的常量不会成为一行;新增一个能映射到上下文属性的 ID 会改变采集对象,而浏览器并没有变化。
遍历结束后,页面脚本还把三类非参数来源并入同一个对象:getContextAttributes() 返回的上下文属性、drawingBufferColorSpace 与 unpackColorSpace 两个属性,以及受支持的扩展列表。上下文属性是创建上下文时传入的值,而不是浏览器对硬件的报告;两个色彩空间属性是跟随画布色彩空间设置的字符串。把它们放在同一张表里便于阅读,但它们回答的问题与 getParameter 行不同。
未掩码厂商和渲染器字段使用另一条分支。脚本先调用 getExtension('WEBGL_debug_renderer_info');只有得到扩展对象后,才读取 UNMASKED_RENDERER_WEBGL 和 UNMASKED_VENDOR_WEBGL。MDN 说明,扩展可用时 getExtension() 返回对象,不可用时返回 null。这个 API 契约可以解释分支条件,却不能说明具体报告中为何没有未掩码字符串。WEBGL_debug_renderer_info 隐私边界分析 进一步区分 API 状态与身份推断。
另有两个参数通过各自的扩展读取,而不走普通列表:WEBGL_draw_buffers 的 MAX_DRAW_BUFFERS_WEBGL,以及依次尝试 EXT_texture_filter_anisotropic、WEBKIT_EXT_texture_filter_anisotropic、MOZ_EXT_texture_filter_anisotropic 后取到的 MAX_TEXTURE_MAX_ANISOTROPY_EXT。两者都有扩展查找作为前置条件,因此可能与未掩码渲染器出于同样的原因而为空。在页面标记中,这两行与普通参数并列,扫表时容易忽略这一差别。
请在报告记录中区分这些情况:
| 观察结果 | 有依据的说法 | 无依据的推断 |
|---|---|---|
| 普通渲染器行存在,未掩码行为空 | 普通行已显示,可选的未掩码值没有显示 | “浏览器隐藏了某个 GPU” |
| 扩展查询没有返回对象 | 可选分支没有可读取的对象 | “隐私工具拦截了扩展” |
| 没有选中上下文 | 页面脚本没有使用已选上下文继续 | “浏览器没有 GPU” |
| 保存的报告中没有字段 | 保存的产物不含该字段 | “原始 API 调用失败了” |
每种无依据推断都补上了没有观察到的原因。第二次采集可以检查相同阶段能否重现,但如果不记录变化条件,仍无法证明原因。
精度与像素输出是不同信号
着色器精度来自独立的 getShaderPrecisionFormat 调用,不是根据调试渲染器字符串推导。精度摘要和渲染器标签可以不同,不代表其中一个是另一个的损坏副本。如果报告消费端展示这些字段,请分别记录着色器阶段、精度类别、返回的范围与精度,以及异常或缺失状态。
报告给出两种不同的精度摘要。一种把同一着色器阶段的高精度浮点结果与中精度浮点结果对比,当高精度结果报告为零精度时回落到中精度结果。另一种把 HIGH_FLOAT 和 HIGH_INT 两次独立查询压缩成 highp/mediump 与 highp/lowp 的短标签。因此表中的单个标签背后最多对应三次辅助调用,看起来较粗的值也可能来自回落分支,而不是浏览器真实的精度支持。
图像哈希还走另一条路径。BrowserLeaks 绘制一个小型 WebGL 场景,用 readPixels 将像素读入 Uint8Array,序列化数组,再计算该序列化内容的摘要。它描述的是所选上下文和执行条件下的那次绘图,不是 API 参数,也不证明识别了某个人或设备。
绘图本身是一段由 128 步循环生成、角度从 45 度到 315 度的固定扇形线段,使用 LINE_STRIP 和硬编码的投影矩阵绘制。由于几何来自脚本中的常量,图像哈希变化指向渲染路径、canvas 尺寸、色彩空间或回读环节,而不是指向 GPU 型号本身发生变化。报告哈希覆盖的输入不同:它是采集对象序列化后的摘要,任何一行变化都会改变它,包括与渲染无关的行。
进行受控比较时,保留报告版本、浏览器构建、操作系统、来源、所选上下文名称、视口和采集时间。参数行只与参数行比较;扩展数据与同类扩展数据比较;精度与相同查询比较;图像哈希仅在绘图和读取流程不变时比较。如果只有哈希变化,先检查绘图输入和像素读取阶段,再考虑渲染器或配置差异。
不要把证据压成一个名为“WebGL 指纹”的字段。这个标签隐藏了值来自哪个调用,也遮蔽了能够复现的步骤。诊断报告导出或页面兼容性时,按阶段记录结果更有用。
解释之前先分类缺失字段
从第一条匹配的情况开始。后续检查用来区分采集阶段问题和报告展示问题,不预设具体浏览器故障。
查表之前,有两类状态值得先分开。页面显示为 empty 的值,是 getParameter 返回了 null 或空字符串,属于“有返回但为空”,而不是调用缺失。页面显示为 undefined 的值,表示采集对象里根本没有这一行的键,问题指向采集步骤而不是查询本身。这两个标签在截图里相似,后续动作却完全不同。
| 缺少什么 | 首要检查 | 可重复的后续动作 |
|---|---|---|
| 所有 WebGL 报告区块 | 支持检测与上下文发现是否运行 | 在相同来源和浏览器版本下重载一次,记录所选上下文或精确失败阶段 |
| 某个参数 | 下载的页面版本中是否有对应行 ID | 在自己控制的诊断页重复该查询,保留返回类型/值或异常 |
| 仅缺未掩码厂商或渲染器 | 可选分支是否得到扩展对象 | 将扩展可用性与普通渲染器行分开记录;不要从缺失直接推断拦截 |
| 精度摘要 | 查询了哪个着色器阶段和精度类别 | 重复相同调用并保留结果或异常 |
| 仅缺图像哈希 | 绘图、readPixels、序列化和摘要计算是否完成 | 保留绘图版本和读取结果;不要比较不同场景的哈希 |
| 仅缺报告哈希 | 两次采集间是否有任何采集行发生变化 | 先比较行级数值;行未变而报告哈希变化,说明序列化顺序变了 |
如果两次采集不同,写下保持不变与发生变化的条件。页面版本、上下文后备路径、来源、显示配置或绘图不同都可能改变流程,但不能证明浏览器身份变化。相反,单份报告中数值相同,也不能证明无关应用相互等价。
页面保留一个“原始键”开关,用来改写表格左列。切换它只改变读者看到的标签,不改变采集对象,因此同一份采集的两次截图可能在第一列看起来不同。核对两次报告是否采集了相同的行时,应比较键名而非显示标签。
使用受控的报告记录
这是记录结构,不是示例输出。根据报告和自己的采集填写;不要把预期值抄进观察字段。
用一次采集填写的记录,回答的问题比用多次采集填写的记录更窄。一次采集只能确认:在你写下的条件下,某个分支产出了值。只有记录中其余字段保持不变时,改变一个条件的多次采集才能说明该分支是否对该变化敏感。这份记录描述的是条件,不给页面打分。
{
"toolUrl": "https://browserleaks.com/webgl",
"capturedAt": "YYYY-MM-DDTHH:mm:ssZ",
"pageRevision": "保存脚本 URL 或内容哈希",
"browserBuild": "记录实际构建版本",
"origin": "记录报告来源",
"selectedContext": "记录实际上下文名称或 null",
"ordinaryParameterRows": "逐行记录存在、缺失或错误",
"debugRendererExtension": "对象、null 或未执行到",
"precisionChecks": "记录每个查询类别及结果",
"pixelRead": "完成、缺失或错误",
"imageDigest": "仅在确实出现时记录"
}
这个结构将页面版本和采集状态与值放在一起,便于后续重复同一流程。它刻意没有判定字段:要判断应用是否接受某个环境,必须依据该应用文档化且获授权的测试上下文。
FAQ
未掩码渲染器为空就表示扩展被拦截了吗?
不是。这只表示保存的报告没有显示该值。先记录是否到达扩展分支,以及 getExtension() 返回什么,再做更具体的表述。
图像哈希是另一个 GPU 标识符吗?
它是脚本所绘像素的摘要。它可用于比较相同绘图流程,但不能单独识别 GPU、设备或个人。
BotBrowser 能保证这些字段与 BrowserLeaks 相同吗?
不能。BotBrowser 文档说明了经授权的浏览器表面验证配置。BrowserLeaks 报告取决于自己的脚本、页面版本与执行条件,因此任何设置都不能保证第三方输出或分类。
Sources
- BrowserLeaks WebGL 报告及其公开采集脚本,按源码于 2026-11-04 阅读。
- MDN:
WebGLRenderingContext.getExtension()和getParameter()。 - BotBrowser:WebGL 指纹配置与自动化一致性实践。
- CreepJS WebGL 报告采集与能力信号,用于对照 CreepJS 的独立采集流程。
实践结论
应将 BrowserLeaks WebGL 报告看作多个采集路径,而不是单一的通过/失败结果。分别保留上下文、参数、可选扩展、精度与像素读取状态;在记录条件下只重跑缺失的阶段。这样可以形成有依据的比较,无需猜测字段缺失的原因,也不会把渲染差异变成对个人或机器人的判定。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界