CreepJS SVG 指标:字段与边界
从固定版本源码追踪 CreepJS SVG 报告字段,区分有损长度聚合与几何结果,并在不推断身份的前提下读取 blocked 状态。
BotBrowser Team
SVG 文本测量来自浏览器的 SVG/DOM API,本身不是设备标签。BotBrowser 可为兼容性工作配置 profile 字体,但这份源码解析没有运行 CreepJS,也不承诺各平台的 SVG 测量相同。
TL;DR
固定版本的 CreepJS SVG 收集器会创建屏幕外文本样本、归并 SVG 包围盒、按不同的 getComputedTextLength() 字符串各保留一个 emoji,并比较添加 shift-svg 类之前、类存在时以及移除后的测量值。这些字段描述的是该实现中的测量与代码分支,不是操作系统、设备或个人标识。尤其是 svgrectSystemSum:它是对不同长度字符串做的有损求和,源码没有将它定义为系统指标。
目录
收集器的构造与范围
BotBrowser 文档描述了基于 profile 的字体包与回退控制,但这不会让 CreepJS 结果变成操作系统或设备标识。这里要回答的是更窄的问题:固定版本 src/svg/index.ts 中哪些操作会生成渲染器称为 SVGRect 的字段?
收集器先组合若干已有的一致性标记,包括 SVGRect 属性、SVGRectElement.getBBox() 和三个 SVGTextContentElement 方法。随后它选择一个 document 引用,追加 div,并插入含多个 <text> 节点的 SVG,每个节点对应 EMOJIS 中一项。样本放在屏幕外,文本使用配置的字体族和 200px 字号。收集器在两次读取之间添加并移除 shift-svg 类。基础选择器与该类选择器指定相同的缩放值,后者只额外使用 !important,因此这些声明并未表明数值缩放发生变化。
这些是源码事实,不是运行测量。源码不能证明每个浏览器都会渲染该样本、配置的字体都已安装,或结果值对某个平台唯一。因此,逐字段的来源表比猜测操作系统更有用:它把每个输出、变换步骤和信息损失放在一起。
收集器还会在测量前和测量过程中等待 queueEvent(timer)。这说明固定实现中的执行顺序,不证明某次活动浏览器会话真的生成了报告。这里没有声称执行了 CreepJS 页面或测量了浏览器。
七个报告字段的来源
返回的 data 对象含七个属性。渲染器会把显示为 bBox、char、subs 和 text 的字段值除以 10,000,并把包含 0 在内的假值显示为 BLOCKED。假值 svgrectSystemSum 显示为 UNSUPPORTED。emojiSet 通过 formatEmojiSet() 格式化;帮助标题包含短哈希和代表值列表。
| 字段 | 源码操作 | 变换或边界 |
|---|---|---|
bBox | svgBox.getBBox() | reduceToObject() 保留非函数属性;getObjectSum() 对这些属性取绝对值后求和。 |
extentOfChar | svgElems[0].getExtentOfChar(EMOJIS[0]) | 参数传入第一个配置的 emoji。仅凭源码不能说该值就是完整 emoji 的 extent。 |
subStringLength | svgElems[0].getSubStringLength(0, 10) | 查询第一个节点,起点为 0、长度为 10;这是调用形式,不是跨浏览器测量结果。 |
computedTextLength | svgElems[0].getComputedTextLength() | 第一个节点的一次测量,单独存储,不等于代表值集合。 |
emojiSet | 每个节点的 getComputedTextLength() | 每个尚未出现的字符串化长度只保留一个 emoji。 |
svgrectSystemSum | 这些字符串组成的 pattern 集合 | 不同字符串的数值片段先求和,再乘以 0.00001。 |
lied | 之前的标记与位移判断 | 初始值来自选定标记;位移/恢复比较不一致时可变成 true。 |
bBox 不是矩形的完整序列化。reduceToObject() 枚举 nativeObj.__proto__ 的键,去掉函数成员,再返回其他属性。之后 getObjectSum() 对每个属性取绝对值并求和。因此,不能把 bBox 重新解释为源码保留了 x、y、width、height 四元组;它是从矩形类对象派生出的标量。
方法名也对应不同输入。getBBox() 获取 SVG 图形元素的包围盒;getComputedTextLength() 返回文本内容长度;getSubStringLength() 接收一对类似范围索引的参数。源码把 EMOJIS[0] 字符串传给 getExtentOfChar()。在没有另外确认 API 如何转换该参数之前,应保留调用本身,不要把返回值说成整个 emoji 的 extent。
字段与操作的对应图是这篇内容的核心产物:源代码形态、变换和解释边界可以同时检查。笼统称为“SVG 指纹”会抹平这些区别,并超出代码实际证明的内容。
emojiSet 为什么丢失重复次数
收集器创建 pattern = new Set(),并取得 .svgrect-emoji 元素列表。对索引 i 对应的每个元素,它读取 getComputedTextLength() 并转成字符串。如果该字符串未出现在 pattern 中,源码才把它加入集合,并把 EMOJIS[i] 加入 emojiSet。
顺序决定了信息损失。如果多个 emoji 得到同一长度字符串,emojiSet 只保留第一个对应项。它不会保留全部输入 emoji、频率计数、字体文件名、回退链或完整字形支持清单。代表值集合不是字体库存。
例如,纯用于说明算法的字符串序列 "42"、"42"、"57",在 pattern 中得到两项,而不是三项。选择结果会是第一个对应 "42" 的 emoji 和第一个对应 "57" 的 emoji。这些数字只是算术示例,不是 CreepJS 输出。示例展示了数据丢失,但没有发布 emoji 语料,也没有声称某个 emoji 有稳定数值。
这与 CreepJS emoji 指标边界不同:那篇追踪 Canvas TextMetrics 元组及其代表值分组。它也不同于字体清单:document.fonts.check() 或 profile 字体列表回答的是另一个问题,并不等同于 SVG 文本节点的计算长度。BotBrowser 字体文档介绍 profile 字体包、回退和渲染控制,但不会把测量长度变成已安装文件列表。
Set 会删除频率。若十个输入共享一个长度、另一个输入不同,后续求和只看到两个不同字符串。它无法分辨第一组包含两个 emoji 还是两百个。把 emojiSet 称作数量、覆盖率或完整支持列表,会与实际数据流冲突。
svgrectSystemSum 计算了什么,又丢掉了什么
变量名含有 System,但实现定义的是更有限的计算。对 pattern 中每个字符串,源码按逗号拆分,用一元 + 转成数值,把假值数值结果替换成 0,再将各部分相加。随后对不同字符串的总和再求和,最终乘以 0.00001。
源码逻辑可以表示为:
sum(pattern.map(s => sum(s.split(',').map(part => +part || 0)))) * 0.00001
输入是不同的 getComputedTextLength() 字符串,不是全部原始节点,也不是包含七个属性的几何元组。重复字符串更早就已被 Set 去除,所以逗号拆分之前信息已经丢失。缩放系数只缩小数值,不会恢复结构,也不会识别平台。
看一个明确的假设集合 {"42", "57"}:两个数值片段相加为 99,再乘 0.00001 得到 0.00099。如果十个节点给出 "42"、一个节点给出 "57",不同值集合仍相同,计算结果也一样。这只是构造出来的算术例子,不是浏览器测量、CreepJS 样本、阈值或参考值。
该计算也不保留每个值由哪个 API 产生、emoji 原始索引、重复次数或字体解释。不要在正文中把它改名为“系统分数”“OS 总和”或“字体指纹”。严谨表述是:“按这个固定源码,对不同的字符串化文本长度中的数值片段求和并缩放”。
Canvas 的区别很重要。文本测量指纹文章讨论 Canvas measureText() 与 TextMetrics,其 API 和结果维度都不同。SVG 收集器不是换个名字重复这些字段。ClientRects 一致性检查讨论 Element 和 Range 的矩形片段,也不负责 SVG 文本长度或此处的代表值集合。
位移检查与 lied 状态
构建代表值集合后,收集器选择 svgElems[0],读取初始 getComputedTextLength()。它添加 shift-svg,再读一次;移除类后,第三次读取恢复值。比较式为:
initial - shifted != unshifted - shifted;
表达式为真时,源码设置 lied = true,并调用 documentLie('SVGTextContentElement.getComputedTextLength', 'failed unshift calculation')。这只说明固定实现走到什么分支,不定义普遍适用的浏览器不变量、不证明两个数值为何不同,也不能证明外部服务会将环境判为自动化。
lied 不只由这项检查初始化。检查之前,它会根据选定的 lieProps 项构造,包含 SVGRect 属性以及 getBBox、getExtentOfChar、getSubStringLength 和 getComputedTextLength。因此 false 只代表当前实现所走路径中的累计判断仍为假,不证明所有 SVG 行为都是原生、未修改或跨平台相同。
状态表可以把观察和解释分开:
| 源码可见状态 | 代码能说明什么 | 不能说明什么 |
|---|---|---|
| 初始、位移和恢复值满足比较关系 | 这条路径没有让该谓词设置 lied。 | profile 正常、特定 OS 或浏览器未修改。 |
| 比较结果不同 | 收集器设置 lied 并记录命名的 documentLie 项。 | 差异原因、意图或下游判定。 |
某个选定的早期 lieProps 项为真 | 初始 lied 已可能为真。 | 未保留相关证据时,哪个具体变化导致它。 |
| 调用抛出异常 | catch 记录 passed: false,返回时没有数据对象。 | SVG 不支持、被策略阻挡或缺失的具体原因。 |
这张表是源码阅读辅助,不是测试结果。它避免把分支名称误写为因果结论。
缺失、blocked 与错误分支
结果渲染器对 !fp.svg 有独立分支,会显示 SVGRect 标题,并在 bBox、char、subs、text 和摘要区放置 blocked 占位符。页面不会说明 fp.svg 为什么缺失。没有独立证据时,不应把占位符改述成“SVG 已禁用”“字体不可用”或“检测到 bot”。
采集本身也包在 try/catch 中。发生异常时,源码调用 logTestResult({ test: 'svg', passed: false }),把错误交给 captureError,随后返回。这与成功得到 lied: true 的 data 对象不同,也与 svgHTML() 收到没有 svg 属性的对象不同。解释记录应保留这些分支,而不是把三种观察压成一个“失败”标签。
支持或研究记录只保留必要信息即可:固定源码版本、字段名、报告显示值还是占位符,以及正在讨论哪条分支。若字段级说明足够,不要保留完整浏览器 profile、原始用户标识或整份 CreepJS 结果。哈希或短标签仍然是数据,也需要明确保留用途。
字段缺失时,先写“在这份可见输出中不存在”。然后把源码中可能的路径列为假设,例如 !fp.svg 展示分支或 catch 分支;没有运行证据时必须注明未确认。固定源码能证明这些分支存在,却不能告诉读者一个未经执行跟踪的报告实际走了哪条路径。
SVG、Canvas 和 DOM 几何彼此不同
三个相邻主题都会出现几何值,但回答的问题不同。SVG 的 getBBox() 和 SVGTextContentElement 方法作用于 SVG 图形或文本。Canvas measureText() 为画布文本返回 TextMetrics。CSSOM getClientRects() 返回元素或范围的已渲染片段矩形。名字相似不意味着数据可互换。
API 边界可以指导检查范围。分析 SVG 文本长度时,跟踪 src/svg/index.ts 中实际元素与 getComputedTextLength() 调用。分析 Canvas 时,记录 measureText() 的输入,以及宽度、实际边界等属性。分析 CSSOM 几何时,分开保留元素或范围片段及其归并过程。ClientRects 一致性文章描述的是最后一种问题,不应用来解释 svgrectSystemSum。
区分这些 API 也有助于产品分析。profile 字体会影响渲染,但一个收集器字段不会透露哪个字体文件提供了字形、是否发生回退,或两个不同长度是否来自不同字体。这些问题需要查看相关字体加载和渲染路径,不能只看 CreepJS 输出。
最小解释记录
下面的模板把结论与证据绑定,同时避免把记录变成用户画像:
collector: CreepJS src/svg/index.ts
source_revision: 10aa6724cd33a1015db1574211890518cd04f0cc
report_field: emojiSet | svgrectSystemSum | lied | other named field
source_operation: exact method or branch
display_state: value shown | blocked placeholder | absent | error recorded
claim: source-bounded description of what was observed
not_established: OS, device, person, cause, or external verdict
retention: reason and deletion date
这是文档模板,不是报告生成器。它刻意不包含数值样本、浏览器 profile 或按用户列出的 emoji。如果比较需要具体值,只记录问题所需的最少值并注明来源;没有真实运行记录时,不要声称这是新运行结果。
比较两份获准记录时,应先对齐源码版本、字段、显示分支和变换。比较 emojiSet 字符串不等于比较所有输入 emoji。由于 Set 丢弃了重复次数,比较 svgrectSystemSum 也不等于比较原始长度分布。比较 lied 是比较收集器谓词,不是证明一个环境真实而另一个不真实。
BotBrowser 能力与限制
BotBrowser 文档描述 profile 字体包、回退行为和跨平台渲染控制。这些能力可用于获准的兼容性比较,例如检查应用在已记录配置下如何工作。公开字体文档没有承诺所有平台的 SVGTextContentElement 测量一致,不提供 SVG 收集器结果,也不定义 CreepJS 中 svgrectSystemSum 的含义。
这条限制很重要:profile 配置不会把静态源码分析变成运行结果,也不会支持对特定设备下结论。BotBrowser 不会因为这份源码解析而运行 CreepJS、改变 SVG 结果、隐藏报告或保证跨平台一致。真实兼容性调查应在已记录配置下比较明确的应用样本,并只保留回答问题所需的证据。
常见问题
svgrectSystemSum 能识别操作系统吗?
不能。固定源码用不同长度字符串中的数值片段计算总和,再乘以 0.00001。它没有定义 OS 标签或可逆分类器。
emojiSet 是完整的 emoji 支持列表吗?
不是。收集器对每个不同的 getComputedTextLength() 字符串只保留第一个 emoji。Set 会移除重复值,源码不记录支持数量或字体文件映射。
extentOfChar 测量完整 emoji 吗?
源码把 EMOJIS[0] 作为方法参数。本次源码检查不能证明返回值代表完整 emoji,因此不做这种解释。
lied: false 能证明测量是原生的吗?
不能。它只说明当前表示的路径没有让累计谓词变为真,不是对全部 SVG 行为或每个 API 调用的通用证明。
blocked 占位符是什么意思?
当 fp.svg 缺失时,渲染器会显示占位符。可见状态不会解释缺失原因。收集器的 catch 是另一个独立分支,没有运行证据时不能推断它发生过。
来源
- 固定版本 CreepJS SVG 收集器,
src/svg/index.ts - MDN:
SVGGraphicsElement.getBBox() - MDN:
SVGTextContentElement.getComputedTextLength() - MDN:
SVGTextContentElement.getExtentOfChar() - MDN:
SVGTextContentElement.getSubStringLength() - BotBrowser 字体文档
实践结论
应把 CreepJS SVG 卡片理解为一串源码定义的操作:包围盒归并、逐节点文本长度测量、按不同字符串选择首个代表值、有损缩放聚合,以及位移/恢复判断。将 bBox、extentOfChar、subStringLength、computedTextLength、emojiSet、svgrectSystemSum 和 lied 分别对应到各自变换。缺失占位符和异常是不同状态。这些字段都不能单独证明操作系统、设备、个人、字体库存或外部判定。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界