Signal

Screen geometry

Captures display width, height, and device pixel ratio from globalThis.screen and devicePixelRatio — contributing per-browser-session entropy to fingerprint but not to hardwareFingerprint.

Tier 1 engine src/signals/screen.ts

What it measures

The signal reads three properties from globalThis.screen and globalThis.devicePixelRatio: width, height, and devicePixelRatio. These three values are joined and hashed. The colorDepth, availWidth, availHeight, and pixelDepth properties are also collected and stored in value.engine for diagnostics, but they are explicitly excluded from the hash.

Collection is synchronous and sub-millisecond. The signal uses isFirefoxResist() to detect Firefox resistFingerprinting mode and downgrades confidence to degraded when it is active.

How it's collected

If globalThis.screen is undefined, the result is absent. Otherwise the collector reads screen.width, screen.height, and devicePixelRatio (defaulting to 1 if undefined). The three values are converted to strings and joined with the pipe delimiter, then hashed with xxHash64. The isFirefoxResist() check fires when the browser user agent contains 'firefox' (case-insensitive) and both screen.width is divisible by 200 and screen.height is divisible by 100 (both greater than zero).

availWidth, availHeight, colorDepth, and pixelDepth are read into an auxiliary object and returned in value.engine for downstream diagnostics but never enter the hash concatenation.

Confidence rules

ConfidenceTrigger
normalglobalThis.screen is present and isFirefoxResist() is false
degradedisFirefoxResist() returns true (Firefox rounds width/height to 200x100 multiples)
absentglobalThis.screen is undefined, or an unhandled exception

Why engine-bound

A 2026-05-20 cross-browser audit on an Apple M2 Pro Mac running Chrome, Safari, Firefox, and Brave revealed two independent reasons the signal cannot be treated as a cross-browser hardware signal. Safari reports colorDepth as 24 while Chromium and Firefox report 30 for the same display. Brave Shields Standard mode deterministically randomises width, height, and devicePixelRatio per eTLD+1, returning values like 1920x1080 at 1.79 or 1680x1050 at 2.0 on the same device — neither matching the actual display.

Removing colorDepth from the hash resolved the Safari divergence, but Brave farbling of the primary dimensions cannot be normalised away. The signal was reclassified as engine-bound to reflect that it provides per-browser-instance entropy rather than cross-browser device identity. It contributes to fingerprint but no longer to hardwareFingerprint.

Things worth knowing

  • devicePixelRatio changes when the user zooms in or out (Ctrl+/Ctrl-). This causes hash instability across sessions if the zoom level changes between collections. The comparison layer accounts for this via ScreenFuzzyThresholds.dprZoomTolerance.
  • screen.width and screen.height report the primary display dimensions. Secondary monitor configurations are not captured.
  • Firefox resistFingerprinting rounds screen.width and screen.height to the nearest 200x100 multiples, which breaks cross-device differentiation for screens sharing the same rounded resolution. The signal is degraded not absent because the rounded values still carry some information.
  • All property reads use null-safe access (?./) with defaults of 0 or 1 to avoid runtime errors in non-standard environments.