The display geometry surface
A browser exposes two related sets of measurements. First the screen itself: screen.width/height (the full display), availWidth/availHeight (the display minus the taskbar, dock, or menu bar), colorDepth (bits of color per pixel, almost always 24), and devicePixelRatio (physical pixels per CSS pixel - 1 on a standard screen, 2 on Retina, 1.25 or 1.5 on scaled Windows). Then the window: innerWidth/innerHeight (the viewport, meaning the visible page area), outerWidth/outerHeight (including the browser's own toolbars, known as chrome), and screenX/screenY (where the window sits on the screen). Common resolutions (1920x1080, 1440x900, 390x844) are shared by many users, so resolution alone is low-entropy. But the full set together - resolution plus available area plus pixel ratio plus window size - is meaningfully identifying and, just as important, it is structured: the values have to make sense relative to each other.
Coherence rules and headless defaults
That structure is what catches bots, because the values cannot be set independently:
- Window within screen -
outerWidth/Heightcannot exceedscreen.availWidth/Height. A window bigger than the screen is impossible on real hardware. - Available area -
availHeightshould bescreen.heightminus a plausible taskbar/dock; if the two are equal (avail == full) it implies no OS bars at all, which is common in headless. - Chrome height -
outerHeight - innerHeightis the height of the browser's toolbars; zero means no chrome (headless), and the same fixed, unrealistic value across many machines is a tell. - Pixel ratio coherence - an iPhone or Retina Mac User-Agent paired with
devicePixelRatio: 1is a contradiction, because those devices are always >= 2.
Headless browsers ship with telltale default geometry - 800x600, 1280x720 with inner == outer, or 0x0 - and because those defaults are reused across an entire scraping fleet, they stand out against the natural spread of real displays.
Setting believable display metrics
The fix is to present one complete, internally consistent display profile that matches the device you claim to be: a common real resolution, an available area reduced by a realistic amount of OS chrome, a window smaller than the screen with a non-zero toolbar height, and a device pixel ratio that fits the platform. For a desktop, 1920x1080 with availHeight around 1040, dpr 1, and a windowed viewport is safe; for a specific phone, copy that model's exact metrics and pixel ratio.
As with the rest of the fingerprint, the display values must also agree with the User-Agent, Client Hints, hardware signals, and media queries (orientation, pointer type). The display is just one facet of a single coherent device identity - editing screen.width on its own, while leaving the window sizes and pixel ratio at their headless defaults, creates exactly the contradictions anti-bot systems look for.
The CSS engine as an independent witness
Display geometry is unusual among fingerprinting surfaces because the browser exposes it through two entirely separate subsystems. The screen object and the window dimensions are read by JavaScript; the same physical facts are also readable through CSS media queries, evaluated by the layout engine. Those are different code paths reaching the same underlying values, which makes display geometry one of the easiest places to run a cross-API coherence check.
The core probe is a single line: matchMedia('(device-width: ' + screen.width + 'px)') must match. If screen.width was changed by assigning to the object, CSS still reports the real display and the query fails. The same pairing works for devicePixelRatio against (resolution: Xdppx), and for screen.colorDepth against the color media feature, which expresses the same fact in bits per channel rather than bits per pixel - so the two must convert into one another.
A second family of checks reads the relationships between values rather than the values themselves. availWidth and availHeight describe the screen minus the operating system chrome, so on a desktop they are normally slightly smaller than the full dimensions - a taskbar or menu bar occupies real pixels. Values that match exactly suggest no desktop environment at all. Similarly the outer window must fit inside the screen, screen.orientation must agree with which dimension is larger, and visualViewport must reconcile with innerWidth and innerHeight.
Form-factor coherence: the display is not the only witness
Screen dimensions imply a class of device, and that implication is testable against inputs that have nothing to do with the display.
- Pointer and hover - the
pointer: fineandhover: hovermedia features describe a mouse. A desktop-sized viewport reporting a coarse pointer with no hover capability, or a mobile User-Agent reporting a fine pointer, is describing two different devices. - Touch stack -
navigator.maxTouchPointsmust agree with the pointer media features, and a mobile User-Agent should expose a working touch event stack rather than merely reporting a nonzero touch point count. - Plausibility bounds - real displays fall in a bounded range. Dimensions of a few dozen pixels, or tens of thousands, describe no shipping hardware.
- Native accessors - the
screenproperties should resolve through prototype getters, not own data properties on the instance, exactly as covered in native function integrity checking. - Event coordinates - a pointer event carries both viewport-relative and screen-relative coordinates, and the offset between them must be consistent with the reported window position and size.
Because these constraints reference each other, editing one value has a wide blast radius: a change to screen.width that is not reflected in CSS, in the available dimensions, in the window bounds, in the orientation, and in the event coordinate offsets creates several contradictions rather than one altered value. This is the general shape of the problem - display geometry is a system of related facts, and it is coherent only when set at the level where all of them derive from the same source, which is the window manager and the browser engine rather than page JavaScript.
