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