DuckDuckGo makes its own browser for Mac, Windows, iPhone and Android. Underneath it uses the engine each system offers: Apple’s WebKit on the Mac and iPhone, Chromium on Windows and Android. To a web page it looks much like Safari or Chrome, so WhiskerJS checks for DuckDuckGo first and reports its own class, duckduckgo. Nothing is left out on purpose here; everything else works as in the browser with the same engine.
At a glance
- Browser class:
duckduckgosince signalsVersion 10 (navigator.duckduckgowith anisDuckDuckGomethod, or aDdg/orDuckDuckGo/token in the user agent). Before, the class of its engine:safarion macOS and iOS,chromiumorchromium_webviewelsewhere. - Excluded components: none. The
duckduckgorow of the exclusion table is empty. - Engine: WebKit on macOS and iOS (vendor
Apple Computer, Inc.), Chromium on Windows and Android. Engine-dependent reads and checks follow it. - On macOS and iOS, as in Safari:
device_memory,heap_limit,ua_client_hints,ua_versionsandkeyboard_layoutareunavailable/no_api; a CPU count of 4 or 8 is the bucket4_8;audio_latencyis read. - Lab runs: on one Mac, 10 of 11 manual demo runs kept one visitorId at signalsVersion 9; not yet run at signalsVersion 10 or 11. See Lab runs.
How DuckDuckGo is recognised
The class test checks for DuckDuckGo right after Brave and before everything else, because on Apple devices DuckDuckGo would otherwise match the Safari step. It looks for two things, without calling or awaiting anything:
navigator.duckduckgowith anisDuckDuckGomethod. DuckDuckGo’s open-source content-scope scripts define it.- The browser’s own token in the user agent:
Ddg/<version>on macOS,DuckDuckGo/<version>on iOS and Android.
Stock Safari, Safari with Advanced Fingerprinting Protection and look-alikes (the DuckDuckBot/ crawler, a bare window.duckduckgo, an object without the method) are not classed duckduckgo; the library’s tests cover each case. Like every class, it is a hint from values the client controls.
What works
- On macOS and iOS (WebKit) WhiskerJS reads what it reads in Safari: canvas, WebGL, offline audio, fonts, font preferences, Math, codecs, plugins, locale, screen, storage presence, permission states and the display preference queries, plus
audio_latency. Whetherapple_payandprivate_clickreport a value ornonedepends on what the app turns on. - On Windows and Android (Chromium) the Chromium-only components (
device_memory,heap_limit,ua_client_hints,ua_versions) can be read, as in Chrome. canvasandwebgl_image: since signalsVersion 10, when the first two renders differ, up to two more run within the component’s 1000 ms timeout, and the value is accepted once two in a row agree. Only a mismatch that persists isnoisy/render_mismatch.content_blockers: DuckDuckGo hides ads by default and collapses the closest empty parent of a hidden ad, so the bait container can disappear. Since signalsVersion 10 that reports every bait name, and layout is judged from a plain control element. It is outside both ids.
What is unavailable or blocked
On macOS and iOS the five Chromium-only components report unavailable / no_api, as in Safari: device_memory, heap_limit, ua_client_hints, ua_versions and keyboard_layout. A non-ok status counts the same in every run, so these do not make the id unstable. webgpu depends on the version and the system.
What WhiskerJS excludes and why
Nothing. DuckDuckGo’s help page on its web tracking protections names no API it randomises or replaces with a fixed value, so there is no evidence for a row. If DuckDuckGo adds seeded noise anyway, the known-answer probes turn canvas, webgl_image or audio into noisy / seeded_noise instead of letting the id change.
Integrity flags to expect
engine_mismatchchecks the engine behind the class, which forduckduckgois decided fromnavigator.vendorand the user agent: WebKit behaviour on Apple devices, V8 behaviour elsewhere. A user-agent switcher can raise it.ua_ch_mismatchcan only run where Client Hints exist (Windows and Android).native_tamperingandprototype_lieare designed to spot scripts that override functions. Whether DuckDuckGo’s own injected scripts trip them has not been measured.
What the lab runs showed
On one Mac (macOS 26.5.1), manual demo runs in the lab mode, a check, not a benchmark:
- signalsVersion 9: 11 runs over 6 page loads. 10 runs had one visitorId; one run had a second, because one canvas render differed from the other (
canvasnoisy/render_mismatch), which changed thegpupart. In 7 of the 11 runscontent_blockerswasunavailable/no_layout. The class wassafari, as signalsVersion 9 had no DuckDuckGo class. The visitorIds differed from those of Brave, Chrome and Firefox on the same Mac. - These two findings led to the extra renders and the content-blocker fix of signalsVersion 10.
- signalsVersion 10 and 11 (11 is the live demo’s version since 2026-10-26; it changes nothing for DuckDuckGo): not yet run. DuckDuckGo has no automation interface, so the automated local run left it out; a manual run is pending.
DuckDuckGo on Windows, iOS and Android has not been run at all.
Testing it yourself
Open the live demo in DuckDuckGo: browser_class should say duckduckgo, and the demo’s detected browser should say DuckDuckGo. Run it several times, close the window, and run again; the lab mode copies one line per run, so you can count how many visitorIds you see. Nothing is sent anywhere.
Lab runs for DuckDuckGo on macOS
Aggregated outcomes from the lab runs (updated 2026-10-26): how many runs and how many different visitorIds. A check on one device, not a benchmark.
| signalsVersion | Method | Browser | Runs | visitorIds | Note |
|---|---|---|---|---|---|
| 9 | manual demo runs, 2026-10-26 | DuckDuckGo, macOS (classed safari at signalsVersion 9) | 11 | 2 | 6 page loads; one run had a noisy canvas render (render_mismatch) that changed the gpu part; content_blockers was not ok in 7 of 11 runs |
Support table for DuckDuckGo on macOS
Expected status of every component in this browser at signalsVersion 11, 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 at signalsVersion 1; "run" means seen in every run of the automated local run at signalsVersion 10 on a Mac; "ci" means seen in the CI browser matrix at signalsVersion 10 on Linux (automated, headless).
| Component | Status | Basis | Note |
|---|---|---|---|
apple_pay | varies | code | Depends on whether the app offers ApplePaySession; none where it is missing. |
audio | ok | code | No exclusion row for duckduckgo. |
audio_latency | ok | code | Read on the WebKit engine; the context is closed at once. |
browser_class | ok | code | Class duckduckgo since signalsVersion 10 (navigator.duckduckgo with isDuckDuckGo, or a Ddg/ token in the user agent); signalsVersion 9 classed it safari. |
canvas | ok | code | Rendered again when the first two renders differ (since signalsVersion 10); only a persistent mismatch is noisy / render_mismatch. At signalsVersion 9 one of 11 manual runs had a noisy canvas render. |
canvas_readback | ok | code | exact, unless an extension adds canvas noise (then altered). |
color_depth | ok | code | |
color_gamut | ok | code | |
content_blockers | ok | code | DuckDuckGo hides ads by default, so expect bait names or none. At signalsVersion 9 it was unavailable / no_layout in 7 of 11 manual runs; fixed in signalsVersion 10, not yet rerun here. |
cpu_architecture | ok | code | |
device_memory | unavailable | code | WebKit does not offer navigator.deviceMemory. Seen unavailable / no_api in the manual runs at signalsVersion 9. |
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. Seen unavailable / no_api in the manual runs at signalsVersion 9. |
intl_locale | ok | code | |
keyboard_layout | unavailable | code | No navigator.keyboard here: unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9. |
languages | ok | code | |
math | ok | code | |
media_codecs | ok | code | |
noise_profile | ok | code | exact unless something adds noise to the test drawings. |
pdf_viewer | ok | code | |
permissions | ok | code | |
pixel_ratio | ok | code | |
platform | ok | code | |
plugins | ok | code | |
prefers_contrast | ok | code | |
private_click | varies | code | Depends on whether the app turns on Private Click Measurement; none where it is off. |
reduced_motion | ok | code | |
screen_frame | ok | code | |
screen_resolution | ok | code | |
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. Seen unavailable / no_api in the manual runs at signalsVersion 9. |
ua_versions | unavailable | code | navigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9. |
vendor | ok | code | |
vendor_flavors | ok | code | |
webgl_image | ok | code | Rendered again when the first two renders differ (since signalsVersion 10). |
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. Seen ok in every run of the automated local run at signalsVersion 10 (default settings). |
Questions and answers
Why does DuckDuckGo need its own class?
On a Mac or an iPhone the DuckDuckGo browser looks like Safari to a page: the Apple vendor and a Safari-like user agent. Until signalsVersion 9 WhiskerJS classed it safari, so a server could not tell the two apart. Since signalsVersion 10 it is classed duckduckgo, recognised by navigator.duckduckgo with an isDuckDuckGo method or by the browser's own user-agent token.
Does WhiskerJS exclude anything in DuckDuckGo?
No. DuckDuckGo's help page on its web tracking protections names no browser API it randomises, so the duckduckgo row of the exclusion table is empty. If a measurement shows otherwise, the row changes and signalsVersion goes up.
Is the id stable in DuckDuckGo?
Not measured at signalsVersion 10 or 11 yet. At signalsVersion 9, on one Mac, 11 manual demo runs gave one visitorId 10 times; the odd run had one perturbed canvas render. signalsVersion 10 renders again when two renders differ, which is meant to settle such a one-off, but a manual rerun is still pending.
Can the local real-browser script test DuckDuckGo?
No. DuckDuckGo offers no WebDriver or other automation interface, so the library's script lists it as manual. DuckDuckGo is tested by hand with the demo's lab mode.