Anti-Disable-Devtool

智能拦截 disable-devtool 反调试脚本,强制恢复 F12 和右键菜单

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.

You will need to install an extension such as Stylus to install this style.

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

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

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

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

// ==UserScript==
// @name         Anti-Disable-Devtool
// @namespace    https://github.com/MissChina/anti-disable-devtool
// @version      12.0.0
// @description  智能拦截 disable-devtool 反调试脚本,强制恢复 F12 和右键菜单
// @author       MissChina
// @license      Personal Non-Commercial License
// @match        *://*.hhkan0.com/*
// @match        *://*.hhkan1.com/*
// @match        *://*.hhkan2.com/*
// @match        *://*.hhkan3.com/*
// @match        *://*.hhkan4.com/*
// @match        *://hhkan0.com/*
// @match        *://hhkan1.com/*
// @match        *://hhkan2.com/*
// @match        *://hhkan3.com/*
// @match        *://hhkan4.com/*
// @run-at       document-start
// @grant        none
// @icon         https://github.com/MissChina/anti-disable-devtool/raw/main/icon.svg
// @homepageURL  https://github.com/MissChina/anti-disable-devtool
// @supportURL   https://github.com/MissChina/anti-disable-devtool/issues
// ==/UserScript==

// ================================================================
// 【Phase 0】直接在 Tampermonkey 上下文中执行(最快,不经过 DOM 注入)
// @grant none 时共享页面 JS 上下文,可以直接修改原型链
// 这段代码在 document-start 时刻立即执行,先于任何页面脚本
// ================================================================
(function() {
    'use strict';

    const BAD_DOMAINS = ['baidu.com', 'google.com', 'bing.com', 'theajack.github.io', '404.html', 'about:blank'];
    const isBadUrl = (url) => {
        if (!url) return false;
        const s = String(url).toLowerCase();
        return BAD_DOMAINS.some(d => s.includes(d));
    };

    // --- 1) 立即 hook Location.prototype(最关键) ---
    try {
        const L = Location.prototype;
        const _assign = L.assign;
        const _replace = L.replace;

        L.assign = function(url) {
            if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 assign:', url); return; }
            return _assign.call(this, url);
        };
        L.replace = function(url) {
            if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 replace:', url); return; }
            return _replace.call(this, url);
        };

        const hrefDesc = Object.getOwnPropertyDescriptor(L, 'href');
        if (hrefDesc && hrefDesc.set) {
            const _hrefSet = hrefDesc.set;
            Object.defineProperty(L, 'href', {
                get: hrefDesc.get,
                set: function(url) {
                    if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 href:', url); return; }
                    return _hrefSet.call(this, url);
                },
                configurable: false,
                enumerable: true
            });
        }
    } catch(e) { console.warn('[Anti-DD/P0] Location hook 失败:', e); }

    // --- 2) 立即伪造 outerWidth / outerHeight(防止尺寸差检测触发跳转) ---
    try {
        Object.defineProperty(window, 'outerWidth', {
            get: function() { return window.innerWidth; },
            configurable: false
        });
        Object.defineProperty(window, 'outerHeight', {
            get: function() { return window.innerHeight; },
            configurable: false
        });
    } catch(e) {}

    // 同时保护 screen 相关属性(某些检测用 screen.availWidth vs outerWidth)
    try {
        const origAvailWidth = screen.availWidth;
        const origAvailHeight = screen.availHeight;
        Object.defineProperty(screen, 'availWidth', {
            get: function() { return window.innerWidth; },
            configurable: false
        });
        Object.defineProperty(screen, 'availHeight', {
            get: function() { return window.innerHeight; },
            configurable: false
        });
    } catch(e) {}

    // --- 3) Navigation API 终极拦截(Chrome 102+,能捕获所有导航方式) ---
    try {
        if (window.navigation) {
            window.navigation.addEventListener('navigate', function(e) {
                if (e.destination && isBadUrl(e.destination.url)) {
                    console.log('[Anti-DD/P0] Navigation API 拦截:', e.destination.url);
                    e.preventDefault();
                }
            });
        }
    } catch(e) {}

    // --- 4) 锁定 window.location 赋值 ---
    // 注意:现代浏览器中 window.location 是 unforgeable 属性,defineProperty 可能失败
    // 但 Location.prototype.href 的 hook 已经能捕获 window.location.href = url
    // 这里做额外尝试,失败也没关系
    ['window', 'self', 'top', 'parent'].forEach(function(name) {
        try {
            var obj = window[name === 'window' ? name : name];
            if (name === 'top') obj = window.top;
            if (name === 'self') obj = window.self;
            if (name === 'parent') obj = window.parent;
            if (!obj) return;
            var loc = obj.location;
            Object.defineProperty(obj, 'location', {
                get: function() { return loc; },
                set: function(url) {
                    if (isBadUrl(url)) {
                        console.log('[Anti-DD/P0] 阻止 ' + name + '.location =', url);
                        return;
                    }
                    loc.href = url;
                },
                configurable: false
            });
        } catch(e) {}
    });

    // --- 5) 拦截 window.open ---
    var _open = window.open;
    window.open = function(url) {
        if (isBadUrl(url)) {
            console.log('[Anti-DD/P0] 阻止 window.open:', url);
            return null;
        }
        return _open.apply(this, arguments);
    };

    console.log('[Anti-DD/P0] Phase 0 反跳转保护已激活');
})();


