Skip to content
All fingerprint checksScreen & display

visualViewport agrees with innerWidth/innerHeight

Check id visual-viewport-vs-window

What an ordinary browser yields

At scale 1 the difference between innerWidth and visualViewport.width is between 0 and about 20 pixels — a scrollbar gutter and nothing else.

What a detector infers

window.innerWidth and visualViewport.width are two independent readings of one viewport, and they are related by exactly one thing: the classic scrollbar gutter, which innerWidth includes and the visual viewport excludes. That makes the GAP between them the signal, not either value. At scale 1 the difference can only be a scrollbar, because both numbers describe the same surface — one measured by the layout engine, the other reported by the compositor. The gutter is not hard-coded but MEASURED, with a private probe element, and that detail is the whole check. A scrollbar is a fixed physical widget sized from the device scale factor, which does not include page zoom, while innerWidth and visualViewport.width are both divided by the zoom factor — so the gutter's width in CSS pixels GROWS as you zoom out, roughly fifteen pixels at 100% but thirty at 50% and sixty at 25%. A fixed CSS-pixel bound therefore fails an honest Windows or Linux Chrome that is merely zoomed out, and the pinch-zoom guard never catches it because desktop page zoom leaves visualViewport.scale at exactly 1. The probe is also deliberately independent of innerWidth rather than derived from it: a rewritten innerWidth would shift both sides of a derived comparison by the same amount and cancel itself out. A viewport size rewritten in JavaScript moves one of the two and not the other, and the gap immediately stops being scrollbar-shaped. On a stock Chrome this was measured at innerWidth 1624 against visualViewport.width 1614: a ten-pixel overlay scrollbar, exactly as it should be. The check declines when the page is pinch-zoomed, because the visual viewport is the zoomed viewport by design and the two readings are then legitimately scaled apart rather than contradictory — that is the difference between a signal and a false positive. Kasada reads seven visual_viewport_* probes; this is the coherence that makes them worth reading.

How to resolve it

Do not rewrite innerWidth/innerHeight. The visual viewport is reported by the compositor and keeps describing the real surface, so patching the window properties only separates two numbers that must stay a scrollbar apart. Size the actual window instead — at launch, so both readings derive from the same surface.

How is a headless browser detected?

Read in the wild by

Kasada

Reads the visual viewport across seven probes, alongside window.inner*, giving two views of one viewport.

visual_viewport_width / _height / _scale / _offset_top / _offset_left / _page_top / _page_left

Kasada ips.js teardown — the full 427-probe list

Every attribution traces to a published artifact. See the sources and their limits.

Nearby checks in Screen & display

See all 15 checks in Screen & display
Who builds this test

Clearcote is a browser built for fingerprint coherence

It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.

This audit takes no position on how Clearcote scores on Screen & display checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.

See the other checks in Screen & display — the family visual-viewport-vs-window belongs to.