| Component name | webgl_image |
|---|---|
| In the visitorId | yes |
| structuredId group | gpu (graphics) |
| Hashing | always a digest (first 128 bits of SHA-256) |
| Excluded under | Brave (brave_farbling); Firefox resistFingerprinting and Tor Browser (firefox_rfp) |
| Inner timeout | 1000 ms |
| Added in | signalsVersion 1 |
WhiskerJS asks the graphics chip to draw one small coloured triangle with a tiny shader program and reads the resulting pixels back. Different GPUs and drivers compute colours and edges in slightly different ways, so the pixels differ between devices. The drawing is done twice: if the two results are not identical, the browser is adding noise and the value is left out.
What it reads
webgl_image uses the shared WebGL context of the collection: a detached 32 by 32 pixel canvas with a webgl (or experimental-webgl) context created with preserveDrawingBuffer: true and antialias: false. It compiles a vertex shader and a fragment shader, draws one triangle whose colour is computed per pixel from its position (with fract and sin), and reads all 32 by 32 RGBA pixels back with readPixels(). Those 4096 bytes are the value.
How the technique works
- Compile and link a minimal shader program, upload three vertices, clear the canvas to a fixed colour and draw the triangle.
- Read the pixel buffer with
readPixels(). - Draw and read again.
- Compare the two buffers byte by byte. Any difference means
noisywith ruleIdrender_mismatch. - If they match, the bytes are hashed with SHA-256 and the first 128 bits are reported.
The shader’s procedural colour depends on floating-point precision in the GPU’s fragment pipeline, and the triangle’s edges depend on the rasteriser, which is where devices tend to differ.
Why it helps
Shader arithmetic and rasterisation are implemented by the GPU, its driver and the browser’s graphics layer (for example ANGLE on many Chromium builds). Different combinations can round colours or place edge pixels differently. The same combination normally gives the same bytes. It is part of the gpu group of the structuredId.
Stability
It can change after a GPU driver update, a browser update that changes the graphics back end, switching hardware acceleration on or off, moving a window to a GPU that the browser treats differently, or a browser mode or extension that adds readback noise. A change of only the gpu structuredId part is the typical pattern; see Comparing two results.
Browser support and what blocks it
okwherever WebGL 1 is available, which includes current desktop and mobile versions of Chrome, Edge, Safari, Firefox and Samsung Internet, unless WebGL is turned off or blocked.- Brave:
excluded/brave_farbling. - Firefox resistFingerprinting and Tor Browser:
excluded/firefox_rfp. - Without WebGL (turned off, blocklisted GPU, some containers):
unavailable/no_context. In the headless Firefox test run in a container used for these pages, all four WebGL components wereunavailable/no_context.
Privacy
The image is a fixed test pattern; it contains nothing about the page or the user. It reveals graphics hardware and driver behaviour. It leaves the library only as a 32-character digest. WebGL needs no permission and WhiskerJS makes no request.
How WhiskerJS handles noise and failures
| Status | ruleId | When |
|---|---|---|
ok |
none | two identical reads; value is a digest |
noisy |
render_mismatch |
the two reads differed |
unavailable |
no_document, no_api, no_context |
no document, no getContext, or no WebGL context |
unavailable |
no_digest |
no Web Crypto to hash the bytes |
blocked |
security_error, threw |
a WebGL call threw |
excluded |
brave_farbling, firefox_rfp, caller_excluded |
exclusion table or your exclude list |
timed_out |
timeout, aborted |
1000 ms inner timeout, overall budget, abort |
Example value
From a headless Chromium test browser in a container (software rendering through SwiftShader):
"webgl_image": { "status": "ok", "value": "fc51c63a9379589055c4503f7b7a2cb6", "hashed": true }
Questions and answers
Why is webgl_image unavailable with no_context?
The browser could not create a WebGL 1 context: WebGL is turned off, the GPU is blocklisted, or the environment has no graphics support at all. The other WebGL components report the same reason because they share one context.
Does WhiskerJS keep WebGL running after collect()?
No. One WebGL context is created per collection and released with the WEBGL_lose_context extension once every collector has settled.
Why is it excluded in Brave and Firefox resistFingerprinting?
Both randomise or block WebGL readback on purpose. Reading it would only add noise, so the exclusion table skips it and reports brave_farbling or firefox_rfp.