Signal · signalsVersion 5

browser_class

WhiskerJS's own guess at the browser family: brave, firefox_rfp, firefox, safari, chromium or other. It picks the exclusion row and joins the id.

browser_class at a glance (from the library code, signalsVersion 5)
Component namebrowser_class
In the visitorIdyes
structuredId groupbrowser (engine and build)
Hashingplain value; a digest only when longer than 128 characters or not printable ASCII
Excluded underno browser class
Inner timeout1000 ms
Added insignalsVersion 1
In plain words

Before measuring anything, WhiskerJS makes a quick guess at which kind of browser it is in: Brave, Firefox with its anti-fingerprinting mode, Firefox, Safari, a Chromium-based browser, or something else. That guess decides which signals are skipped because the browser randomises them on purpose, and it is also reported as a signal of its own. It is a guess from what the browser says about itself, not proof.

What it reads

  • navigator.brave and whether navigator.brave.isBrave is a function;
  • the user agent string (navigator.userAgent);
  • whether mozInnerScreenX exists on the window (a Firefox-only property);
  • navigator.vendor;
  • whether window.chrome exists;
  • for Firefox only, the resistFingerprinting traits: the Intl time zone, screen and avail* sizes, innerWidth and innerHeight, devicePixelRatio, screenX, screenY and navigator.webdriver.

How the technique works

The checks run in this order and the first match wins:

  1. brave: navigator.brave.isBrave is a function.
  2. firefox or firefox_rfp: the user agent contains Firefox/ followed by a number, or the window has mozInnerScreenX. It is firefox_rfp only when every one of these holds at once: the time zone is UTC, Etc/UTC or Atlantic/Reykjavik; navigator.webdriver is not true; the screen is exactly the size of the content area (innerWidth, innerHeight); the available screen equals the screen; devicePixelRatio is 1; and screenX and screenY are 0. Otherwise it is firefox.
  3. chromium: the user agent contains Chrome/ or Chromium/ followed by a number at a word boundary, or window.chrome exists and the vendor is Google Inc..
  4. safari: the vendor is Apple Computer, Inc., or the user agent has both Safari/ and Version/.
  5. other: none of the above, or something threw.

The class picks a row of the exclusion table: brave removes seven components, firefox_rfp removes nine, the others remove none.

Why it helps

The class separates engine families in one short word, and because it is in the id, a browser that changes family on the same device changes the id. Its main job is internal, though: it keeps randomised values (Brave farbling, Firefox resistFingerprinting) out of the id instead of letting them change it on every visit.

Stability

It changes only when the browser changes family, when a user switches Firefox resistFingerprinting on or off, or when a user agent switcher changes what the browser claims. The firefox_rfp test is deliberately strict: each trait alone is common in stock Firefox, and stock Firefox under automation (Playwright runs it in UTC with the screen equal to the viewport) matches several. A missed resistFingerprinting browser only leaves normalised values in; a false positive would drop nine components, seven of them from the id. That was fixed in signalsVersion 5; see What’s new.

Browser support and what blocks it

Browser Class
Chrome desktop, Chrome Android, Edge, Samsung Internet chromium (their user agents carry Chrome/)
Brave brave
Firefox firefox
Firefox with resistFingerprinting, Tor Browser firefox_rfp when every trait holds, else firefox
Safari on macOS safari
Every iOS browser (Safari, Chrome CriOS, Firefox FxiOS, Edge EdgiOS) safari: they all report the Apple vendor and carry no Chrome/ or Firefox/ token
Headless Chromium with a HeadlessChrome user agent and no window.chrome other

Nothing blocks it: the checks only read properties and never throw out of the collector.

Privacy

The class is a coarse category shared by millions of browsers. It reveals no version and no personal setting, except that firefox_rfp says the user has turned on (or uses a browser with) resistFingerprinting.

How WhiskerJS handles noise and failures

Status ruleId When
ok always when it runs: one of the six class names
excluded caller_excluded you excluded it; the exclusion table still uses the class internally
timed_out timeout, aborted budget or abort, which is unlikely for a synchronous check

If any read throws, the class falls back to other rather than failing. The value is never hashed. Related integrity checks: engine_mismatch compares the class with the printed length of eval (33 characters in V8, 37 in Gecko and WebKit) and navigator.productSub; it is not checked for class other.

Example value

From the headless test browsers in a Linux container: Firefox gave firefox, WebKit gave safari, and headless Chromium gave other (its user agent says HeadlessChrome and it has no window.chrome). A normal desktop Chrome reports chromium.

Questions and answers

Why is Chrome on my iPhone classed safari?

Every iOS browser runs on Apple's WebKit, reports the vendor Apple Computer, Inc. and has no Chrome/ token in its user agent (Chrome on iOS says CriOS). WhiskerJS follows the engine, so it is classed safari.

Why did headless Chromium come out as other?

Its user agent says HeadlessChrome, which does not match the Chrome/ token at a word boundary, and the old headless mode has no window.chrome object. Neither chromium rule matches, so the class is other.

Can I trust browser_class to tell me the real browser?

No. It is a guess from values the client controls. Use it as a feature, compare it with the User-Agent header your server sees, and look at the engine_mismatch flag.