navigator.plugins is consistent with the PDF viewer
Check id has-plugins
What an ordinary browser yields
pdfViewerEnabled=true together with a non-zero plugin count (real Chrome exposes several PDF-viewer entries), or pdfViewerEnabled=false with an empty list.
What a detector infers
An empty `navigator.plugins` is only meaningful when it contradicts something else, and this check is written to respect that. It reads `navigator.pdfViewerEnabled` first: only when that is true does it require `navigator.plugins.length > 0`. Per spec, Chrome exposes its PDF-viewer plugin entries exactly when the PDF viewer is supported, so 'pdfViewerEnabled=true with 0 plugins' is a state real Chrome cannot produce — it means someone reported the flag without materialising the plugin array behind it. When pdfViewerEnabled is false or absent, the check returns N/A rather than failing, because mobile Chrome, iOS/WebKit and policy-disabled desktop all legitimately report no viewer and no plugins.
How to resolve it
Make the two agree at the source. If you are presenting desktop Chrome, run a build with the PDF viewer enabled so the plugin entries exist naturally, rather than setting pdfViewerEnabled or the plugin list independently from JS.
Read in the wild by
Kasada
Reads the plugin list in both the page realm and a fresh iframe realm, filling separate payload slots for each.
427-probe slot map: 7:navigator_plugins(fn_235083), 248:window_plugins_list(fn_26695), 280:window_result_plugins(fn_134365), 49:iframe_plugins_list(fn_173458), 166:iframe_result_plugins(fn_182879)
PerimeterX / HUMAN
Walks navigator.plugins and ships the plugin name list verbatim (unhashed), capped at 30 entries, alongside the plugin count.
Deployed PerimeterX collector main.min.js (ifood; PerimeterX_RE/stample/ifood/source/main.min.js, mirrored at node_bridge/ifood/perimeterx/main.min.js). Collector `CC()` walks the plugin list: `for(hM[hR+-100]=0;hM[hT+260]<hU.plugins[hQ(155)]&&hM[hR+-100]<BO;hM[1]++)hM[204][hQ(hT+493)](hU[hQ(382)][hM[1]].name)` — with `BO=30`, `hU=navigator` (the file's only navigator binding), and string-table entries `hQ(382)='plugins'`, `hQ(155)='length'` (hQ_map.json). Callsite `Cq()` closes the chain: `hU[hQ(382)]&&(t[2]=hU[hQ(382)][hQ(155)],t[3]=CC()),t[0]["T386NQoaPg4="]=t[3],t[0][hQ(839)]=t[2]` — the name list goes to EV2 field `T386NQoaPg4=` and the count to the field written via hQ(839). Decoded payload (all_fields_map.json line 949, case 2026-05-19_ifood): key `T386NQoaPg4=`, via "plain", val `["PDF Viewer","Chrome PDF Viewer","Chrom…`; the count field carries val "5", matching Chrome's five PDF-viewer plugins.
CreepJS
CreepJS, an open-source fingerprinting research demo rather than a production detector, counts an empty `navigator.plugins` on a Blink runtime among its "like headless" indicators, alongside an empty `navigator.mimeTypes` list.
src/headless/index.ts, `likeHeadless` block: `noPlugins: IS_BLINK && navigator.plugins.length === 0`, `noMimeTypes: IS_BLINK && mimeTypes.length === 0` (where `mimeTypes = Object.keys({ ...navigator.mimeTypes })`)
Every attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Environment & locale
- Intl timezone resolves
timezone-presentThis check reads Intl.DateTimeFormat().resolvedOptions().timeZone and records it alongside -new Date().getTimezoneOffset() and… - timezone is not UTC
timezone-not-utcThe check resolves the Intl zone and fails only when it is exactly 'UTC' or 'Etc/UTC'. - IANA zone offset matches the JS clock offset (incl. across DST)
timezone-offset-coherenceThis is a two-source cross-check on the same fact. It takes the resolved IANA zone, formats the current instant through Intl.DateTimeFormat… - navigator.pdfViewerEnabled is true
pdf-viewerA direct read of navigator.pdfViewerEnabled. It is N/A when the property is not exposed at all, passes when true, and is marked false… - mimeTypes are consistent with navigator.plugins
plugins-mimetypesThis walks navigator.mimeTypes, and for each entry reads .enabledPlugin and checks that the exact object is present in a Set built from… - proprietary codecs (H.264 / AAC) present for a Chrome UA
codec-supportThe check calls canPlayType on a video and audio element for H.264 ('avc1.42E01E') and AAC ('mp4a.40.2'), plus MP4/WebM/Ogg for context, and… - Blink-only Web APIs present for a Chrome UA
blink-web-apisWhen the UA matches Chrome/\d+ and the page is a secure context, this probes four Blink-only surfaces: navigator.connection… - CSS feature support matches the engine (Blink for a Chrome UA)
css-engine-surfaceFor any UA containing 'Chrome/', this requires CSS.supports('-webkit-app-region', 'drag') to be true and the Gecko markers — MozAppearance…
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 Environment & locale 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 Environment & locale — the family has-plugins belongs to.