| Component name | engine_features |
|---|---|
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | signalsVersion 12 |
| Collector revision | 1 (reported as revision with every result) |
Every browser version adds new built-in features to its JavaScript engine. WhiskerJS checks which of a fixed list of 48 features exist (it only looks, it never uses them) and writes the answer as three short codes. Because each feature first shipped in a known version, the codes say which engine versions are possible.
What it reads
Presence only, in three groups (bit 0 first):
- JavaScript (24):
Array.prototype.at,Object.hasOwn,Array.prototype.findLast,Array.prototype.toSorted,String.prototype.isWellFormed,ArrayBuffer.prototype.transfer,Object.groupBy,Promise.withResolvers,Array.fromAsync,Set.prototype.union,Iterator.prototype.map,Promise.try,Atomics.pause,Error.isError,Float16Array,RegExp.escape,Uint8Array.fromBase64,Temporal,Map.prototype.getOrInsert,Iterator.concat,Math.sumPrecise,Iterator.zip,Error.captureStackTrace,Atomics.waitAsync. - Intl (8):
ListFormat,DisplayNames,Segmenter,supportedValuesOf,NumberFormat.prototype.formatRange,PluralRules.prototype.selectRange,DurationFormat,Locale.prototype.getWeekInfo. - WebAssembly (16): multi-value, sign extension, saturating float to int, bulk memory, reference types, fixed-width SIMD, threads (a shared memory), exceptions, tail calls, extended constant expressions, relaxed SIMD, typed function references, garbage collection, multiple memories, 64-bit memory, and exceptions with
exnref.
How the technique works
| Group | How a feature is found |
|---|---|
| JavaScript and Intl | a property lookup; for a function, the engine’s own Function.prototype.toString must print native code |
| WebAssembly | WebAssembly.validate() of a tiny module that uses the feature; never compiled, instantiated or run |
Each group becomes a bit mask in hex, bit i for entry i of its list: js=<hex>;intl=<hex>;wasm=<hex>. A group that cannot be read is -: no Intl, or no WebAssembly.validate (Safari Lockdown Mode, Edge’s enhanced security, V8 without a JIT). The lists only grow at the end; a longer list raises the collector’s revision.
Why it helps
Little new entropy, but it dates the engine. Each feature has a first release per engine, taken from MDN’s browser-compat-data 8.1.7, so the vector says which engine versions are possible. The version_vector_mismatch flag compares it with the engine version the user agent and the Client Hints claim, and fires when the claim is impossible: anti-detect browsers and bot frameworks often claim an engine or a version they do not run.
Stability
It changes with every browser update that adds a listed feature, and is the same between sessions in one version. Reported outside both ids. Added in library PR #33 (2026-10-27) at signalsVersion 12: 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 #33 (23 browsers and channels on Linux) the value was
js=ffffff;intl=ff;wasm=ffffin every browser (Chrome 156 to canary 158, Edge 156 to dev 158, Chromium, Brave 155 and 156, Opera, Firefox ESR 153 to Nightly 161, Playwright’s WebKit), except Opera beta, which runs Chromium 142:js=c1ffff. - A WebAssembly group of
-whereWebAssembly.validateis missing: Safari Lockdown Mode, Edge’s enhanced security mode, V8 without a JIT. unavailable/no_apionly when the window has no JavaScript built-ins at all.- No browser class excludes it.
Privacy
It reads whether built-in features exist, nothing about the user, and runs no code from them. No prompt, no request. Leave it out with collect({ exclude: ['engine_features'] }) if you do not need it; no id changes.
How WhiskerJS handles noise and failures
| Status | ruleId | When |
|---|---|---|
ok |
three bit masks, - for a group that cannot be read |
|
unavailable |
no_api |
no JavaScript built-ins in the window |
blocked |
threw, security_error |
a read threw |
excluded |
caller_excluded |
you excluded it |
timed_out |
timeout, aborted |
budget or abort |
Example value
"engine_features": { "status": "ok", "value": "js=ffffff;intl=ff;wasm=ffff", "hashed": false, "durationMs": 1.6, "revision": 1 }
Questions and answers
Does engine_features run any WebAssembly code?
No. It calls WebAssembly.validate() on tiny modules of a few bytes each, which only checks whether the engine understands them. Nothing is compiled, instantiated or run.
Can a page polyfill change the value?
No. A function counts only when the engine's own Function.prototype.toString prints it as native code, so a polyfill written in JavaScript does not count. The version_vector_mismatch check also reads the features in a clean, hidden iframe.
Why is it not in the visitorId?
Browsers add features with almost every release, so the value changes with each update. It is reported for your server, and the version_vector_mismatch flag compares it with the version the browser claims.