Firefox火狐指纹浏览器定制WebGPU指纹方案说明
本文档说明当前 Firefox 代码里 WebGPU 指纹方案的文件落点、改动内容和改动原因。这里记录的是 Firefox 版实现,不是之前 WebKit 方案的迁移说明。
目标
目标是让页面侧看到一组可配置、互相一致的 WebGPU 能力,而不是直接暴露真实机器或因为系统 WebGPU 不可用而显示不支持。
BrowserScan 一类站点通常会检查这些点:
navigator.gpu是否存在。canvas.getContext("webgpu")是否可用。navigator.gpu.requestAdapter()是否返回 adapter。adapter.info的vendor、architecture、device、description、subgroupMinSize、subgroupMaxSize、isFallbackAdapter。adapter.features和device.features。adapter.limits和device.limits。device.adapterInfo是否和adapter.info一致。navigator.gpu.wgslLanguageFeatures。navigator.gpu.getPreferredCanvasFormat()。
因此改动不是只把一个开关打开,而是让 WebGPU 的暴露、adapter、device、features、limits、WGSL 和 canvas format 走同一份 fp.txt 配置。
配置入口
运行 Firefox 时需要带 --fpfile,例如当前方案使用:
txt
C:\firefox\fp.txt
WebGPU 相关字段采用两种命名兼容:
- 推荐新格式:
webgpu.enabled:true、webgpu.vendor:AMD、webgpu.limits.maxTextureDimension2D:16384 - 兼容旧格式:
webgpu_enabled=1、webgpu_vendor=AMD、webgpu_max_texture_dimension_2d=16384
当前 fp.txt 中主要字段示例:
txt
webgpu.enabled:true
webgpu.vendor:AMD
webgpu.architecture:rdna2
webgpu.device:AMD Radeon RX 6800 XT
webgpu.description:ANGLE (AMD, AMD Radeon RX 6800 XT Direct3D12)
webgpu.subgroupMinSize:4
webgpu.subgroupMaxSize:128
webgpu.isFallbackAdapter:false
webgpu.preferredCanvasFormat:bgra8unorm
webgpu.features:core-features-and-limits,texture-compression-bc,shader-f16,bgra8unorm-storage
webgpu.wgslLanguageFeatures:readonly_and_readwrite_storage_textures,packed_4x8_integer_dot_product,pointer_composite_access
webgpu.limits.maxTextureDimension1D:16384
webgpu.limits.maxTextureDimension2D:16384
webgpu.limits.maxTextureDimension3D:2048
文件改动
dom/webgpu/Utility.h
新增 WebGPU 指纹读取的公共接口:
cpp
bool ReadFpFileValue(std::string_view aKey, std::string& aOutValue);
bool ReadFpFileBool(std::string_view aKey, bool* aOutValue);
bool WebGPUFpEnabled();
为什么改这里:
- WebGPU 的多个文件都要读
fp.txt,不能每个点各自写一套开关逻辑。 Utility.h已经是 WebGPU 模块公共工具头,放这里能让Instance、Adapter、IPC 回调和 canvas 类型判断复用同一套语义。
dom/webgpu/Utility.cpp
新增 --fpfile 读取和解析逻辑:
- 从命令行读取
fpfileswitch。 - 支持
key:value和key=value两种格式。 - 去掉 key/value 两侧空白。
- 读取失败时返回空配置。
WebGPUFpEnabled()支持显式开关:webgpu.enabled/webgpu_enabled。- 没有显式开关时,只要存在
webgpu.或webgpu_前缀字段,也认为 WebGPU 指纹模式启用。
为什么这么改:
- 之前 WebGPU 是否暴露完全受 Firefox pref、blocklist、后端能力影响。这样会导致 BrowserScan 显示"不支持 WebGPU"。
- 指纹模式需要独立于真实硬件可用性,只要配置文件声明 WebGPU,就能走后续合成/覆盖逻辑。
- 支持新旧 key 是为了兼容之前 WebKit 方案里的字段风格,同时符合当前 Firefox 代码里
webgpu.*的命名习惯。
dom/webgpu/Instance.cpp
改动点:
Instance::PrefEnabled():当WebGPUFpEnabled()为真时,即使dom.webgpu.enabledpref 没开,也暴露navigator.gpu。- service worker 场景同样允许由 WebGPU 指纹开关启用。
Instance::Instance():读取webgpu.wgslLanguageFeatures/webgpu_wgsl_language_features,覆盖navigator.gpu.wgslLanguageFeatures。Instance::GetPreferredCanvasFormat():读取webgpu.preferredCanvasFormat、webgpu.canvasFormat、webgpu_preferred_canvas_format,覆盖navigator.gpu.getPreferredCanvasFormat()。Instance::RequestAdapter():在 blocklist、pref 禁用、dxcompiler 不可用等检查里加入webgpuFpEnabled例外。- 请求 adapter 时把
webgpuFpEnabled传给底层 wgpu 请求流程,用于允许 no-op fallback。
为什么改这里:
Instance是页面入口,也就是navigator.gpu的背后对象。- 如果这里不放行,后面 adapter/features/limits 都不会被页面看到。
- WGSL features 和 preferred canvas format 是
navigator.gpu级别 API,不属于 adapter,因此必须在Instance里覆盖。
dom/webgpu/Adapter.cpp
这是 WebGPU 指纹的核心覆盖点。
新增/修改内容:
- 增加读取数字、布尔、字符串、limits、features 的辅助函数。
AdapterInfogetter 读取fp.txt:vendorarchitecturedevicedescriptionsubgroupMinSizesubgroupMaxSizeisFallbackAdapterwgpuName也支持由webgpu.adapterName/webgpu.description覆盖。
- 新增
CreateWebGPUFpAdapterInfo(),用于没有真实 adapter 时创建一个 synthetic adapter 信息。 - 新增
Adapter::CreateFallback(),用于requestAdapter()原本返回 null 时仍能返回一个可用的 adapter 对象。 - 构造
Adapter时调用:ApplyWebGPUFpLimits(mLimits)ApplyWebGPUFpFeatures(this, mFeatures)
requestDevice()路径里也调用:MergeWebGPUFpFeatures(this, features)ApplyWebGPUFpLimits(limits)
- fallback adapter 的
requestDevice()不再去真实后端创建 device,而是直接合成Device、queue id 和 limits/features。
为什么改这里:
- BrowserScan 会同时读取
adapter和device两边。如果只改adapter.limits,但device.limits仍是真实值,会出现交叉不一致。 - adapter info 是最明显的 GPU 指纹来源,必须由
fp.txt覆盖。 - features 必须过滤 Firefox 当前真正实现的 feature,避免配置了未实现 feature 后
requestDevice()失败。 - fallback adapter 是为了处理真实机器、blocklist、驱动、后端不满足时
requestAdapter()返回 null 的情况。
dom/webgpu/SupportedLimits.h
新增 limit override 存储:
cpp
std::array<uint64_t, kLimitCount> mLimitOverrides;
std::array<bool, kLimitCount> mHasLimitOverride;
void SetLimitOverride(Limit, uint64_t);
uint64_t GetLimitValue(Limit) const;
为什么改这里:
- WebGPU limits 的 getter 都走
SupportedLimits。 - 直接改底层
ffi::WGPULimits不够,因为有些 JS 暴露字段是合成字段,例如maxBindGroupsPlusVertexBuffers。 - 用 override 数组可以只覆盖
fp.txt配置过的字段,没有配置的字段仍保留原来的默认/真实值。
dom/webgpu/SupportedLimits.cpp
实现 GetLimitValue() 和 SetLimitOverride()。
改动语义:
- 如果某个 limit 有 fp override,就优先返回 override。
- 否则返回原始
ffi::WGPULimits中的值。
为什么改这里:
adapter.limits.xxx和device.limits.xxx都最终通过这些 getter 暴露给 JS。- 把覆盖集中在这里,可以避免每个 limit getter 单独写判断。
dom/webgpu/ipc/WebGPUChild.cpp
改动 wgpu_child_resolve_request_adapter_promise():
- 原本底层返回
aAdapterInfo == nullptr时,promise resolve 为null。 - 现在如果
WebGPUFpEnabled()为真,会创建Adapter::CreateFallback()。 - 如果页面显式请求
forceFallbackAdapter,会检查isFallbackAdapter配置,避免语义冲突。
为什么改这里:
- 很多"WebGPU 不支持"的最终表现就是
requestAdapter()resolve 为 null。 - 在这个 IPC 回调点处理,可以覆盖真实后端失败、NOOP 后端失败、blocklist 等导致的 null adapter。
dom/canvas/CanvasUtils.cpp
改动 canvas context 类型判断:
cpp
if (gfxVars::AllowWebGPU() || mozilla::webgpu::WebGPUFpEnabled()) {
if (str.EqualsLiteral("webgpu")) {
*out_type = dom::CanvasContextType::WebGPU;
return true;
}
}
为什么改这里:
- BrowserScan 会检查
document.createElement("canvas").getContext("webgpu")。 - 只暴露
navigator.gpu不够,canvas 的webgpucontext 也必须可识别。
gfx/wgpu_bindings/src/client.rs
wgpu_client_request_adapter() 增加 allow_noop_fallback 参数,并通过 IPC message 传给 server。
为什么改这里:
- C++ 层知道当前是否处于 WebGPU fp 模式,但真正选择后端是在 Rust wgpu server 侧。
- 需要把"允许 no-op fallback"的意图从 C++ 传到 Rust。
gfx/wgpu_bindings/src/server.rs
Message::RequestAdapter 增加 allow_noop_fallback 字段。
改动逻辑:
- 如果
allow_noop_fallback为真,优先尝试Backends::NOOP。 - 之后仍保留 Windows DX12/WebRender adapter、正常 backend 等原有流程。
为什么改这里:
- NOOP 后端可以在没有真实 GPU adapter 或真实 adapter 不可用时,让 WebGPU 请求流程继续走下去。
- BrowserScan 主要检查 JS 暴露面,不一定会做复杂 GPU 工作;NOOP fallback 足以支撑基础 API 指纹检查。
ruyi_webgpu_probe.py
扩展本地探针:
- 设置 WebGPU 相关 prefs。
- 使用
--fpfile=C:\firefox\fp.txt启动当前构建的 Firefox。 - 检查:
isSecureContextnavigator.gpucanvas.getContext("webgpu")requestAdapter()adapter.infoadapter.featuresadapter.limitsrequestDevice()device.featuresdevice.limitsdevice.adapterInfowgslLanguageFeaturespreferredCanvasFormat
- 后续也加入了 WebGL 和 Touch 探针,方便看 BrowserScan 相关的整体一致性。
为什么改这里:
- BrowserScan 是线上站点,结果受页面脚本变化、网络和 profile 状态影响。
- 本地 probe 能快速确认 Firefox JS 暴露面是否已经按
fp.txt输出。
当前支持的 WebGPU key
信息类:
txt
webgpu.enabled
webgpu.vendor
webgpu.architecture
webgpu.device
webgpu.description
webgpu.subgroupMinSize
webgpu.subgroupMaxSize
webgpu.isFallbackAdapter
webgpu.adapterName
集合类:
txt
webgpu.features
webgpu.wgslLanguageFeatures
canvas:
txt
webgpu.preferredCanvasFormat
webgpu.canvasFormat
limits 类推荐使用:
txt
webgpu.limits.maxTextureDimension1D
webgpu.limits.maxTextureDimension2D
webgpu.limits.maxTextureDimension3D
webgpu.limits.maxTextureArrayLayers
webgpu.limits.maxBindGroups
webgpu.limits.maxBindGroupsPlusVertexBuffers
webgpu.limits.maxBindingsPerBindGroup
webgpu.limits.maxDynamicUniformBuffersPerPipelineLayout
webgpu.limits.maxDynamicStorageBuffersPerPipelineLayout
webgpu.limits.maxSampledTexturesPerShaderStage
webgpu.limits.maxSamplersPerShaderStage
webgpu.limits.maxStorageBuffersInVertexStage
webgpu.limits.maxStorageBuffersInFragmentStage
webgpu.limits.maxStorageBuffersPerShaderStage
webgpu.limits.maxStorageTexturesInVertexStage
webgpu.limits.maxStorageTexturesInFragmentStage
webgpu.limits.maxStorageTexturesPerShaderStage
webgpu.limits.maxUniformBuffersPerShaderStage
webgpu.limits.maxUniformBufferBindingSize
webgpu.limits.maxStorageBufferBindingSize
webgpu.limits.minUniformBufferOffsetAlignment
webgpu.limits.minStorageBufferOffsetAlignment
webgpu.limits.maxVertexBuffers
webgpu.limits.maxBufferSize
webgpu.limits.maxVertexAttributes
webgpu.limits.maxVertexBufferArrayStride
webgpu.limits.maxInterStageShaderVariables
webgpu.limits.maxColorAttachments
webgpu.limits.maxColorAttachmentBytesPerSample
webgpu.limits.maxComputeWorkgroupStorageSize
webgpu.limits.maxComputeInvocationsPerWorkgroup
webgpu.limits.maxComputeWorkgroupSizeX
webgpu.limits.maxComputeWorkgroupSizeY
webgpu.limits.maxComputeWorkgroupSizeZ
webgpu.limits.maxComputeWorkgroupsPerDimension
一致性原则
WebGPU 指纹不要孤立配置。建议和 WebGL 保持同一个 GPU 画像:
webgpu.vendor与webgl.unmasked_vendor不要冲突。webgpu.device/webgpu.description与webgl.renderer不要表现成完全不同显卡。- limits 不要超过该 GPU/浏览器组合常见上限。
- features 不要配置 Firefox 当前未实现的枚举,否则高级探针可以通过
requestDevice()或 shader/resource 创建发现不一致。
已知边界
当前方案主要覆盖 BrowserScan 常见的 WebGPU JS 暴露面,不等于完整模拟真实 GPU。
仍可能被深度探测的点:
- 真实 shader 编译和 WGSL validation 行为。
- 复杂 texture/buffer/pipeline 创建行为。
- ChromeOnly 的部分
wgpu*字段仍可能来自真实后端或 fallback 默认值。 subgroups、dual-source-blending、primitive-index等 Firefox 未实现 feature 会被过滤。- NOOP fallback 可以支撑基础 API,但不能等价于真实 DX12/AMD 后端。
测试方法
编译后运行本地探针:
powershell
cd C:\firefox\firefox
python ruyi_webgpu_probe.py
重点确认:
hasNavigatorGpu为true。canvas.webgpuContextAvailable为true。adapterError为null。adapter.info与fp.txt一致。adapter.features和device.features一致。adapter.limits和device.limits一致。preferredCanvasFormat与fp.txt一致。
BrowserScan 测试时,重点看 WebGPU 是否从"不支持"变成有 adapter、features、limits 和 canvas format。