Signal · signalsVersion 14

canvas_offscreen

The canvas scene drawn on a DOM canvas and on a main-thread OffscreenCanvas, compared byte for byte: same or differs. Reported, not in either id.

canvas_offscreen at a glance (from the library code, signalsVersion 14)
Component namecanvas_offscreen
In the visitorIdno (reported, kept out of both ids)
structuredId groupnone
Hashingplain value; a digest only when longer than 128 characters or not printable ASCII
Excluded underno browser class
Inner timeout1000 ms
Added insignalsVersion 14
Collector revision1 (reported as revision with every result)
In plain words

Web pages can draw on two kinds of canvas: the normal one on the page and an “offscreen” one that is not shown. A browser draws both with the same engine, so the same drawing gives the same pixels. Tools that fake canvas fingerprints often change only the normal one. WhiskerJS draws the same small picture on both and reports whether they match.

What it reads

The scene of the canvas component (text, emoji, blending and winding, 260 by 80 pixels), drawn again on a DOM canvas and on an OffscreenCanvas created on the main thread, both with willReadFrequently so that both take the same CPU raster path, and read with getImageData. Nothing is shown on the page, and no worker is used.

How the technique works

Step What happens Value
1 draw on both, compare the pixels byte for byte equal: same
2 they differ: draw a second pair
3 a canvas differs from its own first render per-read noise: noisy / render_mismatch
4 each canvas repeats its own render, the two still differ differs

differs is what the canvas_realm_mismatch flag reads.

Why it helps

A script or extension that patches only HTMLCanvasElement or CanvasRenderingContext2D (and not OffscreenCanvas and its context) makes the two differ; canvas-spoofing extensions often patch only those. On an honest browser the answer is always same, so the component carries no entropy: its value is the comparison.

Stability

same in every collection on an honest browser. Reported outside both ids. Added in library PR #35 (2026-10-27) at signalsVersion 14: it is outside both ids, so the ids did not change; the version moved because of the new flag.

Browser support and what blocks it

  • In the self-hosted CI browser matrix of PR #35 (23 browsers and channels on Linux, load, reload and a new context) every browser reported same in all three collections, Brave and Brave beta included (Brave farbles both kinds of canvas the same way within a session), except Playwright’s Firefox with resistFingerprinting: noisy / render_mismatch, because its canvas noise is drawn on every read.
  • unavailable / no_api without OffscreenCanvas: Safari before 16.4, Firefox before 105.
  • No browser class excludes it; the flag skips the classes that alter canvas readback themselves.

Privacy

It draws a fixed test picture twice, off screen, and keeps only the answer same or differs; the pixels never leave the library. No prompt, no request. Leave it out with collect({ exclude: ['canvas_offscreen'] }) if you do not need it; no id changes, and canvas_realm_mismatch then never fires.

How WhiskerJS handles noise and failures

Status ruleId When
ok same or differs
unavailable no_api no OffscreenCanvas
unavailable no_document no document
unavailable no_context the DOM canvas gave no 2D context or no pixels
unavailable no_offscreen_context the OffscreenCanvas gave no 2D context or no pixels
noisy render_mismatch a canvas differed from its own first render
blocked empty_image the second pair could not be read
blocked threw, security_error a call threw
excluded caller_excluded you excluded it
timed_out timeout, aborted budget or abort

Example value

"canvas_offscreen": { "status": "ok", "value": "same", "hashed": false, "durationMs": 9.8, "revision": 1 }

Questions and answers

Does canvas_offscreen use a worker?

No. The OffscreenCanvas is created on the main thread. WhiskerJS never builds a worker, because a page's security policy can block it and report the block over the network.

Is it excluded in Brave?

No. Brave farbles both kinds of canvas the same way within a session, so in the CI matrix it reported same. The canvas_realm_mismatch flag is never checked for Brave and the other browsers that alter canvas readback themselves.

Why is it not in the visitorId?

It is a yes or no answer about the page, not a device trait: same on every honest browser.