Event Listener Blocker & Focus Lock

Blocks detection of focus/tab/clipboard events without breaking user behavior; applies globally, incl. iframes/shadow DOMs. MAY break some functions.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name        Event Listener Blocker & Focus Lock
// @namespace   Violentmonkey Scripts
// @match       *://*/*
// @grant       none
// @version     1.13
// @author      ShortTimeNoSee
// @description Blocks detection of focus/tab/clipboard events without breaking user behavior; applies globally, incl. iframes/shadow DOMs. MAY break some functions.
// @license     MIT
// ==/UserScript==

(function() {
    'use strict';

    // debug toggle
    const DEBUG = false;

    const isGemini = window.location.hostname.includes('gemini.google.com');

    // expose/modifiable block-list for power users
    // if you want to add/remove events, do it on window.__blockEventList before this script runs
    window.__blockEventList = window.__blockEventList || new Set([
        'blur', 'focus', 'focusin', 'focusout',
        'visibilitychange', 'pagehide', 'pageshow', 'freeze', 'resume',
        'copy', 'cut', 'contextmenu', 'selectstart'
    ]);

    if (isGemini) {
        // Gemini's UI components need this event to function.
        // This removes it from the block list *only* on Gemini.
        window.__blockEventList.delete('focus');
    }

    // now use the global list
    const banned = window.__blockEventList;

    function isTextField(el) {
        return el instanceof HTMLInputElement ||
               el instanceof HTMLTextAreaElement ||
               (el instanceof HTMLElement && el.isContentEditable);
    }

    // avoid re-patching same context
    const patchedContexts = new WeakSet();

    function patchContext(ctx) {
        if (patchedContexts.has(ctx)) return;
        patchedContexts.add(ctx);
        if (DEBUG) console.log('patchContext on', ctx);

        try {
            const ET = ctx.EventTarget.prototype;
            const origAdd = ET.addEventListener;
            const origRemove = ET.removeEventListener;
            const origDispatch = ET.dispatchEvent;

            function patchedAdd(type, listener, opts) {
                const passive = opts && typeof opts === 'object' && opts.passive === true;
                if ((type === 'blur' || type === 'focusout') && isTextField(this)) {
                    return origAdd.call(this, type, listener, opts);
                }
                if (banned.has(type) && !passive) {
                    if (DEBUG) console.log(`blocked addEventListener(${type})`, this);
                    return;
                }
                return origAdd.call(this, type, listener, opts);
            }

            function patchedRemove(type, listener, opts) {
                const passive = opts && typeof opts === 'object' && opts.passive === true;
                if ((type === 'blur' || type === 'focusout') && isTextField(this)) {
                    return origRemove.call(this, type, listener, opts);
                }
                if (banned.has(type) && !passive) {
                    if (DEBUG) console.log(`blocked removeEventListener(${type})`, this);
                    return;
                }
                return origRemove.call(this, type, listener, opts);
            }

            function patchedDispatch(evt) {
                if ((evt.type === 'blur' || evt.type === 'focusout') && isTextField(this)) {
                    return origDispatch.call(this, evt);
                }
                if (banned.has(evt.type)) {
                    if (DEBUG) console.log(`blocked dispatchEvent(${evt.type})`, this);
                    return false;
                }
                return origDispatch.call(this, evt);
            }

            Object.defineProperty(ET, 'addEventListener',   { value: patchedAdd,    configurable: false, writable: false });
            Object.defineProperty(ET, 'removeEventListener',{ value: patchedRemove, configurable: false, writable: false });
            Object.defineProperty(ET, 'dispatchEvent',      { value: patchedDispatch,configurable: false, writable: false });
        } catch (e) {}

        // null out window/document handlers
        try {
            const doc = ctx.document;
            ctx.window.onblur = ctx.window.onfocus = null;
            ['visibilitychange','freeze','resume','pagehide','pageshow']
                .forEach(ev => { doc['on' + ev] = null; });
            ['copy','cut','contextmenu','selectstart']
                .forEach(ev => { doc['on' + ev] = null; });
        } catch (e) {}

        // force visibility and focus
        try {
            const doc = ctx.document;
            Object.defineProperty(doc, 'hidden',          { get: () => false,    configurable: true });
            Object.defineProperty(doc, 'visibilityState', { get: () => 'visible',configurable: true });
            doc.hasFocus = () => true;
        } catch (e) {}

        // clear active element and selection
        try {
            const ae = ctx.document.activeElement;
            if (ae) ae.blur();
            if (ctx.window.getSelection) ctx.window.getSelection().removeAllRanges();
        } catch (e) {}
    }

    // initial patch
    patchContext(window);

    // reapply every second
    setInterval(() => patchContext(window), 1000);

    // patch new iframes
    const obs = new MutationObserver(records => {
        records.forEach(r => r.addedNodes.forEach(node => {
            if (node.tagName === 'IFRAME') {
                if (DEBUG) console.log('patching iframe', node);
                node.addEventListener('load', () => {
                    try { patchContext(node.contentWindow); } catch {}
                });
            }
        }));
    });
    obs.observe(document.documentElement, { childList: true, subtree: true });

    // override after readyState changes
    document.addEventListener('readystatechange', () => {
        try {
            Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true });
        } catch (e) {}
    });

    // patch new shadow roots
    (function() {
        const origAttach = Element.prototype.attachShadow;
        Element.prototype.attachShadow = function(init) {
            const root = origAttach.call(this, init);
            patchContext(root);
            if (DEBUG) console.log('patched shadow root on', this);
            return root;
        };
    })();

})();