Hide Referrer

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==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');
})();