Don't Track Me Google

Removes Google result redirect wrappers, click tracking, referrers, AMP wrappers, third-party tracking params, and telemetry beacons; with optional forced SafeSearch disable, an in-page settings panel, and console diagnostics to confirm the script is actually injected.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Don't Track Me Google
// @namespace    https://github.com/rob--w/dont-track-me-google
// @version      2.2.2
// @description  Removes Google result redirect wrappers, click tracking, referrers, AMP wrappers, third-party tracking params, and telemetry beacons; with optional forced SafeSearch disable, an in-page settings panel, and console diagnostics to confirm the script is actually injected.
// @author       KTZ
// @license      MIT
// The two lines below ("google.*" / "*.google.*") are NOT valid
// userscript-manager match patterns: @match only allows a wildcard as
// the whole host or as a "*." subdomain prefix, never at the end of a
// domain (Tampermonkey/Violentmonkey/Greasemonkey all reject or
// silently no-op a trailing "google.*"). That means the script only
// ever actually ran on the literal host "google.*", which doesn't
// exist - so, ironically, it likely wasn't matching real Google pages
// at all in most managers. Fixed by explicitly listing every Google
// country-code TLD (source: Chromium's GOOGLE_TLD_LIST) so the script
// runs on google.com, google.co.uk, google.de, ... and all of their
// subdomains (www., maps., consent., etc.).
// @match        *://google.ac/*
// @match        *://*.google.ac/*
// @match        *://google.ad/*
// @match        *://*.google.ad/*
// @match        *://google.ae/*
// @match        *://*.google.ae/*
// @match        *://google.af/*
// @match        *://*.google.af/*
// @match        *://google.ag/*
// @match        *://*.google.ag/*
// @match        *://google.al/*
// @match        *://*.google.al/*
// @match        *://google.am/*
// @match        *://*.google.am/*
// @match        *://google.as/*
// @match        *://*.google.as/*
// @match        *://google.at/*
// @match        *://*.google.at/*
// @match        *://google.aw/*
// @match        *://*.google.aw/*
// @match        *://google.az/*
// @match        *://*.google.az/*
// @match        *://google.ba/*
// @match        *://*.google.ba/*
// @match        *://google.be/*
// @match        *://*.google.be/*
// @match        *://google.bf/*
// @match        *://*.google.bf/*
// @match        *://google.bg/*
// @match        *://*.google.bg/*
// @match        *://google.bi/*
// @match        *://*.google.bi/*
// @match        *://google.biz/*
// @match        *://*.google.biz/*
// @match        *://google.bj/*
// @match        *://*.google.bj/*
// @match        *://google.bm/*
// @match        *://*.google.bm/*
// @match        *://google.bn/*
// @match        *://*.google.bn/*
// @match        *://google.bo/*
// @match        *://*.google.bo/*
// @match        *://google.bs/*
// @match        *://*.google.bs/*
// @match        *://google.bt/*
// @match        *://*.google.bt/*
// @match        *://google.by/*
// @match        *://*.google.by/*
// @match        *://google.ca/*
// @match        *://*.google.ca/*
// @match        *://google.cat/*
// @match        *://*.google.cat/*
// @match        *://google.cc/*
// @match        *://*.google.cc/*
// @match        *://google.cd/*
// @match        *://*.google.cd/*
// @match        *://google.cf/*
// @match        *://*.google.cf/*
// @match        *://google.cg/*
// @match        *://*.google.cg/*
// @match        *://google.ch/*
// @match        *://*.google.ch/*
// @match        *://google.ci/*
// @match        *://*.google.ci/*
// @match        *://google.cl/*
// @match        *://*.google.cl/*
// @match        *://google.cm/*
// @match        *://*.google.cm/*
// @match        *://google.cn/*
// @match        *://*.google.cn/*
// @match        *://google.co/*
// @match        *://*.google.co/*
// @match        *://google.co.ao/*
// @match        *://*.google.co.ao/*
// @match        *://google.co.at/*
// @match        *://*.google.co.at/*
// @match        *://google.co.bw/*
// @match        *://*.google.co.bw/*
// @match        *://google.co.ck/*
// @match        *://*.google.co.ck/*
// @match        *://google.co.cr/*
// @match        *://*.google.co.cr/*
// @match        *://google.co.gg/*
// @match        *://*.google.co.gg/*
// @match        *://google.co.gy/*
// @match        *://*.google.co.gy/*
// @match        *://google.co.hu/*
// @match        *://*.google.co.hu/*
// @match        *://google.co.id/*
// @match        *://*.google.co.id/*
// @match        *://google.co.il/*
// @match        *://*.google.co.il/*
// @match        *://google.co.im/*
// @match        *://*.google.co.im/*
// @match        *://google.co.in/*
// @match        *://*.google.co.in/*
// @match        *://google.co.je/*
// @match        *://*.google.co.je/*
// @match        *://google.co.jp/*
// @match        *://*.google.co.jp/*
// @match        *://google.co.ke/*
// @match        *://*.google.co.ke/*
// @match        *://google.co.kr/*
// @match        *://*.google.co.kr/*
// @match        *://google.co.ls/*
// @match        *://*.google.co.ls/*
// @match        *://google.co.ma/*
// @match        *://*.google.co.ma/*
// @match        *://google.co.mz/*
// @match        *://*.google.co.mz/*
// @match        *://google.co.nz/*
// @match        *://*.google.co.nz/*
// @match        *://google.co.rs/*
// @match        *://*.google.co.rs/*
// @match        *://google.co.th/*
// @match        *://*.google.co.th/*
// @match        *://google.co.tz/*
// @match        *://*.google.co.tz/*
// @match        *://google.co.ua/*
// @match        *://*.google.co.ua/*
// @match        *://google.co.ug/*
// @match        *://*.google.co.ug/*
// @match        *://google.co.uk/*
// @match        *://*.google.co.uk/*
// @match        *://google.co.uz/*
// @match        *://*.google.co.uz/*
// @match        *://google.co.ve/*
// @match        *://*.google.co.ve/*
// @match        *://google.co.vi/*
// @match        *://*.google.co.vi/*
// @match        *://google.co.za/*
// @match        *://*.google.co.za/*
// @match        *://google.co.zm/*
// @match        *://*.google.co.zm/*
// @match        *://google.co.zw/*
// @match        *://*.google.co.zw/*
// @match        *://google.com/*
// @match        *://*.google.com/*
// @match        *://google.com.af/*
// @match        *://*.google.com.af/*
// @match        *://google.com.ag/*
// @match        *://*.google.com.ag/*
// @match        *://google.com.ai/*
// @match        *://*.google.com.ai/*
// @match        *://google.com.ar/*
// @match        *://*.google.com.ar/*
// @match        *://google.com.au/*
// @match        *://*.google.com.au/*
// @match        *://google.com.az/*
// @match        *://*.google.com.az/*
// @match        *://google.com.bd/*
// @match        *://*.google.com.bd/*
// @match        *://google.com.bh/*
// @match        *://*.google.com.bh/*
// @match        *://google.com.bi/*
// @match        *://*.google.com.bi/*
// @match        *://google.com.bn/*
// @match        *://*.google.com.bn/*
// @match        *://google.com.bo/*
// @match        *://*.google.com.bo/*
// @match        *://google.com.br/*
// @match        *://*.google.com.br/*
// @match        *://google.com.by/*
// @match        *://*.google.com.by/*
// @match        *://google.com.bz/*
// @match        *://*.google.com.bz/*
// @match        *://google.com.cn/*
// @match        *://*.google.com.cn/*
// @match        *://google.com.co/*
// @match        *://*.google.com.co/*
// @match        *://google.com.cu/*
// @match        *://*.google.com.cu/*
// @match        *://google.com.cy/*
// @match        *://*.google.com.cy/*
// @match        *://google.com.do/*
// @match        *://*.google.com.do/*
// @match        *://google.com.dz/*
// @match        *://*.google.com.dz/*
// @match        *://google.com.ec/*
// @match        *://*.google.com.ec/*
// @match        *://google.com.eg/*
// @match        *://*.google.com.eg/*
// @match        *://google.com.er/*
// @match        *://*.google.com.er/*
// @match        *://google.com.et/*
// @match        *://*.google.com.et/*
// @match        *://google.com.fj/*
// @match        *://*.google.com.fj/*
// @match        *://google.com.ge/*
// @match        *://*.google.com.ge/*
// @match        *://google.com.gh/*
// @match        *://*.google.com.gh/*
// @match        *://google.com.gi/*
// @match        *://*.google.com.gi/*
// @match        *://google.com.gp/*
// @match        *://*.google.com.gp/*
// @match        *://google.com.gr/*
// @match        *://*.google.com.gr/*
// @match        *://google.com.gt/*
// @match        *://*.google.com.gt/*
// @match        *://google.com.gy/*
// @match        *://*.google.com.gy/*
// @match        *://google.com.hk/*
// @match        *://*.google.com.hk/*
// @match        *://google.com.ht/*
// @match        *://*.google.com.ht/*
// @match        *://google.com.iq/*
// @match        *://*.google.com.iq/*
// @match        *://google.com.jm/*
// @match        *://*.google.com.jm/*
// @match        *://google.com.jo/*
// @match        *://*.google.com.jo/*
// @match        *://google.com.kh/*
// @match        *://*.google.com.kh/*
// @match        *://google.com.kw/*
// @match        *://*.google.com.kw/*
// @match        *://google.com.kz/*
// @match        *://*.google.com.kz/*
// @match        *://google.com.lb/*
// @match        *://*.google.com.lb/*
// @match        *://google.com.lv/*
// @match        *://*.google.com.lv/*
// @match        *://google.com.ly/*
// @match        *://*.google.com.ly/*
// @match        *://google.com.mm/*
// @match        *://*.google.com.mm/*
// @match        *://google.com.mt/*
// @match        *://*.google.com.mt/*
// @match        *://google.com.mx/*
// @match        *://*.google.com.mx/*
// @match        *://google.com.my/*
// @match        *://*.google.com.my/*
// @match        *://google.com.na/*
// @match        *://*.google.com.na/*
// @match        *://google.com.nc/*
// @match        *://*.google.com.nc/*
// @match        *://google.com.nf/*
// @match        *://*.google.com.nf/*
// @match        *://google.com.ng/*
// @match        *://*.google.com.ng/*
// @match        *://google.com.ni/*
// @match        *://*.google.com.ni/*
// @match        *://google.com.np/*
// @match        *://*.google.com.np/*
// @match        *://google.com.nr/*
// @match        *://*.google.com.nr/*
// @match        *://google.com.om/*
// @match        *://*.google.com.om/*
// @match        *://google.com.pa/*
// @match        *://*.google.com.pa/*
// @match        *://google.com.pe/*
// @match        *://*.google.com.pe/*
// @match        *://google.com.pg/*
// @match        *://*.google.com.pg/*
// @match        *://google.com.ph/*
// @match        *://*.google.com.ph/*
// @match        *://google.com.pk/*
// @match        *://*.google.com.pk/*
// @match        *://google.com.pl/*
// @match        *://*.google.com.pl/*
// @match        *://google.com.pr/*
// @match        *://*.google.com.pr/*
// @match        *://google.com.ps/*
// @match        *://*.google.com.ps/*
// @match        *://google.com.pt/*
// @match        *://*.google.com.pt/*
// @match        *://google.com.py/*
// @match        *://*.google.com.py/*
// @match        *://google.com.qa/*
// @match        *://*.google.com.qa/*
// @match        *://google.com.ru/*
// @match        *://*.google.com.ru/*
// @match        *://google.com.sa/*
// @match        *://*.google.com.sa/*
// @match        *://google.com.sb/*
// @match        *://*.google.com.sb/*
// @match        *://google.com.sg/*
// @match        *://*.google.com.sg/*
// @match        *://google.com.sl/*
// @match        *://*.google.com.sl/*
// @match        *://google.com.sv/*
// @match        *://*.google.com.sv/*
// @match        *://google.com.tj/*
// @match        *://*.google.com.tj/*
// @match        *://google.com.tm/*
// @match        *://*.google.com.tm/*
// @match        *://google.com.tn/*
// @match        *://*.google.com.tn/*
// @match        *://google.com.tr/*
// @match        *://*.google.com.tr/*
// @match        *://google.com.tw/*
// @match        *://*.google.com.tw/*
// @match        *://google.com.ua/*
// @match        *://*.google.com.ua/*
// @match        *://google.com.uy/*
// @match        *://*.google.com.uy/*
// @match        *://google.com.vc/*
// @match        *://*.google.com.vc/*
// @match        *://google.com.ve/*
// @match        *://*.google.com.ve/*
// @match        *://google.com.vn/*
// @match        *://*.google.com.vn/*
// @match        *://google.cv/*
// @match        *://*.google.cv/*
// @match        *://google.cz/*
// @match        *://*.google.cz/*
// @match        *://google.de/*
// @match        *://*.google.de/*
// @match        *://google.dj/*
// @match        *://*.google.dj/*
// @match        *://google.dk/*
// @match        *://*.google.dk/*
// @match        *://google.dm/*
// @match        *://*.google.dm/*
// @match        *://google.do/*
// @match        *://*.google.do/*
// @match        *://google.dz/*
// @match        *://*.google.dz/*
// @match        *://google.ec/*
// @match        *://*.google.ec/*
// @match        *://google.ee/*
// @match        *://*.google.ee/*
// @match        *://google.es/*
// @match        *://*.google.es/*
// @match        *://google.eu/*
// @match        *://*.google.eu/*
// @match        *://google.fi/*
// @match        *://*.google.fi/*
// @match        *://google.fm/*
// @match        *://*.google.fm/*
// @match        *://google.fr/*
// @match        *://*.google.fr/*
// @match        *://google.ga/*
// @match        *://*.google.ga/*
// @match        *://google.gd/*
// @match        *://*.google.gd/*
// @match        *://google.ge/*
// @match        *://*.google.ge/*
// @match        *://google.gf/*
// @match        *://*.google.gf/*
// @match        *://google.gg/*
// @match        *://*.google.gg/*
// @match        *://google.gl/*
// @match        *://*.google.gl/*
// @match        *://google.gm/*
// @match        *://*.google.gm/*
// @match        *://google.gp/*
// @match        *://*.google.gp/*
// @match        *://google.gr/*
// @match        *://*.google.gr/*
// @match        *://google.gw/*
// @match        *://*.google.gw/*
// @match        *://google.gy/*
// @match        *://*.google.gy/*
// @match        *://google.hk/*
// @match        *://*.google.hk/*
// @match        *://google.hn/*
// @match        *://*.google.hn/*
// @match        *://google.hr/*
// @match        *://*.google.hr/*
// @match        *://google.ht/*
// @match        *://*.google.ht/*
// @match        *://google.hu/*
// @match        *://*.google.hu/*
// @match        *://google.ie/*
// @match        *://*.google.ie/*
// @match        *://google.im/*
// @match        *://*.google.im/*
// @match        *://google.in/*
// @match        *://*.google.in/*
// @match        *://google.info/*
// @match        *://*.google.info/*
// @match        *://google.io/*
// @match        *://*.google.io/*
// @match        *://google.iq/*
// @match        *://*.google.iq/*
// @match        *://google.is/*
// @match        *://*.google.is/*
// @match        *://google.it/*
// @match        *://*.google.it/*
// @match        *://google.it.ao/*
// @match        *://*.google.it.ao/*
// @match        *://google.je/*
// @match        *://*.google.je/*
// @match        *://google.jo/*
// @match        *://*.google.jo/*
// @match        *://google.jobs/*
// @match        *://*.google.jobs/*
// @match        *://google.jp/*
// @match        *://*.google.jp/*
// @match        *://google.kg/*
// @match        *://*.google.kg/*
// @match        *://google.ki/*
// @match        *://*.google.ki/*
// @match        *://google.km/*
// @match        *://*.google.km/*
// @match        *://google.kr/*
// @match        *://*.google.kr/*
// @match        *://google.kz/*
// @match        *://*.google.kz/*
// @match        *://google.la/*
// @match        *://*.google.la/*
// @match        *://google.li/*
// @match        *://*.google.li/*
// @match        *://google.lk/*
// @match        *://*.google.lk/*
// @match        *://google.lt/*
// @match        *://*.google.lt/*
// @match        *://google.lu/*
// @match        *://*.google.lu/*
// @match        *://google.lv/*
// @match        *://*.google.lv/*
// @match        *://google.ma/*
// @match        *://*.google.ma/*
// @match        *://google.md/*
// @match        *://*.google.md/*
// @match        *://google.me/*
// @match        *://*.google.me/*
// @match        *://google.mg/*
// @match        *://*.google.mg/*
// @match        *://google.mh/*
// @match        *://*.google.mh/*
// @match        *://google.mk/*
// @match        *://*.google.mk/*
// @match        *://google.ml/*
// @match        *://*.google.ml/*
// @match        *://google.mn/*
// @match        *://*.google.mn/*
// @match        *://google.mr/*
// @match        *://*.google.mr/*
// @match        *://google.ms/*
// @match        *://*.google.ms/*
// @match        *://google.mu/*
// @match        *://*.google.mu/*
// @match        *://google.mv/*
// @match        *://*.google.mv/*
// @match        *://google.mw/*
// @match        *://*.google.mw/*
// @match        *://google.mx/*
// @match        *://*.google.mx/*
// @match        *://google.name/*
// @match        *://*.google.name/*
// @match        *://google.ne/*
// @match        *://*.google.ne/*
// @match        *://google.ne.jp/*
// @match        *://*.google.ne.jp/*
// @match        *://google.net/*
// @match        *://*.google.net/*
// @match        *://google.net.nz/*
// @match        *://*.google.net.nz/*
// @match        *://google.ng/*
// @match        *://*.google.ng/*
// @match        *://google.nl/*
// @match        *://*.google.nl/*
// @match        *://google.no/*
// @match        *://*.google.no/*
// @match        *://google.nom.es/*
// @match        *://*.google.nom.es/*
// @match        *://google.nr/*
// @match        *://*.google.nr/*
// @match        *://google.nu/*
// @match        *://*.google.nu/*
// @match        *://google.off.ai/*
// @match        *://*.google.off.ai/*
// @match        *://google.org/*
// @match        *://*.google.org/*
// @match        *://google.org.es/*
// @match        *://*.google.org.es/*
// @match        *://google.org.nz/*
// @match        *://*.google.org.nz/*
// @match        *://google.org.uk/*
// @match        *://*.google.org.uk/*
// @match        *://google.pf/*
// @match        *://*.google.pf/*
// @match        *://google.ph/*
// @match        *://*.google.ph/*
// @match        *://google.pk/*
// @match        *://*.google.pk/*
// @match        *://google.pl/*
// @match        *://*.google.pl/*
// @match        *://google.pn/*
// @match        *://*.google.pn/*
// @match        *://google.pro/*
// @match        *://*.google.pro/*
// @match        *://google.ps/*
// @match        *://*.google.ps/*
// @match        *://google.pt/*
// @match        *://*.google.pt/*
// @match        *://google.qa/*
// @match        *://*.google.qa/*
// @match        *://google.re/*
// @match        *://*.google.re/*
// @match        *://google.ro/*
// @match        *://*.google.ro/*
// @match        *://google.rs/*
// @match        *://*.google.rs/*
// @match        *://google.ru/*
// @match        *://*.google.ru/*
// @match        *://google.rw/*
// @match        *://*.google.rw/*
// @match        *://google.sc/*
// @match        *://*.google.sc/*
// @match        *://google.se/*
// @match        *://*.google.se/*
// @match        *://google.sg/*
// @match        *://*.google.sg/*
// @match        *://google.sh/*
// @match        *://*.google.sh/*
// @match        *://google.si/*
// @match        *://*.google.si/*
// @match        *://google.sk/*
// @match        *://*.google.sk/*
// @match        *://google.sl/*
// @match        *://*.google.sl/*
// @match        *://google.sm/*
// @match        *://*.google.sm/*
// @match        *://google.sn/*
// @match        *://*.google.sn/*
// @match        *://google.so/*
// @match        *://*.google.so/*
// @match        *://google.sr/*
// @match        *://*.google.sr/*
// @match        *://google.st/*
// @match        *://*.google.st/*
// @match        *://google.sz/*
// @match        *://*.google.sz/*
// @match        *://google.td/*
// @match        *://*.google.td/*
// @match        *://google.tel/*
// @match        *://*.google.tel/*
// @match        *://google.tg/*
// @match        *://*.google.tg/*
// @match        *://google.tk/*
// @match        *://*.google.tk/*
// @match        *://google.tl/*
// @match        *://*.google.tl/*
// @match        *://google.tm/*
// @match        *://*.google.tm/*
// @match        *://google.tn/*
// @match        *://*.google.tn/*
// @match        *://google.to/*
// @match        *://*.google.to/*
// @match        *://google.tt/*
// @match        *://*.google.tt/*
// @match        *://google.tv/*
// @match        *://*.google.tv/*
// @match        *://google.tw/*
// @match        *://*.google.tw/*
// @match        *://google.ua/*
// @match        *://*.google.ua/*
// @match        *://google.us/*
// @match        *://*.google.us/*
// @match        *://google.uz/*
// @match        *://*.google.uz/*
// @match        *://google.vg/*
// @match        *://*.google.vg/*
// @match        *://google.vn/*
// @match        *://*.google.vn/*
// @match        *://google.vu/*
// @match        *://*.google.vu/*
// @match        *://google.ws/*
// @match        *://*.google.ws/*
// @match        *://google.yt/*
// @match        *://*.google.yt/*
// @run-at       document-start
// @noframes
// ==/UserScript==