// ================================================================
// 【Phase 1】页面注入方式 —— 完整的反调试 + 事件恢复
// ================================================================
const injectedCode = `(function() {
    'use strict';

    // ============================================================
    // 保存原生方法引用
    // ============================================================
    const _addEventListener = EventTarget.prototype.addEventListener;
    const _stopImmediatePropagation = Event.prototype.stopImmediatePropagation;
    const _setTimeout = window.setTimeout;
    const _setInterval = window.setInterval;

    // ============================================================
    // 恶意 URL 判定(与 Phase 0 一致)
    // ============================================================
    const BAD_DOMAINS = ['baidu.com', 'google.com', 'bing.com', 'theajack.github.io', '404.html', 'about:blank'];
    const isBadUrl = (url) => {
        if (!url) return false;
        const s = String(url).toLowerCase();
        return BAD_DOMAINS.some(d => s.includes(d));
    };

    // ============================================================
    // 【事件层】强制恢复 F12 和右键菜单
    // ============================================================

    const isDevToolsKey = (e) => {
        if (!e) return false;
        if (e.keyCode === 123 || e.key === 'F12') return true;
        if (e.ctrlKey && e.shiftKey && [73, 74, 67].includes(e.keyCode)) return true;
        if (e.ctrlKey && !e.shiftKey && !e.altKey && e.keyCode === 85) return true;
        if (e.keyCode === 116) return true;
        return false;
    };

    // 捕获阶段注册(最高优先级)
    _addEventListener.call(window, 'contextmenu', function(e) {
        _stopImmediatePropagation.call(e);
    }, true);

    ['keydown', 'keyup', 'keypress'].forEach(function(evtType) {
        _addEventListener.call(window, evtType, function(e) {
            if (isDevToolsKey(e)) {
                _stopImmediatePropagation.call(e);
            }
        }, true);
    });

    _addEventListener.call(window, 'selectstart', function(e) {
        _stopImmediatePropagation.call(e);
    }, true);

    _addEventListener.call(window, 'copy', function(e) {
        _stopImmediatePropagation.call(e);
    }, true);

    // ============================================================
    // 锁定 on* 属性
    // ============================================================
    const lockEventProps = (obj, name) => {
        ['oncontextmenu', 'onkeydown', 'onkeyup', 'onkeypress', 'onselectstart', 'oncopy', 'ondragstart'].forEach(prop => {
            try {
                Object.defineProperty(obj, prop, {
                    get: () => null,
                    set: () => true,
                    configurable: false
                });
            } catch(e) {}
        });
    };
    lockEventProps(document, 'document');
    lockEventProps(window, 'window');

    const lockBody = () => {
        if (document.body) lockEventProps(document.body, 'body');
    };

    // ============================================================
    // 拦截 addEventListener 注册
    // ============================================================
    const BLOCKED_EVENTS = new Set(['contextmenu', 'selectstart', 'copy', 'dragstart']);
    const KEYBOARD_EVENTS = new Set(['keydown', 'keyup', 'keypress']);

    EventTarget.prototype.addEventListener = function(type, listener, options) {
        if (BLOCKED_EVENTS.has(type)) {
            return;
        }
        if (KEYBOARD_EVENTS.has(type)) {
            const origListener = listener;
            const wrappedListener = function(e) {
                if (isDevToolsKey(e)) {
                    const origPD = e.preventDefault;
                    e.preventDefault = function() {};
                    e.returnValue = true;
                    try {
                        if (typeof origListener === 'function') origListener.call(this, e);
                        else if (origListener && typeof origListener.handleEvent === 'function') origListener.handleEvent(e);
                    } catch(ex) {}
                    e.preventDefault = origPD;
                    return;
                }
                if (typeof origListener === 'function') return origListener.call(this, e);
                else if (origListener && typeof origListener.handleEvent === 'function') return origListener.handleEvent(e);
            };
            return _addEventListener.call(this, type, wrappedListener, options);
        }
        return _addEventListener.call(this, type, listener, options);
    };

    // ============================================================
    // CSS 覆盖
    // ============================================================
    const injectCSS = () => {
        const style = document.createElement('style');
        style.textContent = \`
            html, body, * {
                -webkit-user-select: text !important;
                -moz-user-select: text !important;
                user-select: text !important;
            }
        \`;
        (document.head || document.documentElement).appendChild(style);
    };

    // ============================================================
    // MutationObserver 清理内联事件属性
    // ============================================================
    const INLINE_ATTRS = ['oncontextmenu', 'onselectstart', 'ondragstart', 'oncopy', 'onkeydown', 'onkeyup', 'onkeypress'];

    const cleanElement = (el) => {
        if (!el || !el.removeAttribute) return;
        INLINE_ATTRS.forEach(attr => {
            if (el.hasAttribute && el.hasAttribute(attr)) {
                el.removeAttribute(attr);
            }
        });
    };

    const setupCleanupObserver = () => {
        const observer = new MutationObserver((mutations) => {
            for (const m of mutations) {
                for (const node of m.addedNodes) {
                    if (node.nodeType === 1) {
                        cleanElement(node);
                        if (node.querySelectorAll) {
                            node.querySelectorAll('[oncontextmenu],[onselectstart],[ondragstart],[oncopy],[onkeydown]').forEach(cleanElement);
                        }
                    }
                }
                if (m.type === 'attributes' && INLINE_ATTRS.includes(m.attributeName)) {
                    m.target.removeAttribute(m.attributeName);
                }
            }
        });
        const target = document.documentElement || document;
        observer.observe(target, { childList: true, subtree: true, attributes: true, attributeFilter: INLINE_ATTRS });
    };
    setupCleanupObserver();

    // ============================================================
    // 拦截 meta refresh
    // ============================================================
    const blockMetaRefresh = () => {
        const observer = new MutationObserver((mutations) => {
            for (const m of mutations) {
                for (const node of m.addedNodes) {
                    if (node.tagName === 'META') {
                        const equiv = node.getAttribute('http-equiv');
                        const content = node.getAttribute('content') || '';
                        if (equiv && equiv.toLowerCase() === 'refresh' && isBadUrl(content)) {
                            node.remove();
                        }
                    }
                }
            }
        });
        if (document.documentElement) {
            observer.observe(document.documentElement, { childList: true, subtree: true });
        }
    };
    blockMetaRefresh();

    // ============================================================
    // 拦截 alert / confirm
    // ============================================================
    const _alert = window.alert;
    const _confirm = window.confirm;

    window.alert = function(msg) {
        const s = String(msg || '').toLowerCase();
        if (s.includes('devtool') || s.includes('调试') || s.includes('控制台') || s.includes('检测') || s.includes('debug')) {
            return;
        }
        return _alert.call(this, msg);
    };

    window.confirm = function(msg) {
        const s = String(msg || '').toLowerCase();
        if (s.includes('devtool') || s.includes('调试') || s.includes('控制台')) {
            return false;
        }
        return _confirm.call(this, msg);
    };

    // ============================================================
    // 破坏检测机制
    // ============================================================

    // 伪造 Firebug
    try {
        Object.defineProperty(window, 'Firebug', { get: () => undefined, set: () => true, configurable: false });
    } catch(e) {}

    // 锁定 DisableDevtool 全局变量
    const fakeDD = function() { return { success: false }; };
    fakeDD.md5 = () => '';
    fakeDD.version = '0.0.0';
    fakeDD.isRunning = false;
    fakeDD.isSuspend = true;
    fakeDD.config = () => fakeDD;
    fakeDD.close = () => {};
    fakeDD.ondevtoolopen = null;
    fakeDD.ondevtoolclose = null;

    ['DisableDevtool', 'disableDevtool', 'DISABLE_DEVTOOL', 'dd', 'devtoolsDetector', '__DISABLE_DEVTOOL__'].forEach(name => {
        try {
            Object.defineProperty(window, name, { get: () => fakeDD, set: () => true, configurable: false });
        } catch(e) {}
    });

    // 拦截 Function / eval 中的 debugger
    const _Function = window.Function;
    window.Function = function(...args) {
        const code = args[args.length - 1];
        if (typeof code === 'string' && /debugger/.test(code)) {
            args[args.length - 1] = code.replace(/\\bdebugger\\b/g, '');
        }
        return _Function.apply(this, args);
    };
    window.Function.prototype = _Function.prototype;
    try { window.Function.prototype.constructor = window.Function; } catch(e) {}

    const _eval = window.eval;
    window.eval = function(code) {
        if (typeof code === 'string' && /debugger/.test(code)) {
            code = code.replace(/\\bdebugger\\b/g, '');
        }
        return _eval.call(this, code);
    };

    // 拦截 setInterval / setTimeout 字符串 debugger
    window.setInterval = function(fn, delay) {
        if (typeof fn === 'string' && fn.includes('debugger')) return 0;
        return _setInterval.apply(this, arguments);
    };
    window.setTimeout = function(fn, delay) {
        if (typeof fn === 'string' && fn.includes('debugger')) return 0;
        return _setTimeout.apply(this, arguments);
    };

    // 阻止 console.clear
    console.clear = function() {};

    // 保护 toString 方法
    try {
        Object.defineProperty(RegExp.prototype, 'toString', { value: RegExp.prototype.toString, writable: false, configurable: false });
        Object.defineProperty(Date.prototype, 'toString', { value: Date.prototype.toString, writable: false, configurable: false });
    } catch(e) {}

    // ============================================================
    // 特征库 & 脚本拦截
    // ============================================================
    const CONFIG = { enableBlock: true, threshold: 4 };

    const FEATURES = {
        urls: [
            'disable-devtool', 'disable_devtool', 'disabledevtool',
            'anti-debug', 'anti_debug', 'devtools-detect',
            'devtools-detector', 'console-ban'
        ],
        codes: [
            [/DisableDevtool/i, 3, 'DisableDevtool'],
            [/theajack\\.github\\.io/i, 5, '官方地址'],
            [/ondevtoolopen/i, 3, 'ondevtoolopen'],
            [/ondevtoolclose/i, 2, 'ondevtoolclose'],
            [/isDevToolOpened/i, 2, 'isDevToolOpened'],
            [/clearIntervalWhenDevOpenTrigger/i, 5, '特有函数'],
            [/outerWidth\\s*-\\s*innerWidth/i, 2, '尺寸检测'],
            [/outerHeight\\s*-\\s*innerHeight/i, 2, '高度检测'],
            [/RegToString|FuncToString|DateToString/i, 3, 'ToString检测'],
            [/DefineId|DebugLib/i, 2, 'DefineId'],
            [/Function\\s*\\(\\s*["']debugger["']\\s*\\)/, 3, 'Function debugger'],
            [/setInterval[\\s\\S]{0,100}debugger/, 2, 'setInterval debugger'],
            [/eruda|vconsole/i, 1, '调试工具检测'],
            [/location\\s*[.=][\\s\\S]{0,30}(baidu|google|bing)\\.com/i, 3, '跳转检测'],
            [/oncontextmenu\\s*=\\s*(null|false)/i, 1, '右键禁用'],
            [/keyCode\\s*={2,3}\\s*123/i, 2, 'F12检测'],
            [/devtools/i, 1, 'devtools关键词'],
            [/console\\.clear/i, 1, 'console.clear'],
            [/\\b__DEVTOOLS/i, 2, 'DEVTOOLS变量']
        ]
    };

    const DATA = { scripts: [], blocked: [], cache: new Map(), count: 0 };

    const getName = (url) => {
        if (!url) return '(inline)';
        try { return new URL(url).pathname.split('/').pop() || url; }
        catch { return url.split('/').pop() || url; }
    };

    const getStack = () => {
        try { throw new Error(); }
        catch (e) { return (e.stack || '').split('\\n').slice(3, 7).join('\\n'); }
    };

    const analyze = (code, url) => {
        const key = url || (code ? code.slice(0, 100) : '');
        if (DATA.cache.has(key)) return DATA.cache.get(key);
        const result = { dangerous: false, score: 0, matches: [] };
        if (url) {
            const lower = url.toLowerCase();
            for (const kw of FEATURES.urls) {
                if (lower.includes(kw)) { result.score += 5; result.matches.push({ name: 'URL:' + kw, weight: 5 }); break; }
            }
        }
        if (code && typeof code === 'string') {
            for (const [regex, weight, name] of FEATURES.codes) {
                if (regex.test(code)) { result.score += weight; result.matches.push({ name, weight }); }
            }
        }
        result.dangerous = result.score >= CONFIG.threshold;
        DATA.cache.set(key, result);
        return result;
    };

    const record = (url, code, method, stack) => {
        const analysis = analyze(code || '', url || '');
        const entry = { id: ++DATA.count, url: url || '', name: getName(url), method, analysis, blocked: false, time: Date.now() };
        DATA.scripts.push(entry);
        return entry;
    };

    // ============================================================
    // 拦截脚本加载
    // ============================================================
    const O = {
        createElement: Document.prototype.createElement,
        appendChild: Element.prototype.appendChild,
        insertBefore: Element.prototype.insertBefore,
        append: Element.prototype.append,
        prepend: Element.prototype.prepend,
        setAttribute: Element.prototype.setAttribute,
        innerHTML: Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML'),
        write: Document.prototype.write,
        writeln: Document.prototype.writeln
    };

    Document.prototype.createElement = function(tag, opts) {
        const el = O.createElement.call(this, tag, opts);
        if (tag && tag.toLowerCase() === 'script') {
            const stack = getStack();
            let _src = '';
            Object.defineProperty(el, 'src', {
                get: () => _src,
                set: (url) => {
                    const a = analyze('', url);
                    const r = record(url, '', 'src', stack);
                    if (CONFIG.enableBlock && a.dangerous) {
                        r.blocked = true; DATA.blocked.push(r);
                        console.log('[Anti-DD] 拦截脚本:', getName(url));
                        return;
                    }
                    _src = url;
                    O.setAttribute.call(el, 'src', url);
                },
                configurable: true
            });
            el._stack = stack;
        }
        return el;
    };

    const interceptInsert = (orig, name) => function(...args) {
        for (const node of args) {
            if (node && node.tagName === 'SCRIPT') {
                const url = node.src || (node.getAttribute && node.getAttribute('src')) || '';
                const code = node.textContent || node.innerHTML || '';
                const a = analyze(code, url);
                const r = record(url, code, name, node._stack || getStack());
                if (CONFIG.enableBlock && a.dangerous) {
                    r.blocked = true; DATA.blocked.push(r);
                    console.log('[Anti-DD] 拦截[' + name + ']:', r.name);
                    return node;
                }
            }
        }
        return orig.apply(this, args);
    };

    Element.prototype.appendChild = interceptInsert(O.appendChild, 'appendChild');
    Element.prototype.insertBefore = interceptInsert(O.insertBefore, 'insertBefore');
    if (O.append) Element.prototype.append = interceptInsert(O.append, 'append');
    if (O.prepend) Element.prototype.prepend = interceptInsert(O.prepend, 'prepend');

    if (O.innerHTML && O.innerHTML.set) {
        Object.defineProperty(Element.prototype, 'innerHTML', {
            get: O.innerHTML.get,
            set: function(html) {
                if (typeof html === 'string' && /<script/i.test(html)) {
                    const matches = html.match(/<script[^>]*>[\\s\\S]*?<\\/script>/gi) || [];
                    for (const m of matches) {
                        const srcMatch = m.match(/src=["']([^"']+)["']/i);
                        const url = srcMatch ? srcMatch[1] : '';
                        const codeMatch = m.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/i);
                        const code = codeMatch ? codeMatch[1] : '';
                        const a = analyze(code, url);
                        const r = record(url, code, 'innerHTML', getStack());
                        if (CONFIG.enableBlock && a.dangerous) {
                            r.blocked = true; DATA.blocked.push(r);
                            html = html.replace(m, '<!-- blocked -->');
                        }
                    }
                }
                return O.innerHTML.set.call(this, html);
            },
            configurable: true, enumerable: true
        });
    }

    const interceptWrite = (orig, name) => function(html) {
        if (typeof html === 'string' && /<script/i.test(html)) {
            const matches = html.match(/<script[^>]*>[\\s\\S]*?<\\/script>/gi) || [];
            for (const m of matches) {
                const srcMatch = m.match(/src=["']([^"']+)["']/i);
                const url = srcMatch ? srcMatch[1] : '';
                const codeMatch = m.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/i);
                const code = codeMatch ? codeMatch[1] : '';
                const a = analyze(code, url);
                const r = record(url, code, name, getStack());
                if (CONFIG.enableBlock && a.dangerous) {
                    r.blocked = true; DATA.blocked.push(r);
                    html = html.replace(m, '');
                }
            }
        }
        return orig.call(this, html);
    };

    Document.prototype.write = interceptWrite(O.write, 'write');
    Document.prototype.writeln = interceptWrite(O.writeln, 'writeln');

    // MutationObserver 拦截脚本
    const setupScriptObserver = () => {
        const observer = new MutationObserver((mutations) => {
            for (const m of mutations) {
                for (const node of m.addedNodes) {
                    if (node.tagName === 'SCRIPT' && !node._tracked) {
                        node._tracked = true;
                        const url = node.src || '';
                        const code = node.textContent || '';
                        const a = analyze(code, url);
                        const r = record(url, code, 'Observer', '');
                        if (CONFIG.enableBlock && a.dangerous) {
                            r.blocked = true; DATA.blocked.push(r);
                            node.remove();
                        }
                    }
                }
            }
        });
        if (document.documentElement) {
            observer.observe(document.documentElement, { childList: true, subtree: true });
        } else {
            document.addEventListener('DOMContentLoaded', () => {
                observer.observe(document.documentElement, { childList: true, subtree: true });
            });
        }
    };
    setupScriptObserver();

    // ============================================================
    // DOMContentLoaded 清理
    // ============================================================
    const cleanup = () => {
        lockBody();
        injectCSS();
        document.querySelectorAll('[oncontextmenu],[onselectstart],[ondragstart],[oncopy],[onkeydown],[onkeyup],[onkeypress]').forEach(cleanElement);
        cleanElement(document.body);
        cleanElement(document.documentElement);
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', cleanup);
    } else {
        cleanup();
    }
    _setTimeout.call(window, cleanup, 100);
    _setTimeout.call(window, cleanup, 500);
    _setTimeout.call(window, cleanup, 2000);
    _setTimeout.call(window, cleanup, 5000);

    console.log('[Anti-DD] v12.0.0 Phase 1 已启动');
    window._AntiDD = { version: '12.0.0', config: CONFIG, data: DATA, analyze };
})();`;

// 注入到页面
const script = document.createElement('script');
script.textContent = injectedCode;

if (document.documentElement) {
    document.documentElement.insertBefore(script, document.documentElement.firstChild);
} else {
    const observer = new MutationObserver(() => {
        if (document.documentElement) {
            document.documentElement.insertBefore(script, document.documentElement.firstChild);
            observer.disconnect();
        }
    });
    observer.observe(document, { childList: true });
}