禁用鼠标离开网页/窗口失焦监听

通过多种方式阻止网页检测鼠标离开页面、窗口失去焦点或页面可见性变化,并包含活动模拟,旨在保护用户操作不被意外中断或记录。

// ==UserScript==
// @name         禁用鼠标离开网页/窗口失焦监听
// @namespace    http://tampermonkey.net/
// @version      0.4.2
// @description  通过多种方式阻止网页检测鼠标离开页面、窗口失去焦点或页面可见性变化,并包含活动模拟,旨在保护用户操作不被意外中断或记录。
// @author       Chuwu
// @match        *://*.chaoxing.com/*
// @match        *://*.icve.com.cn/*
// @match        *://*.edu.cn/*
// @match        *://*.icourse163.org/*
// @match        https://e.huawei.com/*
// @match        https://talent.shixizhi.huawei.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chaoxing.com
// @grant        unsafeWindow
// @run-at       document-start
// @license      AGPL3.0
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_NAME = '禁用鼠标离开/失焦监听';
    const DEBUG = false; // 设置为 true 以启用详细日志记录

    // 使用 unsafeWindow 直接访问页面的全局 window 对象
    const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;

    if (DEBUG) console.log(`[${SCRIPT_NAME}] 脚本在 document-start 阶段开始运行。`);

    // --- 1. 重写 EventTarget.prototype.addEventListener ---
    // 阻止特定的事件监听器被添加到 window 或 document 对象上
    const forbiddenEvents = new Set([
        'mouseout',     // 鼠标移出元素
        'mouseleave',   // 鼠标离开元素(不冒泡)
        'blur',         // 元素失去焦点
        'focusout',     // 元素失去焦点(冒泡)
        'visibilitychange' // 页面可见性改变
    ]);

    // 保存原始的 addEventListener 方法引用
    const originalAddEventListener = EventTarget.prototype.addEventListener;

    // 重写 EventTarget 原型上的 addEventListener 方法
    EventTarget.prototype.addEventListener = function(type, listener, options) {
        // 检查:1. 事件类型是否在我们想要阻止的列表中
        //       2. 事件监听的目标 (`this`) 是否是页面的 window 或 document 对象
        if (forbiddenEvents.has(type) && (this === pageWindow || this === pageWindow.document)) {
            if (DEBUG) console.log(`[${SCRIPT_NAME}] 已阻止向`, this, `添加 "${type}" 事件监听器`);
            // 不调用原始的 addEventListener,从而阻止监听器被添加
            return;
        }
        // 如果不是要阻止的事件或目标,则正常调用原始的 addEventListener
        return originalAddEventListener.call(this, type, listener, options);
    };

    if (DEBUG) console.log(`[${SCRIPT_NAME}] EventTarget.prototype.addEventListener 已被重写。`);


    // --- 2. 重写与焦点和可见性相关的属性 ---
    try {
        // --- 2.1 页面可见性 API (Page Visibility API) ---
        // 强制页面始终处于可见状态
        Object.defineProperty(pageWindow.document, 'visibilityState', {
            value: 'visible',  // 状态值固定为 'visible'
            writable: false,   // 设置为不可写,防止页面脚本轻易修改
            configurable: true // 保持可配置,允许油猴脚本自身更新或禁用
        });
        Object.defineProperty(pageWindow.document, 'hidden', {
            value: false,      // hidden 属性固定为 false
            writable: false,
            configurable: true
        });
        // 处理一些旧浏览器或特定浏览器可能使用的前缀版本
        Object.defineProperty(pageWindow.document, 'webkitVisibilityState', {
            value: 'visible', writable: false, configurable: true
        });
        Object.defineProperty(pageWindow.document, 'mozVisibilityState', {
            value: 'visible', writable: false, configurable: true
        });
        Object.defineProperty(pageWindow.document, 'msVisibilityState', {
            value: 'visible', writable: false, configurable: true
        });
        Object.defineProperty(pageWindow.document, 'webkitHidden', {
            value: false, writable: false, configurable: true
        });
        Object.defineProperty(pageWindow.document, 'mozHidden', {
            value: false, writable: false, configurable: true
        });
        Object.defineProperty(pageWindow.document, 'msHidden', {
            value: false, writable: false, configurable: true
        });

        if (DEBUG) console.log(`[${SCRIPT_NAME}] 页面可见性 API (Visibility API) 相关属性已被重写。`);

        // --- 2.2 文档焦点 (Document Focus) ---
        // 重写 document.hasFocus() 方法,使其始终返回 true
        Object.defineProperty(pageWindow.document, 'hasFocus', {
            value: () => true, // 强制返回 true,表示页面始终拥有焦点
            writable: false,   // 不可写
            configurable: true // 可配置
        });

        if (DEBUG) console.log(`[${SCRIPT_NAME}] document.hasFocus 方法已被重写。`);

        // --- 2.3 窗口失焦/聚焦事件 (Window Blur/Focus - 旧版事件处理) ---
        // 通过定义属性来阻止直接赋值 `window.onblur` 或 `window.onfocus`
        Object.defineProperty(pageWindow, 'onblur', {
            value: null,       // 值设为 null
            writable: false,   // 阻止直接赋值,如 `window.onblur = function(){...}`
            configurable: true
        });
        Object.defineProperty(pageWindow, 'onfocus', {
            value: null,
            writable: false,
            configurable: true
        });
        // 同样处理 onfocusout 和 onfocusin
        Object.defineProperty(pageWindow, 'onfocusout', {
            value: null, writable: false, configurable: true
        });
        Object.defineProperty(pageWindow, 'onfocusin', {
            value: null, writable: false, configurable: true
        });

        if (DEBUG) console.log(`[${SCRIPT_NAME}] window.onblur / window.onfocus 等属性已被重写。`);

    } catch (e) {
        console.error(`[${SCRIPT_NAME}] 重写焦点/可见性相关属性时出错:`, e);
    }

    // --- 3. 定期模拟鼠标移动事件 (恢复原方法 5) ---
    // 目的:模拟用户活动,可能用于防止某些基于鼠标静止的检测或闲置超时机制。
    try {
        // 创建一个模拟的鼠标移动事件对象
        const fakeMouseEvent = new MouseEvent('mousemove', {
            bubbles: true,       // 事件应该冒泡
            cancelable: true,    // 事件可以被取消
            view: pageWindow,    // 关联的视图(窗口)
            clientX: 100,        // 模拟的 X 坐标 (可以考虑随机化)
            clientY: 100         // 模拟的 Y 坐标 (可以考虑随机化)
            // screenX, screenY, movementX, movementY 等也可以根据需要添加
        });

        // 设置定时器,定期在 document 上派发这个模拟事件
        const intervalId = pageWindow.setInterval(function() {
            // 在页面的 document 上触发 'mousemove' 事件
            pageWindow.document.dispatchEvent(fakeMouseEvent);
            if (DEBUG) console.log(`[${SCRIPT_NAME}] 已派发模拟的 mousemove 事件。`);
        }, 30000); // 每 30000 毫秒 (30 秒) 触发一次

        if (DEBUG) console.log(`[${SCRIPT_NAME}] 已启动模拟鼠标活动 (定时器 ID: ${intervalId})。`);

        pageWindow.addEventListener('unload', () => {
            pageWindow.clearInterval(intervalId);
            if (DEBUG) console.log(`[${SCRIPT_NAME}] 已清除模拟鼠标活动的定时器。`);
        });

    } catch (e) {
        console.error(`[${SCRIPT_NAME}] 启动模拟鼠标移动时出错:`, e);
    }

    // --- 初始化完成日志 ---
    console.log(`[${SCRIPT_NAME}] 初始化完成。`);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址