在新标签页打开链接(可取消 + 聚焦新页)

强制所有链接和 SPA 路由在新标签页打开并立即聚焦,新页面获得焦点,当前页保持不动。支持按域名禁用。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         在新标签页打开链接(可取消 + 聚焦新页)
// @namespace    http://tampermonkey.net/
// @version      0.0.9
// @description  强制所有链接和 SPA 路由在新标签页打开并立即聚焦,新页面获得焦点,当前页保持不动。支持按域名禁用。
// @author       AvailableForTheWorld + Grok
// @match        *://*/*
// @icon         https://www.svgrepo.com/show/207466/blank-page-list.svg
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// ==/UserScript==

;(function () {
  'use strict'

  // 如果是由本脚本打开的新标签页,直接退出,避免干扰
  if (sessionStorage.getItem('openedByScript') === 'true') {
    sessionStorage.removeItem('openedByScript')
    return
  }

  const currentDomain = window.location.hostname
  const disabledDomains = GM_getValue('disabledDomains', {})
  const isDisabled = !!disabledDomains[currentDomain]

  // === 防重复打开逻辑 ===
  let lastOpenTime = 0
  let lastOpenUrl = ''
  let lastTriggerTime = 0

  function safeOpenInTab(url, options = {}) {
    const now = Date.now()
    if (now - lastOpenTime < 2000 && url === lastOpenUrl) {
      console.log('【新标签页脚本】拦截重复打开:', url)
      return
    }
    lastOpenTime = now
    lastOpenUrl = url

    // 默认立即聚焦新标签页
    const finalOptions = { active: true, ...options }
    GM_openInTab(url, finalOptions)
  }

  // === 注册菜单 ===
  function registerMenuCommands() {
    const isDisabledNow = !!GM_getValue('disabledDomains', {})[currentDomain]
    if (isDisabledNow) {
      GM_registerMenuCommand(`✅ 在此网站启用“新标签页打开”`, () =>
        toggleCurrentDomain(false)
      )
    } else {
      GM_registerMenuCommand(`❌ 在此网站禁用“新标签页打开”`, () =>
        toggleCurrentDomain(true)
      )
    }
    GM_registerMenuCommand('📋 查看已禁用的网站', showDomainManager)
  }

  function toggleCurrentDomain(disable) {
    const obj = GM_getValue('disabledDomains', {})
    if (disable) obj[currentDomain] = true
    else delete obj[currentDomain]
    GM_setValue('disabledDomains', obj)
    if (
      confirm(`${disable ? '已禁用' : '已启用'},需要刷新页面生效。现在刷新?`)
    ) {
      location.reload()
    }
  }

  function showDomainManager() {
    const list = Object.keys(GM_getValue('disabledDomains', {}))
    if (list.length === 0) return alert('没有禁用的网站')
    alert(
      '已禁用的网站:\n\n• ' +
        list.join('\n• ') +
        '\n\n访问对应网站后可重新启用。'
    )
  }

  registerMenuCommands()

  // 如果当前域名被禁用,直接结束
  if (isDisabled) return

  // === 1. 强制普通链接在新标签页打开(并观察动态添加的链接)===
  function setTargetBlank(node) {
    if (node.tagName === 'A' && (!node.target || node.target === '_self')) {
      node.target = '_blank'
      node.rel = 'noopener noreferrer' // 安全 + 性能
    }
  }

  function processLinks() {
    document.querySelectorAll('a').forEach(setTargetBlank)
  }

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((m) => {
      m.addedNodes.forEach((node) => {
        if (node.nodeType !== 1) return
        setTargetBlank(node)
        if (node.querySelectorAll)
          node.querySelectorAll('a').forEach(setTargetBlank)
      })
    })
  })
  observer.observe(document.body, { childList: true, subtree: true })
  processLinks()

  // === 2. 拦截 SPA 路由(pushState / hashchange)并在新标签页打开 + 聚焦 ===
  const origPushState = history.pushState
  const origReplaceState = history.replaceState

  history.pushState = function (state, title, url) {
    if (!url || typeof url !== 'string')
      return origPushState.apply(this, arguments)

    let fullUrl = url
    try {
      fullUrl = new URL(url, location.href).href
    } catch {}

    if (fullUrl === location.href) return origPushState.apply(this, arguments)
    if (Date.now() - lastTriggerTime < 2000) return

    lastTriggerTime = Date.now()
    sessionStorage.setItem('openedByScript', 'true')
    setTimeout(() => safeOpenInTab(fullUrl, { active: true }), 50)

    console.log('【新标签页脚本】拦截 pushState → 新标签页打开并聚焦:', fullUrl)
    // 不调用 origPushState → 当前页彻底不动
  }

  history.replaceState = function (state, title, url) {
    // replaceState 通常只是清理参数,不视为新页面,允许原样执行
    return origReplaceState.apply(this, arguments)
  }

  window.addEventListener('hashchange', (e) => {
    if (Date.now() - lastTriggerTime < 2000) return
    lastTriggerTime = Date.now()
    sessionStorage.setItem('openedByScript', 'true')
    setTimeout(() => safeOpenInTab(e.newURL, { active: true }), 50)
    console.log('【新标签页脚本】hashchange → 新标签页打开并聚焦:', e.newURL)
  })

  // === 3. 拦截 window.open(部分网站使用)===
  const origOpen = window.open
  window.open = function (url, name, features) {
    if (typeof url === 'string' && url) {
      let fullUrl = url
      try {
        fullUrl = new URL(url, location.href).href
      } catch {}
      if (fullUrl !== location.href) {
        sessionStorage.setItem('openedByScript', 'true')
        safeOpenInTab(fullUrl, { active: true })
        return null // 模拟打开成功
      }
    }
    return origOpen.apply(this, arguments)
  }

  console.log(
    '【新标签页强制脚本】已激活(聚焦新页模式) - 当前域名:',
    currentDomain
  )
})()