(function () {
    'use strict';

    /*
     * Don't Track Me Google 2.2.0
     *
     * Design goals:
     * - No mandatory userscript-manager API (works with an implicit
     *   "@grant none" so the script always runs directly in the page's
     *   real window/document instead of a sandboxed proxy - this is
     *   required for the network-level guards below to actually see and
     *   patch the real fetch/XHR/sendBeacon/Image/anchor objects).
     * - Works with Tampermonkey, Violentmonkey, Greasemonkey and other
     *   userscript managers.
     * - Safe localStorage fallback when GM_* APIs are unavailable.
     * - In-page floating settings panel as a fallback UI for managers
     *   that don't expose GM_registerMenuCommand at all (common on
     *   several mobile userscript apps).
     * - Cleans links before Google can process most click handlers, and
     *   guards against later JS rewrites (e.g. Google Maps setting
     *   `a.href` during event bubbling).
     * - Handles dynamically inserted results, SPA navigation, and open
     *   shadow DOM subtrees.
     * - Unwraps AMP viewer URLs and strips common third-party tracking
     *   parameters (utm_*, fbclid, msclkid, ...) from destination links.
     * - Blocks known Google/ad-network telemetry beacons sent via
     *   sendBeacon, fetch, XHR, or tracking-pixel <img> requests.
     * - Forces Google SafeSearch off when enabled.
     * - Does not globally block normal Google navigation.
     *
     * Changelog:
     * 2.2.2
     *   - Fixed @match: the old "google-dot-star" and
     *     "star-dot-google-dot-star" patterns are not valid userscript
     *     match patterns (a wildcard can only be the whole host or a
     *     leading subdomain wildcard, never a trailing "domain-dot-star"),
     *     so the script was likely not actually being injected on real
     *     Google pages in most managers. Replaced with an explicit
     *     subdomain-wildcard match entry for every Google country-code
     *     TLD, which also matches the bare domain and any subdomain
     *     (www., maps., consent., images., ...).
     * 2.2.1
     *   - Added an unconditional startup banner (independent of the
     *     `debug` setting) so it's possible to confirm from the console
     *     alone whether the script file is being injected at all.
     *   - Wrapped every independent subsystem (menu, guards, observer,
     *     SPA watcher, safety sweep, panel) in its own error boundary so
     *     one failing feature can no longer silently prevent the rest -
     *     including basic link cleaning - from running.
     *   - Added an initialized guard so init() can't run twice if both
     *     DOMContentLoaded and load fire.
     *   - Exposed startup errors via DontTrackMeGoogle.getStartupErrors()
     *     for easier troubleshooting.
     * 2.2.0
     *   - Added AMP link unwrapping (unwrapAmpLinks).
     *   - Added destination tracking-parameter stripping for non-Google
     *     links (stripDestinationTrackingParams).
     *   - Added telemetry-beacon blocking across sendBeacon, fetch, XHR
     *     and tracking-pixel <img> requests (blockTrackingBeacons).
     *   - Added tracking-pixel scrubbing for pixels already in the DOM
     *     (removeTrackingPixels).
     *   - Added an aggressive `a.href` property-setter guard so late,
     *     script-driven href rewrites are cleaned too
     *     (aggressiveHrefGuard).
     *   - Added open shadow-DOM traversal and observation
     *     (cleanShadowDom).
     *   - Added data-ved / data-jsarwt / data-usg attribute scrubbing
     *     (removeTrackingAttributes).
     *   - Added consent.google.com and translate.google.com redirect
     *     unwrapping.
     *   - Added an in-page floating settings panel that works even
     *     without GM_registerMenuCommand support.
     *   - Added a lightweight stats counter exposed via the diagnostic
     *     API.
     * 2.1.1
     *   - Baseline release (redirect unwrapping, ping/inline-tracking
     *     removal, no-referrer, optional tracking-param stripping,
     *     forced SafeSearch off, MutationObserver + SPA handling).
     */

    const VERSION = '2.2.2';
    const PREFIX = '[DontTrackMeGoogle]';
    const STORAGE_KEY = 'DTMG:settings';
    const CLEAN_MARK = 'data-dtmg-cleaned';

    /*
     * Unconditional startup banner (independent of the `debug` setting).
     * If this line never appears in the console on a Google page, the
     * script itself is not being injected at all - that points to the
     * userscript manager/browser (e.g. Chrome's "Allow User Scripts"
     * permission, the script being disabled, or a stale/duplicate
     * install) rather than a bug in this file. See the "Troubleshooting"
     * section in the script's description for details.
     */
    try {
        console.info(`${PREFIX} v${VERSION} script file loaded on ${location.hostname}`);
    } catch (_) {
        // Ignore environments without a console.
    }

    const STARTUP_ERRORS = [];

    function safeCall(label, fn) {
        try {
            fn();
            return true;
        } catch (e) {
            STARTUP_ERRORS.push({ label, error: e });

            try {
                console.error(`${PREFIX} "${label}" failed - continuing without it:`, e);
            } catch (_) {
                // Ignore environments without a console.
            }

            return false;
        }
    }

    const DEFAULTS = Object.freeze({
        unwrapRedirects: true,
        removePing: true,
        removeInlineTracking: true,
        removeTrackingAttributes: true,
        noReferrer: true,
        stripGoogleTrackingParams: false,
        stripDestinationTrackingParams: true,
        unwrapAmpLinks: true,
        blockTrackingBeacons: true,
        removeTrackingPixels: true,
        cleanShadowDom: true,
        aggressiveHrefGuard: true,
        forceSafeSearchOff: true,
        floatingPanelEnabled: true,
        debug: false
    });

    let settings = loadSettings();
    let scanQueued = false;
    let scanTimer = 0;
    let lastUrl = location.href;
    let observer = null;
    let observerRoot = null;
    let safeSearchGuard = false;
    let hrefGuardInstalled = false;
    let beaconGuardInstalled = false;
    let imageGuardInstalled = false;
    let panelHost = null;
    let panelInstalled = false;
    let safetyTimer = 0;

    const observedShadowRoots =
        typeof WeakSet === 'function' ? new WeakSet() : null;

    const stats = {
        linksCleaned: 0,
        beaconsBlocked: 0,
        pixelsBlocked: 0
    };

    function log(...args) {
        if (settings.debug && console && typeof console.debug === 'function') {
            console.debug(PREFIX, ...args);
        }
    }

    function warn(...args) {
        if (console && typeof console.warn === 'function') {
            console.warn(PREFIX, ...args);
        }
    }

    /* ------------------------------------------------------------------ */
    /* Storage / compatibility                                            */
    /* ------------------------------------------------------------------ */

    function getGM(name) {
        try {
            return typeof window[name] === 'function' ? window[name] : null;
        } catch (_) {
            return null;
        }
    }

    function loadSettings() {
        const gmGet = getGM('GM_getValue');

        if (gmGet) {
            try {
                const value = gmGet(STORAGE_KEY, null);

                if (value && typeof value === 'object') {
                    return Object.assign({}, DEFAULTS, value);
                }
            } catch (e) {
                log('GM_getValue unavailable:', e);
            }
        }

        try {
            const raw = window.localStorage.getItem(STORAGE_KEY);

            if (raw) {
                const value = JSON.parse(raw);

                if (value && typeof value === 'object') {
                    return Object.assign({}, DEFAULTS, value);
                }
            }
        } catch (e) {
            log('localStorage unavailable:', e);
        }

        return Object.assign({}, DEFAULTS);
    }

    function saveSettings() {
        const gmSet = getGM('GM_setValue');

        if (gmSet) {
            try {
                gmSet(STORAGE_KEY, settings);
                return;
            } catch (e) {
                log('GM_setValue unavailable:', e);
            }
        }

        try {
            window.localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
        } catch (e) {
            log('Could not save settings:', e);
        }
    }

    function registerMenuCommand(title, callback) {
        const gmRegister = getGM('GM_registerMenuCommand');

        if (!gmRegister) {
            return false;
        }

        try {
            gmRegister(title, callback);
            return true;
        } catch (e) {
            log('GM_registerMenuCommand failed:', e);
            return false;
        }
    }

    function makeToggleCommand(label, key, afterToggle) {
        return registerMenuCommand(
            `DTMG: ${label} ${settings[key] ? 'ON' : 'OFF'}`,
            () => {
                settings[key] = !settings[key];
                saveSettings();

                if (typeof afterToggle === 'function') {
                    afterToggle();
                }

                refreshPanelInputs();
                scheduleScan(true);
            }
        );
    }

    function registerMenu() {
        let anyRegistered = false;

        anyRegistered = makeToggleCommand('Redirect unwrapping', 'unwrapRedirects') || anyRegistered;
        anyRegistered = makeToggleCommand('Remove ping', 'removePing') || anyRegistered;
        anyRegistered = makeToggleCommand('Remove inline tracking', 'removeInlineTracking') || anyRegistered;
        anyRegistered = makeToggleCommand('Remove tracking attributes', 'removeTrackingAttributes') || anyRegistered;
        anyRegistered = makeToggleCommand('No-referrer', 'noReferrer') || anyRegistered;
        anyRegistered = makeToggleCommand('Strip Google tracking params', 'stripGoogleTrackingParams') || anyRegistered;
        anyRegistered = makeToggleCommand('Strip destination tracking params', 'stripDestinationTrackingParams') || anyRegistered;
        anyRegistered = makeToggleCommand('Unwrap AMP links', 'unwrapAmpLinks') || anyRegistered;

        anyRegistered = makeToggleCommand('Block tracking beacons', 'blockTrackingBeacons', () => {
            installBeaconGuard();
            installImageBeaconGuard();
        }) || anyRegistered;

        anyRegistered = makeToggleCommand('Remove tracking pixels', 'removeTrackingPixels') || anyRegistered;
        anyRegistered = makeToggleCommand('Clean shadow DOM', 'cleanShadowDom') || anyRegistered;

        anyRegistered = makeToggleCommand('Aggressive href guard', 'aggressiveHrefGuard', () => {
            installHrefGuard();
        }) || anyRegistered;

        anyRegistered = makeToggleCommand('Force SafeSearch OFF', 'forceSafeSearchOff', () => {
            forceSafeSearchOff();
        }) || anyRegistered;

        anyRegistered = makeToggleCommand('Floating settings button', 'floatingPanelEnabled', () => {
            if (settings.floatingPanelEnabled) {
                buildPanel();
            } else {
                removePanel();
            }
        }) || anyRegistered;

        anyRegistered = makeToggleCommand('Debug', 'debug') || anyRegistered;

        anyRegistered = registerMenuCommand('DTMG: Clean now', () => scheduleScan(true)) || anyRegistered;

        anyRegistered = registerMenuCommand(`DTMG: Version ${VERSION}`, () => {
            alert(`Don't Track Me Google ${VERSION}\nCleaned: ${stats.linksCleaned} link(s)\nBlocked: ${stats.beaconsBlocked} beacon(s), ${stats.pixelsBlocked} pixel(s)`);
        }) || anyRegistered;

        return anyRegistered;
    }

    /* ------------------------------------------------------------------ */
    /* In-page floating settings panel (fallback UI)                      */
    /* ------------------------------------------------------------------ */

    const PANEL_TOGGLES = [
        { key: 'unwrapRedirects', label: 'Unwrap redirect links' },
        { key: 'unwrapAmpLinks', label: 'Unwrap AMP links' },
        { key: 'noReferrer', label: 'No-referrer on links' },
        { key: 'removePing', label: 'Remove ping tracking' },
        { key: 'removeInlineTracking', label: 'Remove inline click handlers' },
        { key: 'removeTrackingAttributes', label: 'Remove tracking attributes' },
        { key: 'stripGoogleTrackingParams', label: 'Strip Google tracking params' },
        { key: 'stripDestinationTrackingParams', label: 'Strip destination tracking params' },
        { key: 'blockTrackingBeacons', label: 'Block telemetry beacons' },
        { key: 'removeTrackingPixels', label: 'Remove tracking pixels' },
        { key: 'cleanShadowDom', label: 'Clean shadow DOM' },
        { key: 'aggressiveHrefGuard', label: 'Aggressive href guard' },
        { key: 'forceSafeSearchOff', label: 'Force SafeSearch off' },
        { key: 'debug', label: 'Debug logging' }
    ];

    let panelInputs = null;

    function refreshPanelInputs() {
        if (!panelInputs) {
            return;
        }

        for (const t of PANEL_TOGGLES) {
            const input = panelInputs[t.key];

            if (input) {
                input.checked = !!settings[t.key];
            }
        }
    }

    function buildPanel() {
        if (!settings.floatingPanelEnabled || panelInstalled || !document.documentElement) {
            return;
        }

        try {
            const host = document.createElement('div');
            host.id = 'dtmg-panel-host';
            host.style.all = 'initial';
            host.style.position = 'fixed';
            host.style.zIndex = '2147483647';
            host.style.bottom = '16px';
            host.style.right = '16px';

            const shadow = host.attachShadow({ mode: 'open' });

            const style = document.createElement('style');
            style.textContent = `
                :host { all: initial; }
                * { box-sizing: border-box; }
                .dtmg-toggle {
                    width: 36px; height: 36px; border-radius: 50%;
                    background: #1a73e8; color: #fff; border: none;
                    font: bold 10px/36px Arial, sans-serif; text-align: center;
                    cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,.35);
                }
                .dtmg-panel {
                    display: none;
                    position: absolute; bottom: 44px; right: 0;
                    width: 260px; max-height: 360px; overflow-y: auto;
                    padding: 10px 12px; border-radius: 8px;
                    background: #ffffff; color: #202124;
                    box-shadow: 0 2px 12px rgba(0,0,0,.3);
                    font: 12px/1.4 Arial, sans-serif;
                }
                .dtmg-panel.open { display: block; }
                .dtmg-panel h3 { margin: 0 0 6px; font-size: 13px; }
                .dtmg-close {
                    float: right; cursor: pointer; border: none; background: none;
                    font-size: 13px; color: #5f6368; line-height: 1;
                }
                .dtmg-row { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
                .dtmg-row label { flex: 1; cursor: pointer; }
                .dtmg-actions { margin-top: 6px; display: flex; gap: 6px; }
                .dtmg-actions button {
                    flex: 1; padding: 4px 6px; border: 1px solid #dadce0;
                    background: #f1f3f4; border-radius: 4px; cursor: pointer; font-size: 11px;
                }
                .dtmg-footer { margin-top: 6px; font-size: 10px; color: #5f6368; }
            `;

            const wrapper = document.createElement('div');
            wrapper.style.position = 'relative';

            const toggleBtn = document.createElement('button');
            toggleBtn.type = 'button';
            toggleBtn.className = 'dtmg-toggle';
            toggleBtn.title = "Don't Track Me Google settings";
            toggleBtn.textContent = 'DTMG';

            const panelEl = document.createElement('div');
            panelEl.className = 'dtmg-panel';

            const heading = document.createElement('h3');
            heading.textContent = "Don't Track Me Google";

            const closeBtn = document.createElement('button');
            closeBtn.type = 'button';
            closeBtn.className = 'dtmg-close';
            closeBtn.textContent = '✕';
            closeBtn.addEventListener('click', () => panelEl.classList.remove('open'));
            heading.appendChild(closeBtn);

            panelEl.appendChild(heading);

            panelInputs = {};

            for (const t of PANEL_TOGGLES) {
                const row = document.createElement('div');
                row.className = 'dtmg-row';

                const input = document.createElement('input');
                input.type = 'checkbox';
                input.id = `dtmg-${t.key}`;
                input.checked = !!settings[t.key];

                const label = document.createElement('label');
                label.htmlFor = input.id;
                label.textContent = t.label;

                input.addEventListener('change', () => {
                    settings[t.key] = input.checked;
                    saveSettings();

                    if (t.key === 'forceSafeSearchOff') {
                        forceSafeSearchOff();
                    }

                    if (t.key === 'blockTrackingBeacons') {
                        installBeaconGuard();
                        installImageBeaconGuard();
                    }

                    if (t.key === 'aggressiveHrefGuard') {
                        installHrefGuard();
                    }

                    scheduleScan(true);
                });

                row.appendChild(input);
                row.appendChild(label);
                panelEl.appendChild(row);

                panelInputs[t.key] = input;
            }

            const actions = document.createElement('div');
            actions.className = 'dtmg-actions';

            const cleanNowBtn = document.createElement('button');
            cleanNowBtn.type = 'button';
            cleanNowBtn.textContent = 'Clean now';
            cleanNowBtn.addEventListener('click', () => scheduleScan(true));

            const hideBtn = document.createElement('button');
            hideBtn.type = 'button';
            hideBtn.textContent = 'Hide button';
            hideBtn.addEventListener('click', () => {
                settings.floatingPanelEnabled = false;
                saveSettings();
                removePanel();
            });

            actions.appendChild(cleanNowBtn);
            actions.appendChild(hideBtn);
            panelEl.appendChild(actions);

            const footer = document.createElement('div');
            footer.className = 'dtmg-footer';
            footer.textContent = `v${VERSION} · cleaned ${stats.linksCleaned}, blocked ${stats.beaconsBlocked + stats.pixelsBlocked}`;
            panelEl.appendChild(footer);

            toggleBtn.addEventListener('click', () => {
                panelEl.classList.toggle('open');
                footer.textContent = `v${VERSION} · cleaned ${stats.linksCleaned}, blocked ${stats.beaconsBlocked + stats.pixelsBlocked}`;
            });

            wrapper.appendChild(toggleBtn);
            wrapper.appendChild(panelEl);
            shadow.appendChild(style);
            shadow.appendChild(wrapper);

            document.documentElement.appendChild(host);

            panelHost = host;
            panelInstalled = true;
        } catch (e) {
            log('Could not build floating panel:', e);
        }
    }

    function removePanel() {
        try {
            if (panelHost && panelHost.parentNode) {
                panelHost.parentNode.removeChild(panelHost);
            }
        } catch (_) {
            // Ignore removal errors.
        }

        panelHost = null;
        panelInstalled = false;
        panelInputs = null;
    }

    /* ------------------------------------------------------------------ */
    /* URL handling                                                       */
    /* ------------------------------------------------------------------ */

    const REDIRECT_PATHS = new Set([
        '/url',
        '/imgres',
        '/aclk',
        '/link',
        '/webhp',
        '/interstitial',
        '/translate',
        '/ml'
    ]);

    const TARGET_KEYS = [
        'q',
        'url',
        'u',
        'imgurl',
        'imgrefurl',
        'adurl',
        'continue',
        'destination'
    ];

    const GOOGLE_TRACKING_PARAMS = new Set([
        'gclid',
        'dclid',
        'gbraid',
        'wbraid',
        'gclsrc',
        'ved',
        'ei',
        'sxsrf',
        'iflsig',
        'uule',
        'gs_lcrp',
        'sourceid',
        'client',
        'oq',
        'aqs',
        'hl',
        'gl',
        'ictx',
        'sca_esv',
        'sca_upv'
    ]);

    const DESTINATION_TRACKING_PARAMS = new Set([
        'utm_source',
        'utm_medium',
        'utm_campaign',
        'utm_term',
        'utm_content',
        'utm_id',
        'utm_name',
        'utm_reader',
        'gclid',
        'gclsrc',
        'dclid',
        'gbraid',
        'wbraid',
        'fbclid',
        'igshid',
        'ig_rid',
        'msclkid',
        'mc_eid',
        'mc_cid',
        'yclid',
        'vero_id',
        'oly_anon_id',
        'oly_enc_id',
        '_hsenc',
        '_hsmi',
        'mkt_tok',
        'ref_src',
        'trk',
        'trkCampaign',
        'si'
    ]);

    const TRACKING_BEACON_HOSTS = new Set([
        'doubleclick.net',
        'google-analytics.com',
        'googletagmanager.com',
        'googlesyndication.com',
        'googleadservices.com'
    ]);

    const GOOGLE_BEACON_PATHS = new Set([
        '/gen_204',
        '/client_204',
        '/csi',
        '/log204',
        '/generate_204'
    ]);

    function isHttpUrl(value) {
        return /^https?:\/\//i.test(value);
    }

    function decodeRepeatedly(value, max = 3) {
        let result = value;

        for (let i = 0; i < max; i++) {
            let decoded;

            try {
                decoded = decodeURIComponent(result);
            } catch (_) {
                break;
            }

            if (decoded === result) {
                break;
            }

            result = decoded;
        }

        return result;
    }

    function isGoogleHost(hostname) {
        const host = String(hostname || '').toLowerCase().replace(/\.$/, '');

        return (
            host === 'google.com' ||
            host.endsWith('.google.com') ||
            /^google\.[a-z.]+$/.test(host) ||
            /^www\.google\.[a-z.]+$/.test(host)
        );
    }

    function isTrackingBeaconUrl(rawUrl) {
        if (!rawUrl) {
            return false;
        }

        let url;

        try {
            url = new URL(rawUrl, location.href);
        } catch (_) {
            return false;
        }

        const host = url.hostname.toLowerCase().replace(/\.$/, '');

        if (isGoogleHost(host) && GOOGLE_BEACON_PATHS.has(url.pathname)) {
            return true;
        }

        for (const trackingHost of TRACKING_BEACON_HOSTS) {
            if (host === trackingHost || host.endsWith('.' + trackingHost)) {
                return true;
            }
        }

        return false;
    }

    function extractRedirectTarget(url) {
        if (!url || !settings.unwrapRedirects) {
            return null;
        }

        let parsed;

        try {
            parsed = new URL(url, location.href);
        } catch (_) {
            return null;
        }

        if (!isHttpUrl(parsed.href)) {
            return null;
        }

        if (!isGoogleHost(parsed.hostname)) {
            return null;
        }

        if (!REDIRECT_PATHS.has(parsed.pathname)) {
            return null;
        }

        for (const key of TARGET_KEYS) {
            const raw = parsed.searchParams.get(key);

            if (!raw) {
                continue;
            }

            const candidate = decodeRepeatedly(raw).trim();

            if (!isHttpUrl(candidate)) {
                continue;
            }

            try {
                const target = new URL(candidate);

                if (target.protocol === 'http:' || target.protocol === 'https:') {
                    return target.href;
                }
            } catch (_) {
                // Ignore malformed target.
            }
        }

        return null;
    }

    function unwrapAmpUrl(url) {
        if (!settings.unwrapAmpLinks || !url) {
            return url;
        }

        let parsed;

        try {
            parsed = new URL(url, location.href);
        } catch (_) {
            return url;
        }

        if (!isGoogleHost(parsed.hostname)) {
            return url;
        }

        const secureMatch = parsed.pathname.match(/^\/amp\/s\/(.+)$/i);

        if (secureMatch) {
            try {
                return new URL('https://' + secureMatch[1] + parsed.search + parsed.hash).href;
            } catch (_) {
                return url;
            }
        }

        const plainMatch = parsed.pathname.match(/^\/amp\/(?!s\/)(.+)$/i);

        if (plainMatch) {
            try {
                return new URL('http://' + plainMatch[1] + parsed.search + parsed.hash).href;
            } catch (_) {
                return url;
            }
        }

        return url;
    }

    function stripGoogleParams(url) {
        if (!settings.stripGoogleTrackingParams || !url) {
            return url;
        }

        let parsed;

        try {
            parsed = new URL(url);
        } catch (_) {
            return url;
        }

        let changed = false;

        for (const key of Array.from(parsed.searchParams.keys())) {
            if (GOOGLE_TRACKING_PARAMS.has(key.toLowerCase())) {
                parsed.searchParams.delete(key);
                changed = true;
            }
        }

        return changed ? parsed.href : url;
    }

    function stripDestinationTrackingParams(url) {
        if (!settings.stripDestinationTrackingParams || !url) {
            return url;
        }

        let parsed;

        try {
            parsed = new URL(url);
        } catch (_) {
            return url;
        }

        if (isGoogleHost(parsed.hostname)) {
            // Google's own first-party params are handled separately by
            // stripGoogleTrackingParams, which is opt-in.
            return url;
        }

        let changed = false;

        for (const key of Array.from(parsed.searchParams.keys())) {
            if (DESTINATION_TRACKING_PARAMS.has(key)) {
                parsed.searchParams.delete(key);
                changed = true;
            }
        }

        return changed ? parsed.href : url;
    }

    /* ------------------------------------------------------------------ */
    /* SafeSearch enforcement                                             */
    /* ------------------------------------------------------------------ */

    const SAFESEARCH_KEYS = new Set(['safe', 'safesearch']);

    function forceSafeSearchOffInUrl(inputUrl) {
        if (!settings.forceSafeSearchOff || !inputUrl) {
            return inputUrl;
        }

        let url;

        try {
            url = new URL(inputUrl, location.href);
        } catch (_) {
            return inputUrl;
        }

        if (!isGoogleHost(url.hostname)) {
            return inputUrl;
        }

        let changed = false;

        for (const key of SAFESEARCH_KEYS) {
            if (url.searchParams.has(key)) {
                url.searchParams.set(key, 'off');
                changed = true;
            }
        }

        /*
         * Only add safe=off to actual Google search surfaces.
         * Do not modify the normal Google homepage unnecessarily.
         */
        if (
            url.searchParams.get('safe') === null &&
            (url.pathname === '/search' || url.pathname === '/webhp')
        ) {
            url.searchParams.set('safe', 'off');
            changed = true;
        }

        return changed ? url.href : inputUrl;
    }

    function forceSafeSearchOff() {
        if (!settings.forceSafeSearchOff || safeSearchGuard) {
            return;
        }

        safeSearchGuard = true;

        try {
            const current = new URL(location.href);

            if (isGoogleHost(current.hostname)) {
                const cleaned = forceSafeSearchOffInUrl(current.href);

                if (cleaned !== current.href) {
                    /*
                     * replaceState prevents an extra history entry.
                     */
                    history.replaceState(history.state, '', cleaned);
                    lastUrl = location.href;
                }
            }

            /*
             * Update search form fields.
             */
            const fields = document.querySelectorAll(
                'input[name="safe"], input[name="safesearch"]'
            );

            for (const field of fields) {
                if (field.value && field.value.toLowerCase() !== 'off') {
                    field.value = 'off';
                }
            }

            /*
             * Rewrite existing Google navigation links.
             */
            const links = document.querySelectorAll('a[href]');

            for (const a of links) {
                const href = a.getAttribute('href');

                if (!href) {
                    continue;
                }

                const updated = forceSafeSearchOffInUrl(href);

                if (updated !== href) {
                    a.setAttribute('href', updated);
                }
            }
        } catch (e) {
            log('SafeSearch enforcement error:', e);
        } finally {
            safeSearchGuard = false;
        }
    }

    function cleanUrl(url) {
        let result = url;

        const unwrapped = extractRedirectTarget(result);

        if (unwrapped) {
            result = unwrapped;
        }

        result = unwrapAmpUrl(result);
        result = stripGoogleParams(result);
        result = stripDestinationTrackingParams(result);
        result = forceSafeSearchOffInUrl(result);

        return result;
    }

    /* ------------------------------------------------------------------ */
    /* Link cleaning                                                      */
    /* ------------------------------------------------------------------ */

    function removeGoogleHandlers(a) {
        if (!settings.removeInlineTracking) {
            return;
        }

        const attrs = ['onmousedown', 'onmouseup', 'onclick', 'ontouchstart', 'onpointerdown'];

        for (const attr of attrs) {
            if (a.hasAttribute(attr)) {
                a.removeAttribute(attr);
            }
        }

        const jsaction = a.getAttribute('jsaction');

        if (jsaction) {
            const cleaned = jsaction
                .split(';')
                .map(part => part.trim())
                .filter(part => {
                    const lower = part.toLowerCase();

                    return !(
                        lower.startsWith('mousedown:') ||
                        lower.startsWith('click:') ||
                        lower.startsWith('touchstart:') ||
                        lower.startsWith('pointerdown:')
                    );
                })
                .join(';');

            if (cleaned !== jsaction) {
                if (cleaned) {
                    a.setAttribute('jsaction', cleaned);
                } else {
                    a.removeAttribute('jsaction');
                }
            }
        }
    }

    function removeTrackingAttributes(a) {
        if (!settings.removeTrackingAttributes) {
            return;
        }

        const attrs = ['data-ved', 'data-jsarwt', 'data-usg', 'data-cthref'];

        for (const attr of attrs) {
            if (a.hasAttribute(attr)) {
                a.removeAttribute(attr);
            }
        }
    }

    function applyReferrerPolicy(a) {
        if (!settings.noReferrer) {
            return;
        }

        try {
            a.referrerPolicy = 'no-referrer';

            const rel = new Set(
                (a.getAttribute('rel') || '')
                    .split(/\s+/)
                    .map(v => v.trim().toLowerCase())
                    .filter(Boolean)
            );

            rel.add('noreferrer');

            a.setAttribute('rel', Array.from(rel).join(' '));
        } catch (e) {
            log('Could not apply referrer policy:', e);
        }
    }

    function cleanLink(a) {
        if (!a || a.nodeType !== 1 || a.tagName !== 'A') {
            return false;
        }

        let changed = false;

        try {
            const rawHref = a.getAttribute('href');

            if (!rawHref || rawHref.startsWith('#')) {
                return false;
            }

            const marked = a.getAttribute(CLEAN_MARK) === '1';

            let absolute;

            try {
                absolute = new URL(a.href, location.href).href;
            } catch (_) {
                absolute = rawHref;
            }

            const cleaned = cleanUrl(absolute);

            if (cleaned && cleaned !== absolute && isHttpUrl(cleaned)) {
                a.setAttribute('href', cleaned);
                changed = true;
            }

            if (settings.removePing && a.hasAttribute('ping')) {
                a.removeAttribute('ping');
                changed = true;
            }

            if (settings.noReferrer) {
                applyReferrerPolicy(a);
            }

            if (settings.removeInlineTracking) {
                removeGoogleHandlers(a);
            }

            removeTrackingAttributes(a);

            if (!marked || changed) {
                a.setAttribute(CLEAN_MARK, '1');
            }

            if (changed) {
                stats.linksCleaned++;
            }

            return changed;
        } catch (e) {
            log('Error cleaning link:', e);
            return false;
        }
    }

    function scrubTrackingPixels(root) {
        if (!settings.removeTrackingPixels || !root || !root.querySelectorAll) {
            return 0;
        }

        let count = 0;

        let candidates;

        try {
            candidates = root.querySelectorAll(
                'img[src], link[rel~="preload"][href], link[rel~="prefetch"][href]'
            );
        } catch (_) {
            return 0;
        }

        for (const el of candidates) {
            const isImg = el.tagName === 'IMG';
            const src = isImg ? el.getAttribute('src') : el.getAttribute('href');

            if (!src || !isTrackingBeaconUrl(src)) {
                continue;
            }

            try {
                if (isImg) {
                    el.removeAttribute('src');
                    el.setAttribute('data-dtmg-blocked-pixel', '1');
                } else {
                    el.remove();
                }

                count++;
                stats.pixelsBlocked++;
            } catch (_) {
                // Ignore removal errors.
            }
        }

        return count;
    }

    function cleanRoot(root) {
        if (!root || !root.querySelectorAll) {
            return 0;
        }

        let count = 0;

        let links;

        try {
            links = root.querySelectorAll('a');
        } catch (_) {
            return 0;
        }

        for (const a of links) {
            if (cleanLink(a)) {
                count++;
            }
        }

        scrubTrackingPixels(root);

        return count;
    }

    function forEachShadowRoot(root, callback, seen) {
        seen = seen || new Set();

        if (!root || seen.has(root) || !root.querySelectorAll) {
            return;
        }

        seen.add(root);

        let hosts;

        try {
            hosts = root.querySelectorAll('*');
        } catch (_) {
            return;
        }

        for (const el of hosts) {
            if (el.shadowRoot) {
                callback(el.shadowRoot);
                forEachShadowRoot(el.shadowRoot, callback, seen);
            }
        }
    }

    function observeShadowRoot(root) {
        if (
            !root ||
            !window.MutationObserver ||
            (observedShadowRoots && observedShadowRoots.has(root))
        ) {
            return;
        }

        try {
            const shadowObserver = new MutationObserver(handleMutations);

            shadowObserver.observe(root, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: [
                    'href',
                    'ping',
                    'onmousedown',
                    'onclick',
                    'ontouchstart',
                    'onpointerdown',
                    'jsaction'
                ]
            });

            if (observedShadowRoots) {
                observedShadowRoots.add(root);
            }
        } catch (e) {
            log('Could not observe shadow root:', e);
        }
    }

    function cleanElement(root) {
        if (!root || root.nodeType !== 1) {
            return 0;
        }

        let count = 0;

        if (root.tagName === 'A' && cleanLink(root)) {
            count++;
        }

        if (root.querySelectorAll) {
            const links = root.querySelectorAll('a');

            for (const a of links) {
                if (cleanLink(a)) {
                    count++;
                }
            }

            scrubTrackingPixels(root);

            if (settings.cleanShadowDom) {
                if (root.shadowRoot) {
                    count += cleanRoot(root.shadowRoot);
                    observeShadowRoot(root.shadowRoot);
                }

                forEachShadowRoot(root, shadowRoot => {
                    count += cleanRoot(shadowRoot);
                    observeShadowRoot(shadowRoot);
                });
            }
        }

        return count;
    }

    function processAllLinks() {
        if (!document.documentElement) {
            return 0;
        }

        let count = cleanRoot(document);

        if (settings.cleanShadowDom) {
            forEachShadowRoot(document, shadowRoot => {
                count += cleanRoot(shadowRoot);
                observeShadowRoot(shadowRoot);
            });
        }

        log(`Scan complete; changed ${count} link(s).`);

        return count;
    }

    /* ------------------------------------------------------------------ */
    /* Aggressive href guard (JS-driven late rewrites)                    */
    /* ------------------------------------------------------------------ */

    function installHrefGuard() {
        if (!settings.aggressiveHrefGuard || hrefGuardInstalled) {
            return;
        }

        try {
            const proto = window.HTMLAnchorElement && window.HTMLAnchorElement.prototype;
            const descriptor = proto && Object.getOwnPropertyDescriptor(proto, 'href');

            if (!descriptor || !descriptor.set || !descriptor.get) {
                return;
            }

            const originalSet = descriptor.set;
            const originalGet = descriptor.get;

            Object.defineProperty(proto, 'href', {
                configurable: true,
                enumerable: descriptor.enumerable,
                get: function () {
                    return originalGet.call(this);
                },
                set: function (value) {
                    let outgoing = value;

                    try {
                        if (settings.aggressiveHrefGuard && typeof value === 'string') {
                            const absolute = new URL(value, location.href).href;
                            const cleaned = cleanUrl(absolute);

                            if (cleaned && isHttpUrl(cleaned)) {
                                outgoing = cleaned;
                            }
                        }
                    } catch (_) {
                        outgoing = value;
                    }

                    return originalSet.call(this, outgoing);
                }
            });

            hrefGuardInstalled = true;
            log('Installed aggressive href guard.');
        } catch (e) {
            log('Could not install href guard:', e);
        }
    }

    /* ------------------------------------------------------------------ */
    /* Telemetry beacon guards                                            */
    /* ------------------------------------------------------------------ */

    function installBeaconGuard() {
        if (!settings.blockTrackingBeacons || beaconGuardInstalled) {
            return;
        }

        try {
            if (navigator.sendBeacon) {
                const originalSendBeacon = navigator.sendBeacon.bind(navigator);

                navigator.sendBeacon = function (url, data) {
                    try {
                        if (settings.blockTrackingBeacons && isTrackingBeaconUrl(String(url))) {
                            stats.beaconsBlocked++;
                            log('Blocked tracking beacon:', url);
                            return true;
                        }
                    } catch (_) {
                        // Fall through to the original call.
                    }

                    return originalSendBeacon(url, data);
                };
            }
        } catch (e) {
            log('sendBeacon guard failed:', e);
        }

        try {
            const originalFetch = window.fetch;

            if (typeof originalFetch === 'function') {
                window.fetch = function (input, init) {
                    try {
                        const url = typeof input === 'string' ? input : input && input.url;

                        if (settings.blockTrackingBeacons && url && isTrackingBeaconUrl(url)) {
                            stats.beaconsBlocked++;
                            log('Blocked tracking fetch:', url);
                            return Promise.resolve(new Response(null, { status: 204 }));
                        }
                    } catch (_) {
                        // Fall through to the original call.
                    }

                    return originalFetch.apply(this, arguments);
                };
            }
        } catch (e) {
            log('fetch guard failed:', e);
        }

        try {
            const OriginalXHR = window.XMLHttpRequest;

            if (OriginalXHR) {
                const originalOpen = OriginalXHR.prototype.open;
                const originalSend = OriginalXHR.prototype.send;

                OriginalXHR.prototype.open = function (method, url) {
                    try {
                        this.__dtmgBlocked =
                            settings.blockTrackingBeacons && isTrackingBeaconUrl(String(url));
                    } catch (_) {
                        this.__dtmgBlocked = false;
                    }

                    return originalOpen.apply(this, arguments);
                };

                OriginalXHR.prototype.send = function () {
                    if (this.__dtmgBlocked) {
                        stats.beaconsBlocked++;
                        log('Blocked tracking XHR.');
                        return;
                    }

                    return originalSend.apply(this, arguments);
                };
            }
        } catch (e) {
            log('XHR guard failed:', e);
        }

        beaconGuardInstalled = true;
    }

    function installImageBeaconGuard() {
        if (!settings.blockTrackingBeacons || imageGuardInstalled) {
            return;
        }

        try {
            const proto = window.HTMLImageElement && window.HTMLImageElement.prototype;
            const descriptor = proto && Object.getOwnPropertyDescriptor(proto, 'src');

            if (!descriptor || !descriptor.set) {
                return;
            }

            const originalSet = descriptor.set;

            Object.defineProperty(proto, 'src', {
                configurable: true,
                enumerable: descriptor.enumerable,
                get: descriptor.get,
                set: function (value) {
                    try {
                        if (settings.blockTrackingBeacons && isTrackingBeaconUrl(String(value))) {
                            stats.pixelsBlocked++;
                            log('Blocked tracking pixel image:', value);
                            return;
                        }
                    } catch (_) {
                        // Fall through to the original setter.
                    }

                    return originalSet.call(this, value);
                }
            });

            imageGuardInstalled = true;
            log('Installed image beacon guard.');
        } catch (e) {
            log('Could not install image beacon guard:', e);
        }
    }

    /* ------------------------------------------------------------------ */
    /* Event protection                                                   */
    /* ------------------------------------------------------------------ */

    function findAnchor(target) {
        if (!target) {
            return null;
        }

        if (target.nodeType === 3) {
            target = target.parentElement;
        }

        if (!target || target.nodeType !== 1) {
            return null;
        }

        if (target.closest) {
            return target.closest('a[href]');
        }

        return null;
    }

    function handleLinkEvent(event) {
        const a = findAnchor(event.target);

        if (!a) {
            return;
        }

        const before = a.href;

        cleanLink(a);

        const after = a.href;

        if (before !== after) {
            log('Cleaned event link:', after);
        }
    }

    const EVENTS = ['pointerdown', 'mousedown', 'touchstart', 'click', 'auxclick', 'contextmenu'];

    safeCall('registerEventListeners', () => {
        for (const eventName of EVENTS) {
            window.addEventListener(eventName, handleLinkEvent, {
                capture: true,
                passive: true
            });
        }
    });

    /* ------------------------------------------------------------------ */
    /* MutationObserver + SPA handling                                    */
    /* ------------------------------------------------------------------ */

    function handleMutations(mutations) {
        let sawRelevantNode = false;

        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                    sawRelevantNode = true;
                    cleanElement(node);
                }
            }

            if (mutation.type === 'attributes' && mutation.target && mutation.target.tagName === 'A') {
                sawRelevantNode = true;
                cleanLink(mutation.target);
            }
        }

        if (sawRelevantNode) {
            forceSafeSearchOff();
            scheduleScan(false);
        }
    }

    function startObserver() {
        if (observer || !window.MutationObserver) {
            return;
        }

        const root = document.documentElement || document;

        if (!root) {
            return;
        }

        observerRoot = root;

        observer = new MutationObserver(handleMutations);

        observer.observe(root, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: [
                'href',
                'ping',
                'onmousedown',
                'onclick',
                'ontouchstart',
                'onpointerdown',
                'jsaction'
            ]
        });
    }

    function stopObserver() {
        if (observer) {
            observer.disconnect();
            observer = null;
            observerRoot = null;
        }
    }

    function scheduleScan(immediate) {
        if (scanQueued) {
            return;
        }

        scanQueued = true;

        const run = () => {
            scanQueued = false;
            processAllLinks();
        };

        if (immediate) {
            run();
            return;
        }

        if (typeof window.requestIdleCallback === 'function') {
            window.requestIdleCallback(run, { timeout: 500 });
        } else {
            scanTimer = window.setTimeout(run, 50);
        }
    }

    function watchSpaNavigation() {
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        function changed() {
            if (location.href === lastUrl) {
                forceSafeSearchOff();
                return;
            }

            lastUrl = location.href;

            forceSafeSearchOff();
            scheduleScan(true);
        }

        try {
            history.pushState = function () {
                const result = originalPushState.apply(this, arguments);
                changed();
                return result;
            };

            history.replaceState = function () {
                const result = originalReplaceState.apply(this, arguments);
                changed();
                return result;
            };

            window.addEventListener('popstate', changed, true);
            window.addEventListener('hashchange', changed, true);
        } catch (e) {
            log('SPA navigation hooks unavailable:', e);
        }
    }

    /* ------------------------------------------------------------------ */
    /* Periodic safety sweep                                              */
    /* ------------------------------------------------------------------ */

    function startSafetySweep() {
        if (safetyTimer) {
            return;
        }

        /*
         * Low-frequency safety sweep catches DOM changes made through APIs
         * that bypass MutationObserver or aggressive Google re-rendering.
         */
        safetyTimer = window.setInterval(() => {
            if (document.hidden) {
                return;
            }

            handleSafeSearchNavigation();
            forceSafeSearchOff();
            processAllLinks();

            if (settings.floatingPanelEnabled && !panelInstalled) {
                buildPanel();
            }
        }, 4000);
    }

    function handleSafeSearchNavigation() {
        if (!settings.forceSafeSearchOff) {
            return;
        }

        const current = location.href;

        if (current === lastUrl) {
            return;
        }

        lastUrl = current;

        forceSafeSearchOff();
    }

    /* ------------------------------------------------------------------ */
    /* Initialization                                                     */
    /* ------------------------------------------------------------------ */

    let initialized = false;

    function init() {
        if (initialized) {
            return;
        }

        initialized = true;

        safeCall('registerMenu', registerMenu);
        safeCall('installHrefGuard', installHrefGuard);
        safeCall('installBeaconGuard', installBeaconGuard);
        safeCall('installImageBeaconGuard', installImageBeaconGuard);
        safeCall('startObserver', startObserver);
        safeCall('watchSpaNavigation', watchSpaNavigation);
        safeCall('startSafetySweep', startSafetySweep);
        safeCall('forceSafeSearchOff', forceSafeSearchOff);
        safeCall('initialScan', () => scheduleScan(true));

        if (settings.floatingPanelEnabled) {
            safeCall('buildPanel', buildPanel);
        }

        try {
            console.info(
                `${PREFIX} v${VERSION} initialized on ${location.hostname}` +
                    (STARTUP_ERRORS.length ? ` (${STARTUP_ERRORS.length} subsystem(s) failed - see above)` : '')
            );
        } catch (_) {
            // Ignore environments without a console.
        }

        log(`Initialized ${VERSION}`, settings);
    }

    /*
     * document-start can execute before <html> exists in some managers.
     * The beacon/href guards are installed immediately regardless, since
     * they only touch global prototypes and don't need the DOM. Each is
     * wrapped in safeCall so a failure here (e.g. a browser that exposes
     * a non-configurable property) can't prevent the rest of the script,
     * including basic link cleaning, from running.
     */
    safeCall('installHrefGuard (early)', installHrefGuard);
    safeCall('installBeaconGuard (early)', installBeaconGuard);
    safeCall('installImageBeaconGuard (early)', installImageBeaconGuard);

    if (document.documentElement) {
        init();
    } else {
        const ready = () => {
            document.removeEventListener('DOMContentLoaded', ready);
            init();
        };

        document.addEventListener('DOMContentLoaded', ready, { once: true });

        window.addEventListener('load', () => init(), { once: true });
    }

    /*
     * Tiny diagnostic API for browser-console testing.
     */
    try {
        Object.defineProperty(window, 'DontTrackMeGoogle', {
            configurable: true,
            value: Object.freeze({
                version: VERSION,

                clean: () => processAllLinks(),

                getSettings: () => Object.assign({}, settings),

                getStats: () => Object.assign({}, stats),

                getStartupErrors: () => STARTUP_ERRORS.slice(),

                showPanel: () => {
                    settings.floatingPanelEnabled = true;
                    saveSettings();
                    buildPanel();
                },

                hidePanel: () => {
                    settings.floatingPanelEnabled = false;
                    saveSettings();
                    removePanel();
                }
            })
        });
    } catch (_) {
        // Ignore environments that prevent window properties.
    }
})();