Block All Client-Side Redirects (Quiet, All Frames)

Silently blocks most client-side redirects (links, forms, JS location, window.open, meta refresh). Works in page context & all frames; good for aggressive sites.

As of 15.08.2025. See ბოლო ვერსია.

// ==UserScript==
// @name         Block All Client-Side Redirects (Quiet, All Frames)
// @namespace    https://github.com/osuobiem
// @version      1.4
// @description  Silently blocks most client-side redirects (links, forms, JS location, window.open, meta refresh). Works in page context & all frames; good for aggressive sites.
// @author       Gabriel Osuobiem
// @match        *://*/*
// @run-at       document-start
// @all-frames   true
// @inject-into  page
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // If the manager ignores @inject-into page, fall back to a self-injected <script> so we still run in the page context.
  if (typeof window.__noRedirectInjected === 'undefined') {
    try {
      const s = document.createElement('script');
      s.textContent = '(' + core.toString() + ')();';
      (document.documentElement || document.head || document.body).appendChild(s);
      s.remove();
      window.__noRedirectInjected = true;
    } catch (_) {
      // As a last resort run in this context (may be sandboxed; still helps for anchors/forms/meta).
      core();
    }
  }

  function core() {
    const log = (...a) => { try { console.debug('[NoRedirect]', ...a); } catch (_) {} };
    const noop = () => undefined;

    // Return a harmless Window-like stub so scripts that expect a Window don’t crash.
    const fakeWin = new Proxy(function () {}, {
      apply() { return null; },
      get(_t, prop) {
        if (prop === 'closed') return true;
        if (prop === 'close') return noop;
        if (prop === 'focus') return noop;
        if (prop === 'blur') return noop;
        return noop;
      }
    });

    function hardenLocation(win) {
      try {
        const L = win.location;
        const blockSet = (url) => { log('location set blocked', url); };
        // Block window.location assign
        try {
          Object.defineProperty(win, 'location', {
            configurable: false,
            enumerable: true,
            get: () => L,
            set: blockSet
          });
        } catch (_) {}
        // Block document.location alias
        try {
          Object.defineProperty(win.document, 'location', {
            configurable: false,
            enumerable: true,
            get: () => L,
            set: blockSet
          });
        } catch (_) {}

        // Block methods assign/replace/reload
        ['assign', 'replace', 'reload'].forEach(m => {
          try {
            const orig = L[m].bind(L);
            L[m] = new Proxy(orig, {
              apply(_t, _th, args) {
                log(`location.${m} blocked`, args && args[0]);
                return; // no-op
              }
            });
          } catch (_) {}
        });
      } catch (_) {}
    }

    function hardenWindow(win) {
      // Block window.open
      try {
        const origOpen = win.open.bind(win);
        win.open = new Proxy(origOpen, {
          apply(_t, _th, args) {
            log('window.open blocked', args && args[0]);
            return fakeWin;
          }
        });
      } catch (_) {}

      // Block History API navigations (SPAs/pushState)
      try {
        const H = win.history;
        ['pushState', 'replaceState', 'go', 'back', 'forward'].forEach(m => {
          try {
            const orig = H[m].bind(H);
            H[m] = new Proxy(orig, {
              apply() {
                log(`history.${m} blocked`);
                return; // no-op
              }
            });
          } catch (_) {}
        });
      } catch (_) {}

      // Block JS timer string eval redirects
      const navStr = /\b(?:top|parent|self|window)\s*\.\s*location\b|\blocation\s*=|\blocation\.(?:assign|replace)\s*\(|\bopen\s*\(/i;
      ['setTimeout', 'setInterval'].forEach(name => {
        try {
          const orig = win[name].bind(win);
          win[name] = new Proxy(orig, {
            apply(target, thisArg, args) {
              if (typeof args[0] === 'string' && navStr.test(args[0])) {
                log(`${name} redirect string blocked`, args[0]);
                return 0;
              }
              return Reflect.apply(target, thisArg, args);
            }
          });
        } catch (_) {}
      });

      // Neutralize onbeforeunload tricks
      try {
        Object.defineProperty(win, 'onbeforeunload', {
          configurable: true,
          get: () => null,
          set: () => { log('onbeforeunload blocked'); }
        });
      } catch (_) {}

      // Prevent anchor-link navigations (but keep other clicks functional)
      try {
        win.addEventListener('click', e => {
          const a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
          if (!a) return;
          e.preventDefault();
          e.stopImmediatePropagation();
          log('anchor click blocked', a.href);
        }, true);
      } catch (_) {}

      // Block form submits
      try {
        win.addEventListener('submit', e => {
          e.preventDefault();
          e.stopImmediatePropagation();
          log('form submit blocked', e.target && e.target.action);
        }, true);
      } catch (_) {}

      // Kill meta refresh (initial + dynamic)
      function stripMeta() {
        try {
          win.document.querySelectorAll('meta[http-equiv="refresh"], meta[http-equiv="Refresh"]').forEach(m => {
            log('meta refresh removed', m.content);
            m.remove();
          });
        } catch (_) {}
      }
      stripMeta();
      try {
        new win.MutationObserver(stripMeta).observe(win.document.documentElement, { childList: true, subtree: true });
      } catch (_) {}

      // Add defensive CSP (may be ignored by some browsers/sites)
      try {
        const meta = win.document.createElement('meta');
        meta.httpEquiv = 'Content-Security-Policy';
        meta.content = "navigate-to 'none'; form-action 'none'";
        win.document.documentElement.prepend(meta);
      } catch (_) {}
    }

    function hardenRecursively(win) {
      hardenLocation(win);
      hardenWindow(win);

      // Same-origin child frames
      try {
        for (let i = 0; i < win.frames.length; i++) {
          const f = win.frames[i];
          try {
            if (f.location && f.location.origin === win.location.origin) {
              hardenRecursively(f);
            }
          } catch (_) {}
        }
      } catch (_) {}

      // Also patch parent/top if same-origin
      ['parent', 'top'].forEach(ref => {
        try {
          const w = win[ref];
          if (w && w !== win && w.location && w.location.origin === win.location.origin) {
            hardenLocation(w);
            hardenWindow(w);
          }
        } catch (_) {}
      });
    }

    // Patch *now* at document-start
    hardenRecursively(window);

    // In case frames get added later (ads), attempt a delayed pass
    try {
      const again = () => {
        try { hardenRecursively(window); } catch (_) {}
      };
      document.addEventListener('DOMContentLoaded', again, { once: true });
      setTimeout(again, 1500);
      setTimeout(again, 4000);
    } catch (_) {}

    log('quiet redirect blocking active (page context, all frames).');
  }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。