油猴脚本阻止页面脚本加载:hook appendChild 与 createElement

执行时机:document-start

油猴脚本的执行时机由 @run-at 控制,document-start 是最早的时机,在浏览器开始解析 HTML 前执行:

// ==UserScript==
// @name         Block Scripts
// @match        *://*/*
// @run-at       document-start
// ==/UserScript==

此时 document.body 还不存在,但 DOM API 已可用,可以提前 hook 原型方法。

方案1:hook appendChild 拦截动态 script

// ==UserScript==
// @name         Block Dynamic Scripts
// @match        *://example.com/*
// @run-at       document-start
// ==/UserScript==

(function () {
    const rawAppend = Element.prototype.appendChild;

    Element.prototype.appendChild = function (el) {
        if (el.tagName === 'SCRIPT') {
            const src = el.src || '(inline)';
            console.log('[blocked]', src);

            // 返回 el 但不真正追加,阻止加载
            return el;
        }
        return rawAppend.call(this, el);
    };
})();

rawAppend 保存原始方法,避免无限递归。只拦截 SCRIPT 标签,其他元素正常追加。

方案2:hook createElement,在 src 赋值时拦截

部分页面通过 createElement('script') 创建后再设置 src

const rawCreate = document.createElement.bind(document);

document.createElement = function (tag) {
    const el = rawCreate(tag);

    if (tag === 'script') {
        Object.defineProperty(el, 'src', {
            configurable: true,
            set(v) {
                if (v.includes('ads.example.com')) {
                    console.log('[blocked src]', v);
                    return;
                }
                el.setAttribute('src', v);
            },
            get() {
                return el.getAttribute('src') || '';
            }
        });
    }

    return el;
};

Object.defineProperty 劫持 src 的 setter,在 src 被赋值时决定是否放行。

方案3:MutationObserver 监控已插入的 script

上面两种方案针对动态插入。对于 HTML 中静态的 <script> 标签,可以用 MutationObserver 在插入前禁用:

const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
            if (node.tagName === 'SCRIPT' && node.src.includes('tracker.js')) {
                node.type = 'text/blocked';  // 改掉 type,浏览器不执行
                console.log('[neutralized]', node.src);
            }
        }
    }
});

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

type 改为非 JavaScript MIME 类型,浏览器不会执行该脚本但 DOM 中仍有该节点。

三种方案对比

方案拦截时机适用场景
hook appendChild追加时动态 document.body.appendChild(script)
hook createElement srcsrc 赋值时el.src = '...' 模式
MutationObserver节点插入 DOM 后任意方式插入,包括 innerHTML

实际页面可能混合使用多种方式,建议组合 hook。

注意事项

  • document-startdocument.body 为 null,不要访问 DOM 元素
  • hook 原型方法影响所有调用,测试时先在控制台验证
  • 浏览器扩展的 Content Script 比油猴更底层,如需更强的拦截能力可考虑开发扩展