CreepJS WebGL 报告:上下文与能力信号
沿固定版本源码追踪 CreepJS WebGL 报告字段,区分上下文比较、renderer、像素读取和结论边界。
BotBrowser Team
CreepJS WebGL 报告不是一个图形值,也不是一次简单的通过或失败检查。在固定的公开实现中,不同代码路径分别采集 WebGL 与 WebGL2 参数、可选 renderer 元数据、着色器精度、canvas 图像数据和派生的能力摘要。把这些字段合并成一个判定,会掩盖每个字段由哪个操作产生,以及实际进入了哪条错误处理路径。
BotBrowser 文档说明了由 profile 控制的 WebGL 行为及其验证方式,但并未声称 CreepJS 报告可以识别物理设备或预测其他网站的判定。这里要解决的问题更具体:从一个明确的实现版本追踪报告字段及条件分支,同时保留各类观察的边界。
BotBrowser 支持通过 profile 配置 WebGL,并可在获授权的环境中检查声明的浏览器行为;单凭 CreepJS 字段不能确定物理设备身份或其他网站的判定。
BrowserLeaks 使用另一套采集流程;WebGL 报告分析逐步追踪上下文发现、可选渲染器数据和缺失字段检查,不把这些结果归到 CreepJS 名下。
TL;DR
- 检查的文件是 CreepJS commit
10aa6724cd33a1015db1574211890518cd04f0cc中的src/webgl/index.ts。静态阅读只能说明源码结构,不能证明当前网站行为或 BotBrowser 实测结果。 - 函数分别创建 WebGL 和 WebGL2 上下文。WebGL 上下文缺失时函数结束此路径,但可见分支没有要求 WebGL2 上下文也必须可用才继续。
- 镜像比较会把 WebGL2 参数映射中的选定值与 WebGL 映射中的 truthy 值比较,并跳过
ALIASED_LINE_WIDTH_RANGE、SHADING_LANGUAGE_VERSION和VERSION。 WEBGL_debug_renderer_info、着色器精度、canvas 数据 URI 和readPixels输出是不同的观察。一个通道缺少数据不能解释另一个通道。- 派生能力分支可能在某个值不在内置列表时设置
LowerEntropy.WEBGL。这是该固定实现的行为,不是普遍 WebGL 规则,也不是对个人的判断。
目录
两个上下文启动不同的采集路径
源码为 webgl 上下文创建一个 canvas,为 webgl2 创建另一个,然后分别使用对应名称调用 getContext。函数会检查第一个上下文是否存在。如果不存在,CreepJS 会记录 webgl 测试失败并从这条路径返回。在这一位置,代码没有对 gl2 做等价的提前返回检查;后续 helper 对缺失上下文的处理方式不同。
阅读精简报告时,这个顺序很重要。WebGL 路径失败并不表示两种上下文都已成功测试且返回相同结果。源码也没有说明第一个上下文为什么不可用。创建结果可能受到浏览器支持、canvas 状态、资源条件和应用代码影响。报告字段本身不能确定适用哪种解释。
这一区分对自有诊断页有帮助。页面代码可以分别记录 webgl_context_created 和 webgl2_context_created,并在配置过程抛出异常时另行保留错误。在两次尝试都得到明确定义的结果之前,不应写入单个 graphics_supported 布尔值。这种建议的记录方式用于理解应用自身的采集流程;它不是 CreepJS 的输出格式,也不是浏览器标准 API。
| 采集事件 | 固定源码能说明什么 | 仍然未知的内容 |
|---|---|---|
| WebGL 上下文缺失 | 此函数会记录 webgl 测试失败并停止该路径 | 请求为何没有生成上下文,或重试是否会得到不同结果 |
| WebGL 存在、WebGL2 缺失 | 流程会越过第一道检查;接收缺失上下文的 helper 可能返回空数据或 undefined | 后续报告字段是否为特定使用方保留了这一区别 |
| 两个上下文引用都存在 | 后续参数与渲染 helper 可以接收这两个引用 | 每个可选扩展和读取操作是否都会成功 |
| 字段赋值前配置过程抛出异常 | 外层错误处理可能返回错误形式的结果 | 如果没有连同错误保留阶段信息,就无法知道哪个操作失败 |
这是控制流图,不是兼容性矩阵。上下文可用不代表所有可选扩展都存在,成功创建上下文也不保证绘图缓冲区可读。应用比较浏览器版本时,应分别记录请求的上下文、调用结果和后续操作。这样能明确显示采集代码的变化,避免把它误认为图形能力变化。
WebGL 指纹概览介绍更广泛的信息暴露面和 BotBrowser profile 控制。固定版本的 src/webgl/index.ts 分别构造上下文结果、参数比较、可选 renderer 元数据、着色器精度、canvas 与像素输出,以及派生能力字段。WebGL renderer 缺失状态分析讨论应用侧的状态,例如未请求字段、上下文不可用或丢失、扩展不可用;它不追踪 CreepJS 的输出组装流程。
镜像比较只覆盖条件限定的范围
为两个上下文构造参数对象后,实现会定义三个不参与镜像比较的键:ALIASED_LINE_WIDTH_RANGE、SHADING_LANGUAGE_VERSION 和 VERSION。差异列表从 WebGL2 对象的键中生成。候选键还必须在 WebGL 对象中有 truthy 值、不属于排除项,并且两个对象中的字符串表示不同。只有结果列表非空时,报告分支才会运行。
遍历 WebGL2 参数对象中的每个键:
仅当 WebGL[key] 为 truthy 时保留
跳过 ALIASED_LINE_WIDTH_RANGE、SHADING_LANGUAGE_VERSION、VERSION
比较 String(WebGL[key]) 和 String(WebGL2[key])
只在至少剩下一个键时报告差异
这是一种有用的源码检查方式,因为它同时展示覆盖范围和遗漏之处。它不是对两个上下文暴露的所有值作穷尽相等断言。仅出现在 WebGL 对象中的属性不会被遍历。WebGL 侧的 falsey 值不会通过 truthy 条件。字符串转换可能隐藏的差异,也可能与数组或类型化值的结构比较不同。无论值是什么,三个被排除的键都不在这项检查范围内。
| 输入形态示例 | 会进入源码差异列表吗? | 原因 |
|---|---|---|
| 两个映射中,未排除键具有相同 truthy 值 | 不会 | 字符串表示相同 |
| 两个映射中,未排除键具有不同 truthy 值 | 会 | 字符串表示不同 |
WebGL 值为 0、false、''、null 或 undefined | 不会 | 过滤条件要求 WebGL 侧的值为 truthy |
| WebGL2 映射中不存在该键 | 不会 | 实现遍历 WebGL2 的键 |
键为 VERSION 或另外两个排除键之一 | 不会 | 显式排除映射会将其过滤 |
这些情况描述的是固定源码中可见的谓词,并不声称某个浏览器实际返回了什么值。检查指定 commit 中的 VersionParam 对象、Object.keys(params2) 过滤器以及 mismatch.length 条件,就能复核这个推理。这一区分可以避免过度解读:“报告没有显示差异”不等于所有 WebGL 和 WebGL2 输出都相等;它仅表示该谓词针对收到的数据生成了空列表。
报告也不会告诉维护者,差异源于实现缺陷、不支持的值、序列化、上下文缺失还是采集逻辑变化。这些是可调查的方向,不是分支本身给出的结论。如果原因很重要,诊断记录应保留字段名称和采集阶段;汇总成“WebGL 差异”计数会丢掉区分情况所需的证据。
renderer 字段与精度字段不能互换
源码先收集每个上下文的参数值。然后在 WEBGL_debug_renderer_info 可用时加入未屏蔽的 renderer 信息。helper 会向上下文请求该扩展;如果没有得到扩展对象,就返回空对象。如果扩展可用,它会通过 getParameter 读取 UNMASKED_VENDOR_WEBGL 和 UNMASKED_RENDERER_WEBGL。
这条可选扩展路径不同于常规上下文参数。Khronos 扩展规范定义了可选的未屏蔽 vendor 和 renderer 常量;MDN getParameter 参考说明了查询 API。这些来源都没有把扩展对象缺失解释为特定原因。它不能证明启用了隐私设置、缺少 GPU、策略作出了决定或应用存在某种意图;它仅表示 helper 没有通过这条路径添加这两个字段。
着色器精度是另一类字段。helper 会为 vertex 和 fragment shader 请求多种格式,包括低、中、高浮点精度以及高整数精度。随后,它会将 precision、rangeMax 和 rangeMin 属性写入与着色器阶段和格式关联的键中。这些字段描述的是当前上下文精度查询所返回的结果。它们不是 renderer 名称、扩展标志,也不能证明两个设备在物理上相同。
最终参数对象还会添加选定的上下文属性和限制。可见的组装代码包括 antialias 属性、MAX_VIEWPORT_DIMS、找到各向异性扩展时的最大纹理各向异性,以及找到相应扩展时的 MAX_DRAW_BUFFERS_WEBGL。某些查询由 attempt helper 包装,而依赖扩展的值可能缺失。因此,只要外围输出保留了区别,报告使用方就应分别表示“未出现”“未查询”和“查询发生错误”。
| 输出类别 | 源码操作 | 缺失值不能证明什么 |
|---|---|---|
| 标准参数 | 遍历选定的参数名称并调用 getParameter | 浏览器不具备任何图形能力 |
| 未屏蔽 vendor/renderer | 请求 WEBGL_debug_renderer_info,再查询两个常量 | 缺失是由隐私设置造成的 |
| 着色器精度 | 对指定格式调用 getShaderPrecisionFormat | 稳定的设备身份或网站判定 |
| 可选限制 | 查询依赖扩展的常量或上下文属性 | 另一个独立查询是否会成功 |
这张表有意描述源码路径,而不是推荐的跟踪数据结构。renderer 字符串可能具有识别性,支持问题也未必需要它。产品应只收集完成已声明任务所需的数据,说明可选诊断,并避免把原始标签用作账户键。BotBrowser WebGL 文档介绍由 profile 控制的 BotBrowser 自身设置;CreepJS 源码并不能证明 BotBrowser 会在某人的设备上返回特定值。
canvas 导出与像素读取可能不一致
getWebGLData helper 会在上下文中绘制图案,并尝试生成两类输出。一类是 canvas 数据 URI;另一类是由 readPixels 填充的字节数组。源码读取绘图缓冲区尺寸,将宽和高分别除以 15 和 6 得到更小的尺寸,为每个采样像素分配 4 个字节,然后从左下角原点请求 RGBA 无符号字节值。
这个执行顺序会形成一个具体的部分失败情况。函数先尝试 toDataURL,之后才调用 readPixels。如果内部的 readPixels 调用抛出异常,catch 会返回已经得到的 dataURI,并把 pixels 设为 undefined。这不是一个全有或全无的结果。看到图像值存在但像素数据缺失,不应据此推断整个 WebGL 路径失败。反过来,data URI 缺失也不能证明曾尝试像素读取:更早发生的错误可能先进入外层 catch。
helper 对 OffscreenCanvas 还有特殊处理:它会创建普通 canvas 来生成数据 URI,并在其上绘制。像素读取仍绑定原来的 WebGL 上下文。因此,图像导出路径和像素路径通过绘制相关联,但它们不是同一个 API 调用或同一种输出表示。
| 失败位置 | 数据 URI 路径 | 像素路径 | 解读边界 |
|---|---|---|---|
| 上下文参数缺失 | helper 返回未定义的输出字段 | 不会读取 | 该 helper 不说明上下文为何缺失 |
| 绘制或创建 data URI 时抛错 | 外层错误处理会捕获错误 | 尚未进入后续读取 | 不应归类为 readPixels 失败 |
创建 URI 后 readPixels 抛错 | helper 可能返回 data URI | pixels 变成 undefined | 两个字段没有共同的原子成功标志 |
| 两个操作都返回 | data URI 存在 | 返回像素数组 | 两个值都不能单独描述物理设备 |
维护自有渲染页面的工程师可以用这张图编写支持测试:分别记录是否执行了绘制、序列化是否完成、像素读取是否完成。将输出分开,既能评估备用路径,也避免保留超过支持问题所需的数据。采样网格不是标准化或唯一的指纹,任一输出也不能预测第三方是否接受某个会话。
能力摘要是在后续派生的
收集源字段后,模块会从通过 JavaScript truthy 条件且非字符串的顶层参数值创建标准化数字列表。Array.prototype.flat() 将数组展开一层;随后用 Number 转换得到的每个元素、移除重复项并按数值排序。代码再使用 renderer 字符串派生 GPU 品牌,并计算两个不同摘要:一个由品牌和参数向量生成的紧凑值,以及一个通过折叠数字列表得到的能力值。
模块会将这些派生值与内置列表比较。如果品牌能力值存在但不在品牌列表中,hasSusGpu 条件就为真。如果能力派生值存在但不在数字列表中,hasSusCapabilities 条件就为真。任一分支都可以设置 LowerEntropy.WEBGL,并使用相应标签调用 sendToTrash。之后源码会把 WebGL 测试记录为通过,并返回采集数据及 GPU 分类字段。
变量名里的“suspicious”属于实现的内部命名,不是标准化的可疑硬件定义。值不在源码列表中,可能源于列表更新、列表未覆盖的组合、缺失或转换后的参数,或运行环境。静态源码无法确定当前生成报告的浏览器符合哪种情况。
| 派生项 | 源码中可见的输入 | 该分支不能证明什么 |
|---|---|---|
webglParams | 通过 truthy 条件且非字符串的顶层值;数组展开一层,再转为数字、去重并排序 | 所有 WebGL 参数都存在,或该向量在不同版本间稳定 |
webglBrandCapabilities | 由 renderer 派生的品牌及参数向量的字符串形式 | renderer 字符串指向物理硬件,或派生值能唯一识别设备 |
webglCapabilities | 数字参数值及其排序后位置参与的折叠 | 结果是行业标准的能力标识 |
LowerEntropy.WEBGL 分支 | 对内置品牌和能力数组进行成员检查 | 普遍的 bot 判定、账户身份或策略原因 |
原始字段与派生摘要有重要区别。原始字段可以追溯到一个 WebGL API 调用;摘要则可能依赖采集后的标准化、排序、紧凑值函数、查询列表和 truthy 检查。如果最终报告变化,应先比较源码版本及这些转换步骤,不应直接把变化归因于物理 GPU。
CreepJS 信号组分析介绍报告如何组织独立的浏览器表面。跨组交叉检查与判定分析讨论组合与决策逻辑。WebGL 模块的派生分支使用自身收集的字段和内置列表;单看这一分支不能说明跨组聚合逻辑。
解读报告时保留证据边界
严谨的源码检查从固定版本开始。可变的主分支中,同一路径可能在报告保存后发生变化。固定 commit 10aa6724cd33a1015db1574211890518cd04f0cc 和 src/webgl/index.ts 路径可以让行引用和控制流说明可复核,但仍不能证明线上网站部署了该版本、某份具体报告由它生成,或某个浏览器执行了其中每条路径。
检查报告时,只记录回答问题所需的事实:
- 找到可见字段及其输出类别:上下文、参数、可选扩展、着色器查询、数据 URI、像素数组或派生摘要。
- 归因前,将字段对应到固定源码中的函数和分支。
- 当源码或报告暴露这些状态时,分别保留缺失、undefined、空值、错误和存在值。
- 检查一个值是直接比较、转成字符串、标准化、转换还是与列表匹配。
- 只陈述观察到的分支支持的有限结论;没有观察到的原因保持未定。
这些步骤适用于隐私审查、回归分析和第三方报告解释,不需要编造设备身份。源码快照能说明给定输入下代码会如何处理;它既不提供这些输入,也不能证明未观察到的分支执行过。
常见问题
WebGL renderer 缺失是否表示 WebGL 已禁用?
不是。renderer 字段来自可选扩展 WEBGL_debug_renderer_info,而上下文创建是单独检查的。此路径可能已经有上下文,但没有加入未屏蔽的 renderer 值。仅凭字段缺失无法确定原因。
镜像比较会比较所有 WebGL 值吗?
不会。它遍历 WebGL2 参数对象的键,要求对应的 WebGL 值为 truthy,跳过三个指定键,并比较字符串形式。空差异列表仅适用于这个谓词及其收到的数据。
LowerEntropy.WEBGL 是否表示浏览器是 bot?
固定版本模块会在某个派生值不在内置列表中时设置此内部标记。源码没有把它定义为通用 WebGL 标准;仅凭该字段也无法确定网站对访客的判定。
data URI 成功是否意味着 readPixels 也成功?
不意味着。helper 先序列化,再读取像素。内部读取错误的 catch 可以在像素数据为 undefined 时仍返回 data URI。这两个字段的完成状态不同。
来源
- 固定于 commit
10aa6724cd33a1015db1574211890518cd04f0cc的 CreepJS WebGL 实现 - Khronos WebGL 1.0 规范
- Khronos
WEBGL_debug_renderer_info扩展 - MDN:
WebGLRenderingContext.getParameter() - MDN:
HTMLCanvasElement.toDataURL() - BotBrowser WebGL 文档
- BotBrowser 自动化一致性文档
实践结论
应将报告视为一组依赖源码版本的观察,而不是单一设备标签。固定实现版本,分别保留生成每个值的上下文和操作,并区分可选 renderer 数据、图像序列化、像素读取和派生能力分支。字段缺失时,只在源码支持的范围内描述缺失;不要推断原因或其他网站的判定。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界