getClientRects 指纹:DOMRect 一致性检查
分析 CreepJS 的 DOMRect 检查,区分空几何、文本片段、变换和报告缺失,避免把几何差异直接当成设备身份。
BotBrowser Team
getClientRects 指纹分析的是浏览器布局几何,而不是 Client Hints 请求头。矩形变化可能来自被选中的盒、文本片段或坐标上下文变化。BotBrowser 文档提供默认关闭的 ClientRects 确定性噪声,以及用于复现字体度量的种子;这些控制不能证明所有几何路径一致,也不能证明 CreepJS 会接受结果。有效的 DOMRect 报告需要先区分测量对象、API 与具体一致性规则,再判断原因。
观看视频
这段视频为英语讲解,沿文章同一问题展开:CSSOM View 的四个几何接口返回不同形状,因此片段数量、归约后的矩形和选区的矩形不能被当成一个数值来读。视频展示三种报告状态(缺失、空列表、零尺寸矩形)、包围矩形的三条归约分支,以及受控同源夹具上的两段录制控制台会话。两条原生表达式由人手输入,第三条读数由夹具面板打印。视频不涉及网站收集行为,也不给出第三方判定;几何差异不能识别设备、个人或修改手段。音频为英语。
TL;DR
Element.getClientRects()返回盒片段列表;getBoundingClientRect()将列表归约为一个矩形,空列表和零尺寸另有分支。列表长度与包围尺寸回答不同问题。Range几何可以描述选中的文本片段,不能直接等同于元素的完整边框盒。- CreepJS 修订
10aa6724cd33a1015db1574211890518cd04f0cc保存四组几何数组,执行若干特定测试元素的检查,而不是通用设备身份测试。 - DOMRect 报告缺失、片段列表为空、矩形字段包含零,是不同状态。
lied表示该修订的一致性判断,不能确定谁改了数值、哪个设备生成结果或外部网站会如何决策。
Contents
四类几何接口
CSSOM View 根据元素的 CSS 盒定义 Element.getClientRects()。跨多行的行内元素可以产生多个片段,而非一个连续矩形。列表保留片段之间的区别,包括零尺寸片段。没有关联 CSS 盒的元素返回空列表。因此,“ClientRects 值”并不完整:它可能指整个列表、选中的一项、派生并集,或者多个测试元素的摘要。
Element.getBoundingClientRect() 则将几何归约成一个矩形。规范区分空列表、列表内每个矩形都满足宽度或高度为零,以及剩余的并集情况。第一种返回各字段为零的矩形;第二种返回列表首个矩形。否则,并集包含高度或宽度不为零的矩形,这里使用 OR 条件。最后一条不能覆盖前面的例外分支。位置非零但高度为零的矩形,不等同于根本没有片段。
MDN 将元素包围矩形描述为包含内边距与边框、坐标相对于视口的矩形。拿它与 width: 100px 比较时需要考虑盒模型,而不能要求声明宽度等于所有返回宽度。内容宽度和外层边框盒是不同的量。只保存四舍五入宽度的报告,会丢失 API 选择和对应盒模型。
Range.getClientRects() 针对文档中被选中的部分。对应的包围方法归约该范围的列表,同样区分空列表、全零尺寸分支与并集分支。选中的子串不必与容器元素有相同边界:前者描述被选文本,后者可能包含整个盒。文本测量分析 讨论独立的 Canvas measureText() 表面;其 TextMetrics 字段不是 DOMRect 片段列表。
| API | 返回形态 | 保留语义的比较方式 |
|---|---|---|
Element.getClientRects() | 盒片段构成的 DOMRectList | 相同元素与片段位置 |
Element.getBoundingClientRect() | 由包围算法得到的一个 DOMRect | 相同元素与视口上下文 |
Range.getClientRects() | 选定范围的片段列表 | 保留选区边界和文本布局 |
Range.getBoundingClientRect() | 范围列表归约出的一个矩形 | 保留范围及空列表、零尺寸分支 |
矩形接口提供 x、y、width、height、top、right、bottom 和 left。读取程序可以序列化这些字段,但序列化不会让原始对象具有相同含义。MDN 还区分只读列表接口和 JavaScript 数组。将列表转换为数组,只改变容器,不改变产生各项的布局操作。数组摘要仍不能独立解释某个片段为何变化。
固定修订的 CreepJS 比较什么
该修订的公开 DOMRect 模块将八个字段复制到普通对象中,返回四个分别命名的数组:elementClientRects、elementBoundingClientRect、rangeClientRects 和 rangeBoundingClientRect。对于两个列表 API,测试元素的采集读取列表第一项,而不是任意多行页面中的每个矩形。这项源码选择很重要:显示一个分组摘要,不代表所有元素的每个片段都被检查过。
同一模块还有优先选择矩形的辅助函数。它根据此前记录的 API 疑点,在元素列表、元素包围矩形、范围列表和范围包围矩形之间选择。这个函数与四数组采集是分开的。说“CreepJS 始终使用包围矩形”会抹掉分支结构;反过来,函数的替代路径也不表示四个 API 对任意选区都应返回相同矩形。
移动检查先保存测试元素初始 top,添加位移类,读取位移后的值,移除类,再次读取。它比较相对于位移后测量的恢复情况,并不是要求布局变化后矩形仍不变。恢复检查失败与初始位置变化,是不同的比较;仅记录“位置变了”无法区分它们。
字段运算检查比较已采集元素矩形的对边、尺寸和原点字段之间的关系。另一项检查比较两个预期匹配的特定测试元素的左右边界。二者是不同谓词:内部字段关系一致时,两个元素仍可能不同。CreepJS 信号分组分析 说明分组判断为什么必须保留作用范围,而不能变成关于某个人或所有浏览器 API 的结论。
| 源码定义的检查 | 比较对象 | 单独失败不能证明什么 |
|---|---|---|
| 位移与恢复 | 初始、位移后、恢复后的位置 | 位置差异的原因 |
| 矩形字段运算 | 边界、原点和尺寸的关系 | 哪个软件层提供了值 |
| 相同测试元素 | 两个特定元素的指定边界 | 任意页面元素都应相等 |
| 旋转测试元素 | 带引擎和 DPR 条件的源码预期 | 所有浏览器通用的几何特征 |
| 幽灵测试元素 | 记录的八个字段是否全部为零 | 所有不可见元素都必须是零几何 |
旋转分支依赖该修订的引擎分类,其中一个分支还依赖 devicePixelRatio。预期值属于具体实现的假设,不是标准符合性表。将一个已知摘要复制到报告里,既不能提供底层矩形,也不能证明原始布局被测量过。可支持的结论只是该修订将特定测试元素与有条件的预期比较,而不是不匹配就能识别操作系统。
lied 还可能在这些几何检查前,继承关于四个几何 API 或 String.fromCodePoint 的既有疑点。因此,最终为真不能确定哪个本地谓词最先改变它。模块捕获失败、记录测试未通过并且不返回结果;渲染函数在报告缺失时单独展示阻止状态。这与成功采集到含零数组是不同路径。这里没有声称实际运行过 CreepJS。
会改变比较的布局条件
内容相对于视口移动时,视口坐标会变化。MDN 在解释 getBoundingClientRect() 时明确提到滚动。文档滚动前后的比较已经改变了输入,即使元素 CSS 宽度没有变化。加上滚动偏移可以计算文档坐标,但应明确标记为派生值,而不能悄悄与原始视口坐标比较。
换行会改变片段边界。同一个文本区段在窄列与宽列中可以具有不同的 DOMRectList.length。包围矩形汇总片段,可能包含没有任何一行文本占据的空间。只比较包围范围会掩盖片段变化,只看第一片段又可能漏掉后续所有行。两者都不能证明字体被替换。
CSS 变换会影响几何 API 描述的矩形。因此,旋转测试元素应在相同旋转角度和参考原点下比较,而不是与未旋转 CSS 尺寸比较。包围矩形仍是外部范围,不是可见多边形的轮廓。这就是旋转专用检查需要测试上下文的原因。没有额外几何运算时,测得宽度不能直接当作旋转边的长度。
Range 选区增加了另一项输入:选中的节点或文本偏移。将整个元素改选为一个子串,可以合理地改变范围列表,同时元素自身的盒保持不变。CSSOM View 范围算法对选中文本与元素盒也有不同处理。报告只写 Range.getClientRects() 却不保留选区,就不足以精确比较两次“同一页面”的采集。
字体配置属于布局上下文,但矩形变化并不是一次字体名称查询。字体可用性、替代字体、文本内容、容器宽度与样式,都可能影响指定测试元素。字体测量文档 说明 ClientRects 控制,不提供任意文本、选区和布局的参考 DOMRect。种子解决文档所述噪声的可复现性,而不是所有页面布局变化来源。
CreepJS 幽灵检查也必须绑定具体测试元素。“人眼不可见”可以包含不同渲染状态,不定义唯一 CSS 盒模型。屏幕外的元素可能仍参与布局;没有关联盒则属于另一条 API 路径。将幽灵检查推广成所有不可见元素的通用规则,会把源码具体条件换成更宽泛、缺少支持的断言。
DOMRect 报告状态矩阵
第一项判断是是否取得几何。读取程序异常、空片段列表和零尺寸返回矩形需要不同记录。把所有失败替换成零矩形,会破坏区别,让下游一致性检查看似处理了从未采集的数据。报告应用自行选择的 null 也不是原生 DOMRectList 结果。
| 观察到的状态 | 有边界的解释 | 后续比较输入 |
|---|---|---|
| 读取程序没有返回报告 | 报告缺失,原因未确定 | 采集阶段及可用错误类别 |
列表存在且 length === 0 | 该 API 调用没有返回片段 | 元素关联盒或范围选区上下文 |
| 矩形宽度或高度为零 | 已有几何,但某维度退化 | API、位置与另一维度 |
| 列表长度变化 | 片段结构不同 | 相同文本、宽度、选区与布局时机 |
| 字段运算谓词失败 | 字段不符合该项比较 | 原始字段与谓词名称 |
| 只有分组摘要变化 | 某处序列化输入不同 | 先定位数组、测试元素与字段 |
这些是根据来源构造的判断场景,并非浏览器实测结果。例如,假设先得到空列表,再得到零包围矩形,可以符合规范的空列表归约,不能证明检测器发现了修改。相反,一份声称列表读取成功却不保存列表长度的报告,也不能将该场景与序列化器自行补入的缺失值区分。
第二个假设场景是多行文本在容器变窄后产生更多片段。有用结论是布局输入与片段结构变化,不预测具体宽高,也不识别设备。第三个场景保持元素不变,只改选中的子串:范围结果改变,元素级比较却可能稳定。各场景都保留实际提出的 API 问题。
第四个场景是只有 CreepJS 总评,没有具体原因。最终 lied 不足以区分继承的几何或 String.fromCodePoint 疑点、恢复失败、字段运算失败,以及测试元素的专用预期。读取模块给出的具体诊断原因,比对布尔值猜测原因更有信息量。这不要求采集用户页面全部矩形,也不要求向第三方导出可复用指纹。
有边界的比较记录
最小记录无需保留整页文本或所有几何值,就可以回答解释问题。记录 api、不敏感的测试元素标识、选择类型、适用时的列表长度、采集状态和具名比较规则。解释 CreepJS 专用判断时加上源码修订。底层测量方法标准化,不代表检测规则也成了标准:API 算法与检测器预期属于不同层。
| 记录字段 | 对解释的影响 |
|---|---|
api | 区分 Element/Range 与列表/包围结果 |
selectionType | 区分元素选区与文本子串 |
fragmentCount | 保留空列表与分片状态 |
coordinateSpace | 标明视口坐标或派生转换 |
layoutContext | 标明相关宽度、滚动与旋转条件 |
collectionState | 区分取得结果、空结果和采集失败 |
rule 与 sourceRevision | 将判断绑定到实际谓词和代码 |
上述字段名是一份建议的支持记录结构,除明确列出的源码数组外,并不是 CreepJS 输出键。若采集在取得列表前失败,不应写 fragmentCount: 0。也不应把缺失判断替换为空字符串,否则看起来像已经执行比较但没有发现问题。数据最小化和正确表示状态,都服务于同一个诊断目标。
BotBrowser 文档中的 --bot-noise-client-rects=true 用于开启确定性噪声,默认关闭。--bot-noise-seed 用于文档所述的可复现字体度量。这些说明都不能证明 Element 与 Range 方法覆盖一致、所有检测器特定运算预期保留、匿名性或服务接受结果。那些结论需要各自证据。文本度量另有 --bot-noise-text-rects=true,不能默认将它替代 ClientRects 控制。
比较报告时,可执行顺序是先确定 API 与状态,再检查选区和布局输入是否匹配,最后检查具名谓词。如果缺少这些输入,剩下的只是原因未明的几何差异。将它归为某种操作系统、设备型号或修改机制,会超出记录支持范围。最小后续动作是复现那个具体测试元素,而不是扩展采集无关身份信号。
ClientRects 信号常见问题
ClientRects 就是 Client Hints 吗?
不是。getClientRects() 是几何方法,Client Hints 通过另一套机制传递浏览器信息。名称相似不代表请求头能证明元素布局,DOMRect 报告也不能确定实际发送了哪些请求头。
摘要变了就知道哪个矩形变了吗?
不知道。分组摘要汇总序列化输入;没有数组名、测试元素与底层字段,就无法定位差异。它可以支持进一步检查该分组,但不能独立确定变化输入及原因。
Element 与 Range 几何必须始终匹配吗?
不必须。元素盒和文本选区可以覆盖不同内容。某个测试元素中成立的关系,不能推广到任意选区、多行文本或变换后的盒。
零矩形能证明采集被阻止吗?
不能。空列表归约可能返回零矩形,退化盒又是另一个状态。CreepJS 报告缺失的展示路径单独存在。解释零之前,应保留调用到底得到列表、矩形还是根本没有报告。
旋转测试元素的预期属于网页标准吗?
不属于。固定源码使用带引擎与 DPR 条件的分支。CSSOM View 定义几何方法,不定义检测器的预期摘要。CreepJS 新修订可以修改分支,而 API 名称不变。
设置种子能保证任何环境结果都相同吗?
不能。文档所述种子针对可复现噪声。文本、布局宽度、选区、变换或坐标上下文不同,仍会改变交给几何 API 的问题。单个设置不能证明跨设备等价或检测结果。
来源
- CSSOM View:元素与范围几何
- MDN:Element.getClientRects()
- MDN:Element.getBoundingClientRect()
- MDN:Range.getClientRects()
- MDN:Range.getBoundingClientRect()
- MDN:DOMRect
- MDN:DOMRectList
- CreepJS:固定修订的 DOMRect 模块
- BotBrowser:字体测量控制
实践结论
有效的 getClientRects 指纹报告需要说明 API、所选对象和具体比较。保留空列表、退化与失败状态,将源码里的 CreepJS 规则和标准几何语义分开。若只剩摘要或布尔值,原因仍未解决。在更改浏览器设置或判断设备身份之前,先核对缺失的 API 或测试元素上下文。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界