| Component name | content_blockers |
|---|---|
| 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 3 |
Ad blockers hide page elements whose names look like ads. WhiskerJS puts eight tiny, invisible test boxes with ad-like names on the page for a moment and checks which ones were hidden. That shows whether a blocker is active here, without loading anything. Users switch blockers on and off, so the result is reported but kept out of the identifiers.
What it reads
Whether each bait element has an offsetParent after 50 ms. The baits are 1x1 div elements with these selectors: classes ad-banner, adsbox, ad-slot, banner-ad, sponsored-ad, textAd, and ids ad-unit, ad_leaderboard. They are reported by the keys ad_banner, ad_box, ad_slot, banner_ad, sponsored, text_ad, ad_unit, ad_leaderboard.
How the technique works
- It needs a
document.body; otherwiseunavailable/no_document. - An off-screen container (
position: absolute; left: -9999px) gets the eight baits and is appended to the body. - After 50 ms (time for cosmetic filters to apply), a bait whose
offsetParentisnullcounts as hidden. - If all eight look hidden and the container itself has no
offsetParent, the page is not laid out (for exampledisplay: noneon the body):unavailable/no_layout. - The value is the keys of the hidden baits, comma-separated, or
none. - The container is removed in a
finallyblock, so it leaves nothing behind.
Why it helps
It tells your server whether cosmetic filtering is active on this page, and roughly which kind of filter list: different blockers and lists hide different subsets of the baits. It also helps explain other values, such as missing page elements.
Stability
Low to medium: users enable and disable blockers per site, and filter lists update. Outside both ids for that reason.
Browser support and what blocks it
Works in any browser with a laid-out page: Chrome, Firefox, Safari, iOS browsers, Edge. Brave has a built-in blocker, so baits may be hidden there by default. Pages that are hidden or not laid out give no_layout. On slow or emulated setups the 50 ms wait plus layout can exceed the 1000 ms inner timeout.
Privacy
It reveals whether an ad blocker hides these names on this page. No ad or filter list is loaded, nothing is requested, and the elements are removed before collect() resolves. Leave it out with collect({ exclude: ['content_blockers'] }) if you do not need it; no id changes.
How WhiskerJS handles noise and failures
| Status | ruleId | When |
|---|---|---|
ok |
none or a list of hidden bait keys |
|
unavailable |
no_document |
no document.body |
unavailable |
no_layout |
the container itself is not laid out |
blocked |
threw, security_error |
adding the elements throws |
excluded |
caller_excluded |
you excluded it |
timed_out |
timeout, aborted |
budget or abort |
Example value
In the container test, headless Chromium and Firefox reported none; headless WebKit reported timed_out / timeout (the run was slow under emulation). With a common ad blocker you might see something like ad_banner,ad_box,banner_ad.
Questions and answers
Does WhiskerJS load ads or test URLs to detect blockers?
No. It only adds eight small elements with ad-like class names and ids to the page, waits 50 ms and checks which were hidden. No request is made, and the names are WhiskerJS's own, not copied from any filter list.
What does none mean?
No bait was hidden: either no content blocker is active on this page, or the blocker does not hide these names. It is not proof that no blocker is installed.
Why is it not in the visitorId?
Users turn blockers on and off per site and filter lists update often, so the value changes too easily.