Greasy Fork

Greasy Fork is available in English.

解锁网页复制/粘贴/右键/切屏限制💉

移除网页对复制、粘贴、右键、选中、切屏检测(onblur / visibilitychange)的限制,支持 iframe 与 Shadow DOM 深层拦截。

// ==UserScript==
// @name         解锁网页复制/粘贴/右键/切屏限制💉
// @namespace    http://greasyfork.icu/zh-CN/users/1534803-ookamiame
// @version      1.0.1
// @description  移除网页对复制、粘贴、右键、选中、切屏检测(onblur / visibilitychange)的限制,支持 iframe 与 Shadow DOM 深层拦截。
// @author       狼小雨
// @license      MIT
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    /** 防重复执行标记 */
    if (window.__UNLOCK_SCRIPT_LOADED__) return;
    window.__UNLOCK_SCRIPT_LOADED__ = true;

    /** 屏蔽事件类型(复制、粘贴、右键、切屏等) */
    const blockEvents = new Set([
        'copy', 'cut', 'paste', 'selectstart', 'contextmenu',
        'dragstart', 'mousedown', 'mouseup', 'keydown', 'keyup', 'keypress',
        'blur', 'focus', 'visibilitychange',
        'mouseleave', 'mouseout', 'pagehide', 'beforeunload', 'unload'
    ]);

    /** Hook 全局焦点与可见性检测 */
    function hookGlobalFocus() {
        if (window.__FOCUS_HOOKED__) return;
        window.__FOCUS_HOOKED__ = true;

        /**工具函数:安全重定义属性 */
    function redefine(obj, key, getter, setter) {
        try {
            Object.defineProperty(obj, key, {
                configurable: true,
                enumerable: true,
                get: getter,
                set: setter || (() => {}),
            });
        } catch (e) {
            console.log(`[Hook] 无法定义属性 ${key}:`, e);
        }
    }

        // Hook window.onblur
        let customOnBlur = () =>
        redefine(window, 'onblur',
            () => customOnBlur,
            (v) => {
                if (typeof v === 'function') {
                    console.log('[Hook] 阻止网页覆盖 window.onblur');
                    return;
                }
                if (v == null) customOnBlur = null;
            }
        );

        // Hook window.addEventListener
        const _addEventListener = window.addEventListener;
        window.addEventListener = function (type, listener, options) {
            if (blockEvents.has(type)) {
                console.log('[Hook] 阻止添加事件:', type);
                return;
            }
            return _addEventListener.call(this, type, listener, options);
        };

        // Hook document.onvisibilitychange
          let customVisibility = () =>
        redefine(document, 'onvisibilitychange',
            () => customVisibility,
            (v) => {
                if (typeof v === 'function') {
                    console.log('[Hook] 阻止网页覆盖 document.onvisibilitychange');
                    return;
                }
                if (v == null) customVisibility = null;
            }
        );

        // 页面始终处于前台聚焦状态
        Object.defineProperty(document, 'hidden', { configurable: true, get: () => false });
        Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'visible' });
        document.hasFocus = () => true;
    }


    /** 解锁网页交互限制 */
    function unlockPageRestrictions(root = document) {
        if (!root || root.__UNLOCKED__) return;
        root.__UNLOCKED__ = true;

        // 阻止限制事件冒泡
        blockEvents.forEach(event => {
            root.addEventListener(event, e => e.stopPropagation(), true);
        });

        // 遍历元素解除事件绑定与样式限制
        const all = root.querySelectorAll('*');
        all.forEach(el => {
            ['oncopy','oncut','onpaste','onselectstart','oncontextmenu',
             'ondragstart','onmousedown','onmouseup','onkeydown','onkeypress']
            .forEach(attr => {
                if (el[attr]) el[attr] = null;
            });

            // 仅当未设置 user-select:none 时再恢复
            const style = getComputedStyle(el);
            if (style.userSelect === 'none') {
                el.style.userSelect = 'text';
                el.style.webkitUserSelect = 'text';
                el.style.msUserSelect = 'text';
                el.style.mozUserSelect = 'text';
            }
        });

        // 全局解除
        window.onblur = window.onfocus = document.onvisibilitychange = null;
        document.onkeydown = document.oncontextmenu = null;
    }

    /** 深层处理 iframe 与 shadow DOM */
    function deepUnlock(root = document) {
        unlockPageRestrictions(root);

        // iframe
        root.querySelectorAll('iframe').forEach(iframe => {
            try {
                const doc = iframe.contentDocument || iframe.contentWindow?.document;
                if (doc && !doc.__UNLOCKED__) deepUnlock(doc);
            } catch (e) {
                console.log('[Unlock] 跨域 iframe 无法访问:', e);
            }
        });

        // shadow DOM
        const traverse = node => {
            if (!node) return;
            if (node.shadowRoot && !node.shadowRoot.__UNLOCKED__) deepUnlock(node.shadowRoot);
            node.childNodes.forEach(traverse);
        };
        traverse(root.body || root);
    }

    /** 初始化 */
    function init() {
        hookGlobalFocus();
        deepUnlock(document);
    }

    /** DOM 变化监控(防止重新绑定限制) */
    const observer = new MutationObserver(() => {
        requestIdleCallback(() => deepUnlock(document));
    });

    observer.observe(document, { childList: true, subtree: true });

    // DOMContentLoaded 后执行一次
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();