Safari on the Mac uses Apple’s WebKit engine. It lacks a few measurements that only Chromium-based browsers offer, such as the device memory and Client Hints, so those come back as “unavailable”. In return it has a few of its own, such as Apple Pay support and Private Click Measurement, and WhiskerJS reads the audio output latency here. Nothing is left out in a normal window. When Safari’s Advanced Fingerprinting Protection adds noise to drawings and sound (always in private browsing), WhiskerJS notices with tiny test reads and leaves those signals out.
At a glance
- Browser class:
safari(the vendor isApple Computer, Inc.);safari_afpwhen Advanced Fingerprinting Protection alters canvas or WebGL readback. - Excluded components: none in stock Safari; 5 under Advanced Fingerprinting Protection (
audio,canvas,screen_frame,screen_resolution,webgl_image, ruleIdsafari_afp). - CPU threads: a 4 or an 8 is reported as the bucket
4_8. - Unavailable by design:
device_memory,heap_limit,ua_client_hints,ua_versions,keyboard_layout. - Read only here and on iOS (and, for latency, Android):
apple_paywith a version,private_click,audio_latency. - Lab sample: Mac Safari kept one id across 2 runs at signalsVersion 1.
What works
- Safari-specific components:
apple_payreportsversion=<n>;can_pay=<1 or 0>, found by askingsupportsVersion()from 20 down;can_payisblockedwherecanMakePayments()throws (insecure or cross-origin contexts).private_clickreadsattributionSourceIdfrom a new, unattached<a>element.audio_latencyreadsAudioContext.baseLatencyand closes the context at once. - Graphics: canvas and WebGL are read as elsewhere. The
webgl_rendererstring is often generic (for exampleApple GPU), which tells less than on other browsers. hardware_concurrency: read 16 times; a 4 or an 8 is reported as4_8, because WebKit alternates between 4 and 8 for scripts on its known-fingerprinting list (Safari 26, Safari Technology Preview 254), also between page loads. Other values are exact.- Fonts, font preferences, offline audio, Math, codecs, plugins, locale, storage presence and the display preference queries.
What is unavailable or blocked
These report unavailable with ruleId no_api because Safari does not offer the API:
| Component | Missing API |
|---|---|
device_memory |
navigator.deviceMemory |
heap_limit |
performance.memory |
ua_client_hints |
navigator.userAgentData |
ua_versions |
navigator.userAgentData |
keyboard_layout |
navigator.keyboard |
Because a non-ok status counts the same in every run, these do not make the id unstable: they are simply “missing” every time. webgpu depends on the Safari and macOS version; it is unavailable where navigator.gpu is missing.
What WhiskerJS excludes and why
Nothing in stock Safari. The safari row of the exclusion table is empty.
Safari’s Advanced Fingerprinting Protection (AFP) is on in private browsing, and in all browsing where the Safari setting (or a newer Safari’s default) says so. It adds noise to canvas, WebGL and Web Audio readback and reports the window size as the screen. WhiskerJS does not read the setting: it classes a Safari as safari_afp when the canvas or WebGL known-answer probe came back altered. Then:
| Component | Why it is left out |
|---|---|
audio |
Web Audio readback gets noise |
canvas |
canvas readback gets noise |
screen_frame |
the window size is reported as the screen |
screen_resolution |
the window size is reported as the screen |
webgl_image |
WebGL readback gets noise |
All five report excluded with ruleId safari_afp. Stock Safari reads the known answers back exactly and stays safari. Wherever noise is not covered by a row, canvas, webgl_image and audio still catch it: two renders that differ give noisy / render_mismatch, and a failed known-answer probe gives noisy / seeded_noise. canvas_readback then reports altered.
Integrity flags to expect
iframe_mismatchused to fire in stock Safari and in Playwright’s WebKit build, because WebKit gives a hidden (display: none) iframe a 0 by 0 screen. Since signalsVersion 9 the screen values are compared only when the iframe reports a screen wider than 0, so this no longer happens.engine_mismatchchecks that asafariorsafari_afpclass behaves like WebKit: the printed source ofevalmust be 37 characters andnavigator.productSub20030107. A user-agent switcher can raise it.native_tamperingandprototype_liewith extensions that change browser functions.vm_rendererwhen macOS runs as a virtual machine guest (Apple Paravirtual device).ua_ch_mismatchis never checked: Safari has nonavigator.userAgentData.media_query_mismatchdoes not run for thesafari_afpclass.- Advanced Fingerprinting Protection’s noise is never a flag: it shows in
noise_profileand the excluded components.
Early lab observations
An early, small sample at signalsVersion 1, not a benchmark: on a Mac, Safari kept one id across 2 runs, different from the ids of the other three pairs. device_memory was unavailable, by design. The sample used the first collector (30 components), not the current 47.
Testing it yourself
Run the live demo in a normal Safari window and then in a private window, and compare browser_class, noise_profile and the canvas, WebGL and audio statuses. In the private window the class should say safari_afp and five components excluded, which is why the visitorId differs between the two modes. The lab mode copies one line per run. Nothing is sent anywhere.
Support table for Safari on macOS
Expected status of every component in this browser at signalsVersion 9, from the support matrix (updated 2026-10-26). Basis "code" means expected from the library code and documentation, not yet measured; "lab" means seen in the early lab sample.
| Component | Status | Basis | Note |
|---|---|---|---|
apple_pay | ok | code | version=<n>;can_pay=<1, 0 or blocked>; can_pay is blocked in insecure or cross-origin contexts. |
audio | varies | code | Excluded (safari_afp) when Advanced Fingerprinting Protection is on: always in private browsing, and in all browsing where the setting says so. Otherwise ok. |
audio_latency | ok | code | Read for browser class safari; the context is closed at once. |
browser_class | ok | code | Class safari; safari_afp when Advanced Fingerprinting Protection alters canvas or WebGL readback. |
canvas | varies | code | Excluded (safari_afp) when Advanced Fingerprinting Protection is on: always in private browsing, and in all browsing where the setting says so. Otherwise ok. |
canvas_readback | varies | code | exact, or altered with Advanced Fingerprinting Protection (always in private browsing). |
color_depth | ok | code | |
color_gamut | ok | code | |
content_blockers | ok | code | none, or the bait names a content blocker hid. |
cpu_architecture | ok | code | |
device_memory | unavailable | lab | Seen unavailable on Mac Safari in the lab sample; Safari does not offer navigator.deviceMemory. |
dynamic_range | ok | code | |
font_preferences | ok | code | |
fonts | ok | code | |
fonts_canvas | ok | code | |
forced_colors | ok | code | |
hardware_concurrency | ok | code | Read 16 times; a 4 or an 8 is reported as the bucket 4_8, because WebKit alternates between them for scripts on its known-fingerprinting list. |
heap_limit | unavailable | code | performance.memory is a Chromium API. Reports unavailable / no_api. |
intl_locale | ok | code | |
keyboard_layout | unavailable | code | No navigator.keyboard here: unavailable / no_api. |
languages | ok | code | |
math | ok | code | |
media_codecs | ok | code | |
noise_profile | ok | code | exact in a normal window; seeded for the APIs Advanced Fingerprinting Protection randomises. |
pdf_viewer | ok | code | |
permissions | ok | code | |
pixel_ratio | ok | code | |
platform | ok | code | |
plugins | ok | code | |
prefers_contrast | ok | code | |
private_click | ok | code | Safari Private Click Measurement exposes attributionSourceId. |
reduced_motion | ok | code | |
screen_frame | varies | code | Excluded (safari_afp) when Advanced Fingerprinting Protection is on: always in private browsing, and in all browsing where the setting says so. Otherwise ok. |
screen_resolution | varies | code | Excluded (safari_afp) when Advanced Fingerprinting Protection is on: always in private browsing, and in all browsing where the setting says so. Otherwise ok. |
speech_voices | ok | code | Digest of the voice list, or none; some browsers fill the list late. |
storage | ok | code | |
time_zone | ok | code | |
touch_support | ok | code | |
ua_client_hints | unavailable | code | navigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. |
ua_versions | unavailable | code | navigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. |
vendor | ok | code | |
vendor_flavors | ok | code | |
webgl_image | varies | code | Excluded (safari_afp) when Advanced Fingerprinting Protection is on: always in private browsing, and in all browsing where the setting says so. Otherwise ok. |
webgl_params | ok | code | Where WebGL 1 is available; unavailable / no_context without it. |
webgl_renderer | ok | code | Often a generic string such as Apple GPU. |
webgl_vendor | ok | code | |
webgpu | varies | code | Depends on browser version, operating system and GPU; needs a secure context. unavailable / no_api without navigator.gpu, no_context without an adapter. |
Questions and answers
Why is device_memory unavailable in Safari?
Safari does not offer navigator.deviceMemory; only Chromium-based browsers do. WhiskerJS reports it as unavailable with ruleId no_api, by design. The early lab sample showed exactly this on Mac Safari.
Does Safari's private browsing change the id?
Yes, once. Private browsing turns on Advanced Fingerprinting Protection, which adds noise to canvas, WebGL and Web Audio readback and reports the window size as the screen. WhiskerJS's known-answer probes notice the noise, class the browser safari_afp and leave audio, canvas, screen_frame, screen_resolution and webgl_image out. The id then differs from the normal window's id, but stays the same from one private session to the next.
Why does hardware_concurrency say 4_8?
WebKit gives scripts on its known-fingerprinting list a core count that alternates between 4 and 8, also between page loads. WhiskerJS reports a WebKit 4 or 8 as the one bucket 4_8 so the id does not flap. Other values are reported exactly.
Does WhiskerJS show an Apple Pay sheet?
No. It only checks whether ApplePaySession exists, the highest supported version and canMakePayments(). No payment sheet is shown and no request is made.