WebGL MAX parameters are a fingerprint because the browser does not make them up. MAX_TEXTURE_SIZE, MAX_VERTEX_UNIFORM_VECTORS and the other limits a page reads through getParameter come from the GPU driver, along with the shader precision formats and the list of supported extensions. A renderer string names a GPU. Those values show which GPU is really there. So Tesseract Chrome, our engine, is built to move the limits only from data collected off the device being claimed, and never to fill them in with numbers that merely look plausible.
What the WebGL MAX parameters fingerprint reads besides the renderer
A script that wants the GPU's capabilities does not have to draw anything. It creates a context and asks:
getParameter(pname)for each limit: texture size, viewport size, uniform and varying vector counts, texture units, renderbuffer size, and so ongetShaderPrecisionFormat(shader, precision)for the range and precision of each float and int type in vertex and fragment shadersgetSupportedExtensions()for the list of extensions, whose names and order vary by driver and platform
Pixelscan's collector, which we decoded, puts about sixty of these fields beside the unmasked vendor and renderer in what it calls webglMeta. Our measurements of how it uses them are in Pixelscan reads sixty WebGL fields. The short version: a profile on our Mac claiming another Apple chip failed its consistency verdict, and so did Apple M9 Ultra, a chip that does not exist. The string moved. Sixty honest values next to it did not.
That is the general shape of the problem. A renderer string is a claim. The limits are evidence, because they are produced by the thing the claim is about.
Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.
Mosaic is a desktop app
Email yourself the link and open it on your computer.
How it works: Machines that exist
What the patch does
The gpu-info patch we vendor moves UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL and stops there. Everything else answers for the real driver.
Patch 0108-webgl-capability-record adds the rest of the record, in webgl_rendering_context_base.cc, behind three switches:
--fingerprint-webgl-params=3379:16384,34930:32
--fingerprint-webgl-extensions=EXT_blend_minmax,OES_texture_float
--fingerprint-webgl-precision=35633:36338:127:127:23
The params switch is a list of pname:value pairs, where 3379 is MAX_TEXTURE_SIZE and 34930 is MAX_TEXTURE_IMAGE_UNITS as decimal enums. The precision switch is five numbers per entry: shader type, precision type, range minimum, range maximum and precision bits, so 35633:36338 is a vertex shader's HIGH_FLOAT. The format is a flat list, not JSON, because a command line is a poor place for nested quoting, and a flat list fails loudly instead of half-parsing.
Three choices in it matter more than the parsing.
There is no fallback. If a pname is not in the list, getParameter asks the driver as stock Chrome does. There is no seeded random value, no table of "typical" limits and no default for a GPU we have not recorded. A limit that no real driver reports is a louder tell than the honest number, so the code has nowhere to put one.
An empty extension list and a missing switch are different answers. An empty list is a context with no extensions, which is a real thing a device can report. A missing switch means the engine asks the driver. The code keeps those two apart on purpose.
WebGL1 and WebGL2 are covered by one hook. WebGL2RenderingContextBase inherits these entry points, so the two contexts cannot disagree with each other, which would be a contradiction of its own.
With no switch passed, the engine's behaviour is byte-identical to stock, which is what our check:stock comparison against a real Chrome asserts.
Why Mosaic does not send it
The patch is built and Mosaic does not use it. mosaicArgs(), the function that writes every engine switch for a launch, leaves all three out, and says why in its own comment: the switches "take real data, and we have none".
Sixty driver-derived fields for a device we do not own cannot be reasoned out. We know how badly hand-written GPU values go, because we wrote some. Renderer strings typed in by hand for Windows rows were wrong on 41 of 45 when we checked them against real machines, and that was one string per row. The record is about fifty times the surface. The real-device data we already sample carries one renderer string per GPU and nothing else, so the limits would have to be collected from real machines, one GPU and driver at a time.
Until they are, the honest answer is the host's own. A Mosaic profile on a Mac with an M-series chip claims that chip, and its WebGL limits, precision formats and extensions are the true ones for that chip. Everything else a site re-identifies a profile by (screen, timezone, language, canvas) is drawn per profile, and the list is on real fingerprints.
Checking your own
Open a browser console on any page and run:
const gl = document.createElement('canvas').getContext('webgl2')
gl.getParameter(gl.MAX_TEXTURE_SIZE)
gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)
gl.getSupportedExtensions().length
Then compare the result with the renderer your browser claims. If you see null for the unmasked renderer before calling getExtension('WEBGL_debug_renderer_info'), that is correct Chrome behaviour, and UNMASKED_RENDERER is null until a page asks for it explains why a spoof that answers earlier gives itself away. A tool that changes the renderer and leaves these four lines alone has changed the claim and kept the evidence.


