强制链接在当前标签页打开

强制所有链接 (a 标签) 和 JavaScript 的 window.open() 都在当前标签页中打开,并处理鼠标中键点击。

// ==UserScript==
// @name         Force Links in Current Tab
// @name:zh-CN   强制链接在当前标签页打开
// @namespace    http://tampermonkey.net/
// @version      2025.11.01
// @description  Forces all links (a tags) and window.open() to open in the current tab (_self).
// @description:zh-CN  强制所有链接 (a 标签) 和 JavaScript 的 window.open() 都在当前标签页中打开,并处理鼠标中键点击。
// @author       Your Name
// @match        *://*/*
// @grant        none
// @license MIT 
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. 修补 (Patch) window.open ---
    // 必须在 @run-at document-start 模式下运行,以确保在页面上任何脚本之前执行
    const originalWindowOpen = window.open;

    window.open = function(url, target, features, replace) {
        // 打印日志以便调试 (可选)
        // console.log(`[Force Current Tab] window.open called: url=${url}, target=${target}`);

        // 强制 target 为 _self
        // 将参数转换为数组,以便安全修改
        const args = [...arguments];

        if (args.length < 2) {
            // 如果只有 url,默认是新窗口,我们添加 _self
            args[1] = '_self';
        } else {
            // 如果有 target,我们强制覆盖为 _self
            args[1] = '_self';
        }

        // 使用 .apply 来调用原始函数,保持正确的 'this' 上下文和修改后的参数
        return originalWindowOpen.apply(this, args);
    };

    // --- 2. 处理页面上现有的和动态添加的 <a> 标签 ---

    /**
     * 辅助函数:处理单个节点或节点树中的所有 <a> 标签
     * @param {Node} node - 要处理的根节点 (例如 document.body 或新添加的元素)
     */
    const processLinks = (node) => {
        // 确保我们有 querySelectorAll 方法 (即它是一个元素)
        if (!node || typeof node.querySelectorAll !== 'function') {
            return;
        }

        // 查找所有 <a> 标签
        const links = node.querySelectorAll('a');
        links.forEach(link => {
            if (link.target && link.target !== '_self') {
                // console.log(`[Force Current Tab] Fixing link: ${link.href}`);
                link.target = '_self';

                // 移除 rel=noopener 和 rel=noreferrer,因为它们主要用于新标签页
                // link.removeAttribute('rel');
                // 注意:移除 'rel' 可能会破坏某些网站样式或功能,所以保持修改 target 即可
            }
        });
    };

    // --- 3. 页面加载时执行一次 ---
    document.addEventListener('DOMContentLoaded', () => {
        processLinks(document.body);
    });

    // --- 4. 使用 MutationObserver 监视动态添加的 <a> 标签 ---
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(newNode => {
                    // 检查新添加的节点本身是否是 <a>
                    if (newNode.tagName === 'A' && newNode.target && newNode.target !== '_self') {
                        newNode.target = '_self';
                    }
                    // 检查新添加的节点是否 *包含* <a> 标签
                    if (newNode.nodeType === Node.ELEMENT_NODE) {
                        processLinks(newNode);
                    }
                });
            }
        }
    });

    // 等待 body 出现后再开始观察
    // 因为脚本在 document-start 运行,body 可能还不存在
    if (document.body) {
        observer.observe(document.body, { childList: true, subtree: true });
    } else {
        document.addEventListener('DOMContentLoaded', () => {
            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    // --- 5. 拦截鼠标中键点击 (auxclick) ---
    // 这将阻止中键在新标签页中打开链接
    document.addEventListener('auxclick', (e) => {
        if (e.button === 1) { // 1 = 鼠标中键
            const link = e.target.closest('a[href]');
            if (link) {
                // 阻止默认的中键点击行为(在新标签页打开)
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                // 在当前标签页导航
                window.location.href = link.href;
            }
        }
    }, true); // 使用捕获阶段 (true) 来确保在任何其他脚本之前运行

    // --- 6. 拦截普通点击 (作为最后的保险) ---
    // 尽管 MutationObserver 应该已经处理了,
    // 但这个捕获阶段的点击监听器可以捕获到任何“漏网之鱼”。
    document.addEventListener('click', (e) => {
        const link = e.target.closest('a[href]');
        if (link && link.target && link.target !== '_self') {
            // 确保 target 在点击时也被设置为 _self
            link.target = '_self';
        }
    }, true); // 同样使用捕获阶段

})();