Speed — GPS native vs computed (haversine) idle
-- km/h native
coords.speed × 3.6 (Doppler, direct from GPS chip)
-- km/h computed
haversine over consecutive fixes (fallback when native = null)
-- m/s² Δv
acceleration derived from speed delta
Geolocation raw idle
latitude—
longitude—
accuracy—
altitude—
altitudeAccuracy—
heading (null when stationary)—
speed (raw m/s)—
native km/h—
haversine km/h—
Δ native − haversine—
timestamp—
fix age—
update rate—
updates0
permission state—
DeviceMotion idle
accel x / y / z—
+gravity x / y / z—
rotationRate α/β/γ—
interval—
events0
DeviceOrientation idle
alpha / beta / gamma—
absolute—
events0
Generic Sensor API idle
Accelerometer—
LinearAcceleration—
GravitySensor—
Gyroscope—
Magnetometer—
AmbientLight—
RelativeOrientation—
AbsoluteOrientation—
Network —
online—
connection type—
effectiveType—
downlink—
downlinkMax—
rtt—
saveData—
fetch latency test—
WebSocket—
WebRTC (RTCPeer)—
WebTransport—
GPU / graphics
WebGL 1—
WebGL 2—
WebGPU—
GL vendor (unmasked)—
GL renderer (unmasked)—
GL vendor (masked)—
GL renderer (masked)—
masking shape—
max texture—
rAF framerate—
OffscreenCanvas—
WebGPU vendor—
WebGPU architecture—
WebGPU device—
WebGPU description—
adapter.limits maxTextureDimension2D (hardware)—
device.limits (not queried)—
adapter.isFallbackAdapter—
The masked renderer strings are worthless as GPU identity — Chrome hardcodes "WebKit WebGL",
Firefox sanitizes, and under privacy.resistFingerprinting the debug extension
returns null — but the SHAPE of the masking identifies the engine and the privacy
configuration, which is why both halves of the pair are shown.
getContext('webgl') returning null is meaningful in its own right: Chrome 137+
desktop removed the automatic SwiftShader fallback, so null now means no usable GPU rather
than a slow software path. Adapter limits are the hardware's; a bare
requestDevice() would report spec defaults (8192) instead, so no device is
requested here and none is reported. isFallbackAdapter is documented as always
false on users' devices — probed for completeness, non-functional as a signal.
requestAdapterInfo() is never called: it was removed in Chrome 135 and its
absence serves as a version rung instead.
Performance & jank idle
rAF avg (rolling 120)—
display period (median of 120)—
frame gap p95 / p99—
gap histogram (×period)—
worst frame gap—
dropped frames (round(Δ/period)−1)0
visibilityState while measuring—
LoAF count / worst—
long tasks count / worst—
CLS (layout-shift)—
INP-ish worst event—
JS heap used—
JS heap total—
JS heap limit—
measureUserAgentSpecificMemory—
UA-specific memory needs crossOriginIsolated and a button press (it is async and gated)
The display period is measured, not assumed: 90 Hz and 120 Hz panels exist and scoring them
against a hardcoded 16.67 ms invents jank that is not there. Percentiles are reported rather
than a mean because a 250 ms stall inside an otherwise good second still averages ~55 fps and
looks healthy. Read this alongside visibilityState — a backgrounded tab is throttled to about
1 Hz and would otherwise read as catastrophic jank.
Compute Pressure idle
PressureObserver—
1 · constructor present—
2 · observe() accepted—
3 · sample actually received—
knownSources—
cpu state—
last change—
samples0
origin trial in M125, desktop only — absence here is expected on Android and in-car. The three
stages are reported separately because presence is not function: the constructor is undefined
in headless Chrome and a function in headed Chrome, and even headed it delivered no sample in
12 s on macOS. A green stage 1 with a silent stage 3 is a real and common outcome.
Audio output routing idle
AudioContext.setSinkId (M110)—
HTMLMediaElement.setSinkId—
selectAudioOutput—
audiooutput device count—
default sink id—
ctx sampleRate—
ctx state—
ctx baseLatency—
ctx outputLatency—
context values fill in after "Audio route probe" — no AudioContext is built on load, so nothing steals audio focus
Audio glitch — playback statistics idle
variant found—
read attempt—
underrun events—
underrun duration—
total duration—
dropout ratio—
latency min / avg / max—
Δ since previous read—
counters observed moving—
raw keys on the object—
Two names exist for the same quantities and they differ by 1000×. Legacy playoutStats
(fallbackFramesDuration/Events, totalFramesDuration) is in MILLISECONDS;
shipped playbackStats (underrunDuration/Events, totalDuration)
is in SECONDS. Both are probed and normalised here — durations to seconds, latencies to ms.
Counters are cumulative, so this card diffs successive reads rather than reporting an instant.
Availability is contested upstream (chromestatus lists it behind a flag at milestone 146; an
independent test found it live with no flags on 150). This page makes no support claim: it
reports only which name exists here, whether reading threw, and whether the numbers moved.
Audio clock drift idle
context state—
rate this window—
worst rate seen—
windows scored—
consecutive bad—
verdict—
constant offset (≈outputLatency)—
currentFrame gap detector—
Portable fallback for browsers with no playback statistics: the RATE at which
currentTime advances against the wall clock, measured over a sliding window.
Healthy sits at 0.999–1.003; a starved graph reads ~0.33. The absolute offset is NOT drift —
it is a constant ≈31 ms of outputLatency, shown separately and never scored.
The first window is discarded (startup reads ~0.94) and two consecutive bad windows are
required before starvation is called.
Chromium milestone ladder — feature-bracketed probing…
outputLatency → ≥102—
AudioContext.setSinkId → ≥110—
wgsl packed_4x8_integer_dot_product → ≥123—
wgsl readonly_and_readwrite_storage_textures → ≥124—
adapter.info sync attribute → ≥127—
AudioContext.onerror → ≥128—
requestAdapterInfo removed → ≥135—
isFallbackAdapter moved onto info → ≥140—
inferred lower bound—
UA-parsed version—
features vs UA—
Brackets the engine from what it can DO, then cross-checks the UA string. A disagreement is
itself the finding: it means the UA is reduced, frozen or spoofed. The bound is a floor only —
no upper limit is claimed, because a rung being absent can also mean the feature was removed.
Engine discriminators —
AudioContext.setSinkId (Chromium 110+ only)—
selectAudioOutput (Firefox 116+ only)—
getAutoplayPolicy (Firefox only)—
navigator.audioSession (Safari 16.4+ only)—
HTMLMediaElement.setSinkId (not Chrome Android)—
speaker-selection permission query—
inferred engine—
Tesla in-car browser (UA)—
QtCarBrowser (pre-2018 MCU1)—
TeslaMusic webview (separate runtime)—
Each row isolates one engine. AudioContext.setSinkId present with
HTMLMediaElement.setSinkId absent identifies Chrome Android with no UA involved.
On iOS every browser is WebKit, so audioSession present alongside a Chrome UA
means iOS Chrome, not Blink. permissions.query({name:'speaker-selection'}) throws
a TypeError in Chromium instead of returning a status — throw-vs-return is recorded, because
that difference is itself a discriminator.
Autoplay policy idle
getAutoplayPolicy (Firefox only)—
AudioContext.state at construction—
gesture had occurred by then—
observed at—
Because getAutoplayPolicy is Firefox-only, the universal oracle is the state of a
freshly constructed AudioContext: running = autoplay allowed,
suspended = blocked. It is captured on the first context this page builds (never
on load) and timestamped, because in Chromium it is not stable per-origin — the Media
Engagement Index can flip it between visits.
Display & user preferences
screen.isExtended—
colorDepth / pixelDepth—
devicePixelRatio—
orientation.type—
prefers-reduced-motion—
prefers-color-scheme—
prefers-contrast—
prefers-reduced-data—
prefers-reduced-transparency—
forced-colors—
dynamic-range—
Audio engine idle
AudioContext—
state—
sampleRate—
baseLatency—
outputLatency—
destination.maxChannelCount (addressable channels)—
AudioWorklet—
mp3 / aac / ogg / wav—
decodingInfo · supported (audio)—
decodingInfo · powerEfficient / smooth—
MediaSession API—
speechSynthesis voices—
"addressable channels" is what maxChannelCount reports — a channel budget, not a
speaker count. No web API exposes the real speaker layout, so a value of 6 does not mean six
speakers exist. On decodingInfo, only the supported column carries
information for audio-only configurations: every supported audio codec reports
powerEfficient: true in Chromium, and smooth is defined against
video frame pacing. Both are shown but neither is meaningful here.
Media / video
h264 / vp9 / av1 (mp4|webm)—
HLS native—
MediaSource (MSE)—
EME / Widevine—
getUserMedia—
audioinput devices—
videoinput devices—
audiooutput devices—
Picture-in-Picture—
Input / touch idle
maxTouchPoints—
pointer type—
active touches0
last pointer—
pressure—
keyboard events0
press a key on the car keyboard…
multi-touch test area — put fingers here
Gamepad idle
API present—
connected pads—
pad 0 id—
axes—
buttons pressed—
Hardware bridges (OBD path)
Web Bluetooth—
Web Serial—
Web USB—
Web HID—
Web NFC—
Web MIDI—
any "present" here = potential in-browser route to a CAN/OBD dongle without phone
Storage / persistence
localStorage—
sessionStorage—
IndexedDB—
Cache API—
Service Worker—
quota estimate—
usage—
storage.persisted()—
cookies enabled—
persisted survives reload?—
usageDetails (per-bucket)—
indexedDB.databases()—
usageDetails is a Chromium extension to the standard estimate and breaks the
total down per storage system — its presence alone is an engine signal, and the bucket NAMES
it returns move between Chromium versions.
Environment
secure context—
protocol—
Chromium version—
platform—
UA-CH platform—
UA-CH formFactors—
UA-CH bitness / wow64—
UA-CH architecture—
userActivation.isActive—
userActivation.hasBeenActive—
crossOriginIsolated—
languages—
CPU cores—
deviceMemory—
screen—
viewport—
DPR / colorDepth—
orientation—
battery—
webdriver flag—
timezone—
visibility—
time hidden (suspend test)0 s
WebGL parameter dump —
extension count (WebGL1)—
extension count (WebGL2)—
MAX_TEXTURE_SIZE—
MAX_VIEWPORT_DIMS—
MAX_RENDERBUFFER_SIZE—
MAX_CUBE_MAP_TEXTURE_SIZE—
MAX_VERTEX_ATTRIBS—
MAX_VARYING_VECTORS—
MAX_VERTEX_UNIFORM_VECTORS—
MAX_FRAGMENT_UNIFORM_VECTORS—
MAX_TEXTURE_IMAGE_UNITS—
MAX_COMBINED_TEXTURE_IMAGE_UNITS—
ALIASED_LINE_WIDTH_RANGE—
ALIASED_POINT_SIZE_RANGE—
MAX_ANISOTROPY (EXT)—
vertex HIGH_FLOAT (min/max/prec)—
vertex MEDIUM_FLOAT—
fragment HIGH_FLOAT—
fragment MEDIUM_FLOAT—
WebGL2 MAX_3D_TEXTURE_SIZE—
WebGL2 MAX_ARRAY_TEXTURE_LAYERS—
WebGL2 MAX_DRAW_BUFFERS—
WebGL2 MAX_SAMPLES—
WebGL2 MAX_UNIFORM_BUFFER_BINDINGS—
extension list (click)
—
—
The extension COUNT is more discriminating than any single limit: it moves with driver, GPU
family, ANGLE backend and Chromium version together. Precision formats separate mobile GPUs
that lack true high-float in fragment shaders from desktop ones that do.
WebGPU adapter detail —
adapter.features count—
wgslLanguageFeatures count—
adapter.info fields—
adapter.isFallbackAdapter—
adapter.limits reported—
device.limits—
features · wgsl · limits (click)
—
—
—
Every value here is the ADAPTER's — the hardware ceiling. No requestDevice() is
called anywhere on this page: a device reports spec defaults rather than hardware, and on
Chrome 140+ requesting one consumes the adapter. GPUSupportedLimits exposes its
values on the prototype, not as own properties, so the limits are read from a fixed name list
rather than enumerated — a name missing from that list is a limit this page does not ask for,
not a limit the adapter lacks.
Fingerprint hashes —
canvas 2D hash—
canvas 2D payload length—
WebGL render hash—
AudioContext sum—
AudioContext hash—
audio render status—
combined—
All three are promptless and silent — the audio one renders through an
OfflineAudioContext, which produces no sound and takes no audio focus. FNV-1a,
inline, no dependencies. A hash that changes between two reloads on the same device is the
finding: it means the browser is randomising the surface (Brave's farbling,
privacy.resistFingerprinting), which is far more informative than the hash itself.
Font enumeration (promptless) —
fonts probed—
detected present—
Windows-family hits—
macOS-family hits—
Linux-family hits—
Android-family hits—
web-safe hits—
inferred OS from fonts—
document.fonts.check() behaviour—
detected font list (click)
—
queryLocalFonts() is never called — it prompts. Detection is the
measureText width method: a family is present if its rendered width differs from
all three generic fallbacks. document.fonts.check() is probed too but is NOT used
as the detector: in Chromium it answers true for families that do not exist, so what it
reports is its own behaviour rather than the font set. Whether it discriminates is recorded as
its own row. The SET of families is one of the strongest OS discriminators reachable without a
permission.
Intl & locale surface
resolved locale—
timeZone—
calendar—
numberingSystem—
hourCycle / hour12—
NumberFormat resolved—
navigator.languages—
supportedValuesOf timeZone—
supportedValuesOf currency—
supportedValuesOf calendar—
UTC offset now—
UTC offset January—
UTC offset July—
DST observed—
Intl.Segmenter—
Intl.DurationFormat—
Intl.ListFormat—
Intl.RelativeTimeFormat—
The January/July offset pair reveals the DST RULE rather than the current offset, which
identifies the timezone far more tightly than the zone name alone — and disagrees loudly when
the zone name is spoofed but the underlying ICU data is not. The
supportedValuesOf counts track the bundled ICU version, which moves with the
browser build.
Crypto idle
crypto.subtle—
crypto.randomUUID—
crypto.getRandomValues—
AES-GCM generateKey—
RSA-OAEP generateKey—
ECDSA P-256 generateKey—
Ed25519 generateKey—
X25519 generateKey—
Presence of crypto.subtle says nothing about which algorithms work, so the keys
are actually generated — behind a button, because RSA-OAEP alone can cost hundreds of
milliseconds. Ed25519 and X25519 are the sharp rungs: they shipped unflagged in Chromium 137,
long after the rest, so a working Ed25519 dates the build on its own.
WebRTC detail idle
RTCPeerConnection—
gathering state—
candidates seen—
host candidates—
srflx candidates—
mDNS (.local) obfuscation—
mDNS verdict—
audio codecs (count)—
video codecs (count)—
header extensions
codec lists (click)
—
—
No STUN server is configured, so this works fully offline and reveals only host candidates —
which is the point. Whether those hosts arrive as raw private IPs or as
.local mDNS hostnames is a per-engine, per-version policy and a strong
discriminator. getCapabilities() needs no peer connection and no permission at
all; the codec list and its clock rates vary by platform build far more than the codec matrix
above suggests.
Media device breakdown —
enumerateDevices—
total devices—
audioinput—
audiooutput—
videoinput—
labels populated—
distinct groupIds—
supported constraints (count)—
constraint dictionary (click)
—
enumerateDevices() itself never prompts. Empty labels with a non-zero device
count is the un-granted state; populated labels mean camera or microphone permission has
already been given to this origin — so the label column is read as a permission probe, not as
device names. getSupportedConstraints() varies widely across platform builds and
is one of the cheapest breadth wins available.
Speech synthesis voices —
speechSynthesis—
voice count—
localService—
remote (network)—
distinct langs—
default voice—
voiceschanged fired—
voice list (click)
—
Zero voices is a real and informative result, not a failure — it is what an embedded or in-car
build with no TTS engine installed reports, and it means every speech feature in the app above
is dead on that device. The list populates asynchronously in most engines, so
voiceschanged is listened for and the card updates itself; a count that stays at
zero after the event has fired is a much stronger claim than one read once on load.
Legacy & misc discriminators
navigator.plugins length—
plugin names—
navigator.mimeTypes length—
pdfViewerEnabled—
doNotTrack—
globalPrivacyControl—
error stack first line—
stack shape → engine—
native toString shape—
0.1 + 0.2—
Math.tan(-1e300)—
Math.pow(2,1023)—
Math.sinh(1) / expm1(1)—
permissions policy—
allowedFeatures count—
performance.timeOrigin—
observed timer granularity—
granularity verdict—
allowedFeatures list (click)
—
Math.tan(-1e300) is not a stunt: the result comes from the platform's libm and
differs between V8 on glibc, V8 on Android's bionic, JavaScriptCore and SpiderMonkey, so it
separates engines that every string-based check reports as identical. The observed timer
granularity is measured by spinning performance.now() until it moves — 5 µs means
crossOriginIsolated, 100 µs is the standard Spectre-mitigated clamp, and coarser
values mean an additional privacy mode is active. navigator.plugins is a fixed
fake set in Chromium rather than a real plugin list, which is exactly why its length and
contents identify the engine.
Full API support matrix —
Deze probe leest alleen wat de browser aan een webpagina geeft. CAN-bus data (pedaalpositie, motor-RPM, echte wielsnelheid) is hier per definitie onbereikbaar — de auto geeft die niet aan webcontent. "Native speed" komt van de GPS-ontvanger via de Geolocation API; in de Tesla-browser wordt geolocation vaak op autoniveau geblokkeerd. De "computed" kolom is de fallback: snelheid berekend uit opeenvolgende posities. Groene badges = werkt op jouw firmware, rood = geblokkeerd of afwezig. Exporteer het rapport en vergelijk tussen firmware-versies.