Hide Referrer

Hide HTTP Referrer on all websites to prevent destination sites from tracking your origin page

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Hide Referrer
// @namespace    https://github.com/planetoid/userscripts
// @version      1.0.0
// @description  Hide HTTP Referrer on all websites to prevent destination sites from tracking your origin page
// @author       Planetoid
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ============================================================
    // Configuration
    // ============================================================

    const CONFIG = {
        // Whether to inject <meta> referrer tag (primary protection)
        injectMetaTag: true,

        // Whether to add rel="noreferrer" to all <a> tags
        patchLinks: true,

        // Whether to intercept window.open so it doesn't send referrer
        patchWindowOpen: true,

        // Whether to observe DOM changes and patch dynamically added links (for SPAs)
        observeDom: true,

        // Excluded domains (referrer hiding will be skipped on these sites)
        // e.g. ['example.com', 'mysite.org']
        excludedDomains: [],

        // Whether to show debug messages in the console
        debug: false,
    };

    // ============================================================
    // Utilities
    // ============================================================

    function log(...args) {
        if (CONFIG.debug) {
            console.log('[Hide Referrer]', ...args);
        }
    }

    function isExcluded() {
        return CONFIG.excludedDomains.some(
            (domain) =>
                location.hostname === domain ||
                location.hostname.endsWith('.' + domain)
        );
    }

    function isExternalLink(anchor) {
        try {
            return anchor.href && new URL(anchor.href).hostname !== location.hostname;
        } catch {
            return false;
        }
    }

    // ============================================================
    // 1. Inject <meta name="referrer" content="no-referrer">
    //    Injected as early as possible during document-start
    // ============================================================

    function injectMetaTag() {
        if (!CONFIG.injectMetaTag) return;

        const meta = document.createElement('meta');
        meta.name = 'referrer';
        meta.content = 'no-referrer';

        // <head> may not exist yet at document-start, try multiple mount points
        const parent =
            document.head ||
            document.documentElement ||
            document.querySelector('head');

        if (parent) {
            parent.insertBefore(meta, parent.firstChild);
            log('Meta tag injected into', parent.tagName);
        } else {
            // Very early stage — wait for the DOM to appear before injecting
            const observer = new MutationObserver(() => {
                const target = document.head || document.documentElement;
                if (target) {
                    target.insertBefore(meta, target.firstChild);
                    log('Meta tag injected (deferred)');
                    observer.disconnect();
                }
            });
            observer.observe(document, { childList: true, subtree: true });
        }
    }

    // ============================================================
    // 2. Add rel="noreferrer noopener" to all <a> tags
    // ============================================================

    function patchAnchor(anchor) {
        if (!anchor.href || anchor.dataset.referrerPatched) return;

        // Only patch external links — remove this check to patch all links
        if (!isExternalLink(anchor)) return;

        const relValues = new Set((anchor.rel || '').split(/\s+/).filter(Boolean));
        relValues.add('noreferrer');
        relValues.add('noopener');
        anchor.rel = [...relValues].join(' ');
        anchor.dataset.referrerPatched = '1';

        log('Patched link:', anchor.href);
    }

    function patchAllLinks() {
        if (!CONFIG.patchLinks) return;
        document.querySelectorAll('a[href]').forEach(patchAnchor);
    }

    // ============================================================
    // 3. Intercept window.open — use an intermediate page to strip referrer
    // ============================================================

    function patchWindowOpen() {
        if (!CONFIG.patchWindowOpen) return;

        const originalOpen = window.open;

        window.open = function (url, target, features) {
            if (url) {
                try {
                    const parsed = new URL(url, location.href);
                    if (parsed.hostname !== location.hostname) {
                        // Use a Blob URL as an intermediate page to strip referrer
                        const html = `
              <!DOCTYPE html>
              <html>
              <head>
                <meta name="referrer" content="no-referrer">
                <meta http-equiv="refresh" content="0;url=${parsed.href}">
              </head>
              <body></body>
              </html>`;
                        const blob = new Blob([html], { type: 'text/html' });
                        const blobUrl = URL.createObjectURL(blob);
                        const win = originalOpen.call(window, blobUrl, target, features);

                        // Revoke Blob URL after a short delay
                        setTimeout(() => URL.revokeObjectURL(blobUrl), 3000);
                        log('window.open intercepted:', url);
                        return win;
                    }
                } catch (e) {
                    log('window.open parse error:', e);
                }
            }
            return originalOpen.call(window, url, target, features);
        };

        log('window.open overridden');
    }

    // ============================================================
    // 4. MutationObserver — watch for dynamically added links
    // ============================================================

    function observeDom() {
        if (!CONFIG.observeDom || !CONFIG.patchLinks) return;

        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType !== Node.ELEMENT_NODE) continue;

                    // If the added node itself is an <a>
                    if (node.tagName === 'A') {
                        patchAnchor(node);
                    }

                    // Check the subtree of the added node for <a> elements
                    if (node.querySelectorAll) {
                        node.querySelectorAll('a[href]').forEach(patchAnchor);
                    }
                }
            }
        });

        // Wait for <body> to exist before observing
        function startObserving() {
            if (document.body) {
                observer.observe(document.body, { childList: true, subtree: true });
                log('DOM Observer started');
            } else {
                requestAnimationFrame(startObserving);
            }
        }

        startObserving();
    }

    // ============================================================
    // Main
    // ============================================================

    if (isExcluded()) {
        log('Current domain is excluded, skipping');
        return;
    }

    // Phase 1: document-start (as early as possible)
    injectMetaTag();
    patchWindowOpen();

    // Phase 2: Patch links after DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            patchAllLinks();
            observeDom();
        });
    } else {
        patchAllLinks();
        observeDom();
    }

    log('Hide Referrer userscript loaded');
})();