Anti-Visibility Cloak

Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected

// ==UserScript==
// @name         Anti-Visibility Cloak
// @namespace    https://spin.rip/
// @version      1.1
// @description  Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected
// @author       Spinfal
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @all-frames   true
// @icon         https://cdn.spin.rip/r/antivisibility.png
// @license      gpl-3.0-or-later
// ==/UserScript==

(function() {
  'use strict';

  const CONFIG = {
    notify: {
      properties: true,
      hasFocusCall: false,
      addListener: {
        visibilitychange: true,
        webkitvisibilitychange: true,
        mozvisibilitychange: true,
        msvisibilitychange: true,
        pagehide: true,
        freeze: true,
        resume: true,
        pageshow: true,
        blur: false,
        focus: false,
        mouseout: true,
        mouseleave: true,
        pointerout: true,
        pointerleave: true,
        mouseover: true,
        mouseenter: true,
        pointerover: true,
        mousemove: false
      },
      invoke: {
        visibilitychange: true,
        webkitvisibilitychange: true,
        mozvisibilitychange: true,
        msvisibilitychange: true,
        pagehide: true,
        freeze: true,
        resume: true,
        pageshow: true,
        blur: false,
        focus: false,
        mouseout: true,
        mouseleave: true,
        pointerout: true,
        pointerleave: true,
        mouseover: true,
        mouseenter: true,
        pointerover: true,
        mousemove: false
      }
    },
    suppressFocusOnEditable: true,
    mouse: {
      spoofExit: true,          // block/neutralize exit-intent listeners on window/document
      ignoreGlobalLeave: true,  // ignore global leave events where relatedTarget is null
      blockGlobalEnter: true,   // swallow global enter events where relatedTarget is null
      initialEnterOnce: true,   // allow exactly one initial global enter after load
      fakeMovement: true,       // periodically dispatch synthetic mousemove to suggest presence
      fakeIntervalMs: 12000,    // how often to synthesize a mousemove
      moveJitterPx: 2           // tiny jitter so it doesn’t look robotic
    }
  };

  // popup system
  const makeNotifier = () => {
    let shadow, wrap, root, last, lastAt = 0, hideTimer;
    root = document.createElement('div');
    root.style.all = 'initial';
    root.style.position = 'fixed';
    root.style.zIndex = '2147483647';
    root.style.right = '10px';
    root.style.bottom = '10px';
    root.style.pointerEvents = 'none';
    shadow = root.attachShadow({ mode: 'open' });
    const style = document.createElement('style');
    style.textContent = `
      .wrap{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:12px;line-height:1.2;background:#111;color:#fff;border-radius:8px;padding:8px 10px;box-shadow:0 2px 12px rgba(0,0,0,.35);opacity:.95;max-width:260px;pointer-events:auto}
      .wrap{display:flex;gap:8px;align-items:start}
      .dot{width:8px;height:8px;border-radius:50%;background:#4ade80;margin-top:4px;flex:0 0 8px}
      .msg{white-space:pre-line}
      .hide{animation:fadeout .4s forwards}
      @keyframes fadeout{to{opacity:0;transform:translateY(4px)}}
    `;
    wrap = document.createElement('div');
    wrap.className = 'wrap';
    wrap.innerHTML = `<div class="dot"></div><div class="msg"></div>`;
    shadow.append(style, wrap);
    const set = (text) => {
      if (!root.isConnected) document.documentElement.appendChild(root);
      wrap.querySelector('.msg').textContent = text;
      wrap.classList.remove('hide');
      clearTimeout(hideTimer);
      hideTimer = setTimeout(() => {
        wrap.classList.add('hide');
        setTimeout(() => { if (root.isConnected) root.remove(); }, 450);
      }, 1600);
    };
    return (what) => {
      const now = Date.now();
      if (what === last && (now - lastAt) < 250) return;
      last = what; lastAt = now;
      set(`visibility/mouse check bypassed:\n${what}`);
    };
  };
  const notify = makeNotifier();

  // tiny helper for safe define
  const define = (target, key, descriptor) => {
    try {
      const desc = Object.getOwnPropertyDescriptor(target, key);
      if (desc && desc.configurable === false) return false;
      Object.defineProperty(target, key, { configurable: true, ...descriptor });
      return true;
    } catch { return false; }
  };

  // force document.* visibility values
  const forceVisibilityProps = () => {
    const docProto = Document.prototype;
    const makeGetter = (valName, value) => function() {
      if (CONFIG.notify.properties) notify(`${valName} read`);
      return value;
    };
    define(docProto, 'visibilityState', { get: makeGetter('document.visibilityState', 'visible') }) ||
      define(document, 'visibilityState', { get: makeGetter('document.visibilityState', 'visible') });
    define(docProto, 'hidden', { get: makeGetter('document.hidden', false) }) ||
      define(document, 'hidden', { get: makeGetter('document.hidden', false) });
    const legacy = [
      ['webkitVisibilityState','visible'],
      ['mozVisibilityState','visible'],
      ['msVisibilityState','visible'],
      ['webkitHidden',false],
      ['mozHidden',false],
      ['msHidden',false]
    ];
    for (const [k, v] of legacy) {
      define(docProto, k, { get: makeGetter(`document.${k}`, v) }) ||
      define(document, k, { get: makeGetter(`document.${k}`, v) });
    }
    const origHasFocus = docProto.hasFocus;
    const announceHasFocus = () => { if (CONFIG.notify.hasFocusCall) notify('document.hasFocus() call'); return true; };
    define(docProto, 'hasFocus', { value: announceHasFocus }) ||
    define(document, 'hasFocus', { value: announceHasFocus });
    if (!document.__origHasFocus) Object.defineProperty(document, '__origHasFocus', { value: origHasFocus, configurable: true });
  };

  // patch event listeners to swallow exit/enter-intent and wrap handlers
  const forceVisibilityEvents = () => {
    const TYPES = [
      'visibilitychange','webkitvisibilitychange','mozvisibilitychange','msvisibilitychange',
      'pagehide','freeze','resume','pageshow','blur','focus',
      'mouseleave','mouseout','pointerleave','pointerout',
      'mouseover','mouseenter','pointerover',
      'mousemove'
    ];
    const EXIT_TYPES = new Set(['mouseleave','mouseout','pointerleave','pointerout']);
    const ENTER_TYPES = new Set(['mouseover','mouseenter','pointerover']);
    const isEditableTarget = (ev) => {
      if (!ev || !ev.target) return false;
      const t = ev.target;
      if (t.isContentEditable) return true;
      const tag = (t.tagName || '').toLowerCase();
      return tag === 'input' || tag === 'textarea' || tag === 'select';
    };
    const isGlobal = (ctx) =>
      ctx === window || ctx === document || ctx === document.documentElement;

    let allowOneGlobalEnter = !!CONFIG.mouse.initialEnterOnce;

    const wrapHandler = (type, fn, ctx) => {
      return function(event) {
        try {
          Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true });
          Object.defineProperty(document, 'hidden', { get: () => false, configurable: true });
        } catch {}
        // block global "leaving window" signals
        if (
          CONFIG.mouse.ignoreGlobalLeave &&
          EXIT_TYPES.has(type) &&
          isGlobal(ctx) &&
          (event == null || event.relatedTarget == null)
        ) {
          if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global exit)`);
          return;
        }
        // block global "entering window" signals
        if (
          CONFIG.mouse.blockGlobalEnter &&
          ENTER_TYPES.has(type) &&
          isGlobal(ctx) &&
          (event == null || event.relatedTarget == null)
        ) {
          if (allowOneGlobalEnter) {
            allowOneGlobalEnter = false; // let exactly one through
          } else {
            if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global enter)`);
            return;
          }
        }
        if (CONFIG.notify.invoke[type]) {
          if (!(CONFIG.suppressFocusOnEditable && (type === 'focus' || type === 'blur') && isEditableTarget(event))) {
            notify(`${type} listener invoked`);
          }
        }
        try { return fn.call(this, event); } catch(e) { setTimeout(() => { throw e; }); }
      };
    };

    const patchAEL = (proto, label) => {
      const orig = proto.addEventListener;
      define(proto, 'addEventListener', {
        value: function(type, listener, options) {
          const t = String(type);
          if (!listener) return orig.call(this, type, listener, options);
          if (TYPES.includes(t)) {
            // block registration of exit-intent listeners on global targets
            if (CONFIG.mouse.spoofExit && EXIT_TYPES.has(t) && isGlobal(this)) {
              if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`);
              return;
            }
            // block registration of enter-intent listeners on global targets
            if (CONFIG.mouse.blockGlobalEnter && ENTER_TYPES.has(t) && isGlobal(this)) {
              if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`);
              return;
            }
            if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}")`);
            const wrapped = wrapHandler(t, listener, this);
            try { Object.defineProperty(listener, '__visible_wrap__', { value: wrapped }); } catch {}
            return orig.call(this, type, wrapped, options);
          }
          return orig.call(this, type, listener, options);
        }
      });
      const origRel = proto.removeEventListener;
      define(proto, 'removeEventListener', {
        value: function(type, listener, options) {
          const l = listener && listener.__visible_wrap__ ? listener.__visible_wrap__ : listener;
          return origRel.call(this, type, l, options);
        }
      });
    };

    patchAEL(EventTarget.prototype, 'EventTarget');
  };

  // synthetic mouse activity to suggest presence in page
  const installFakeMouse = () => {
    if (!CONFIG.mouse.fakeMovement) return;
    let lastX = Math.max(10, Math.min(window.innerWidth - 10, Math.floor(window.innerWidth / 2)));
    let lastY = Math.max(10, Math.min(window.innerHeight - 10, Math.floor(window.innerHeight / 2)));
    const update = (e) => {
      if (!e) return;
      if (typeof e.clientX === 'number') lastX = e.clientX;
      if (typeof e.clientY === 'number') lastY = e.clientY;
    };
    window.addEventListener('mousemove', update, { passive: true, capture: true });
    const tick = () => {
      try {
        const jx = (Math.random() * CONFIG.mouse.moveJitterPx * 2 - CONFIG.mouse.moveJitterPx) | 0;
        const jy = (Math.random() * CONFIG.mouse.moveJitterPx * 2 - CONFIG.mouse.moveJitterPx) | 0;
        const x = Math.max(0, Math.min(window.innerWidth - 1, lastX + jx));
        const y = Math.max(0, Math.min(window.innerHeight - 1, lastY + jy));
        const ev = new MouseEvent('mousemove', {
          bubbles: true,
          cancelable: false,
          clientX: x,
          clientY: y,
          screenX: x,
          screenY: y,
          view: window
        });
        document.dispatchEvent(ev);
        if (CONFIG.notify.invoke.mousemove) notify('synthetic mousemove dispatched');
      } catch {}
    };
    setInterval(tick, Math.max(4000, CONFIG.mouse.fakeIntervalMs | 0));
  };

  // one-time visibility ping
  const dispatchInitialPing = () => {
    try {
      const ev = new Event('visibilitychange');
      document.dispatchEvent(ev);
    } catch {}
  };

  // apply
  forceVisibilityProps();
  forceVisibilityEvents();
  installFakeMouse();
  document.addEventListener('DOMContentLoaded', dispatchInitialPing, { once: true });

  // reapply on dom mutations (defensive)
  const reapply = () => {
    try {
      if (document.visibilityState !== 'visible') forceVisibilityProps();
      if (document.hidden !== false) forceVisibilityProps();
    } catch {}
  };
  const mo = new MutationObserver(reapply);
  mo.observe(document.documentElement, { childList: true, subtree: true });
})();