You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Chrome扩展Service Worker阻止网页检测标签页切换?

问题

需要编写一段JavaScript代码,阻止网页检测到用户切换出标签页。需通过Chrome扩展向网页注入代码,但网页在切换标签或应用时会暂停所有JavaScript执行,持续发送消息这类常规方案无效。要求如下:

  • 不允许进行DOM操作(如重写document.visibilityState);
  • 必须在Service Worker文件中实现,不得修改contentScript.js。

以下是不可修改的contentScript.js代码:

function start() {
    // This function is called when the extension is now running on the webpage
    console.log("contentScript.js started")

    // Initialize list to contain logged timestamps
    let loggedTimestamps = [new Date()]

    // Get output list element
    const outputList = document.getElementById("output-stream-list")

    // Get status element
    const statusElement = document.getElementById("status")

    const log = () => {
        // Grab the current visibility state
        const visibilityState = document.visibilityState
        const isTabActive = visibilityState === 'visible'
        if (!isTabActive) {
            // Tab is not active, do not log
            return
        }

        // Get current local time 
        const now = new Date()

        // Set threshold for gap
        const threshold = 1400 // with error margin

        // Check if last timestamp was too long ago
        const lastDiff = now - loggedTimestamps[loggedTimestamps.length - 1]
        if (lastDiff > threshold) {
            // Create new list item and prepend to list
            const listItem = document.createElement("li")
            listItem.appendChild(document.createTextNode(`Tab not visible for ${lastDiff / 1000} seconds! :(`))
            outputList.prepend(listItem)
        }

        // Create new list item and prepend to list
        const logText = `${visibilityState} ${now.toLocaleTimeString()}`
        const listItem = document.createElement("li")
        listItem.appendChild(document.createTextNode(logText))
        outputList.prepend(listItem)

        // Log timestamp
        loggedTimestamps.push(now)

        // Determine if the logged timestamps have been continuous (ie. no gapps greater than threshold)
        let success = true
        for (let i = 1; i < loggedTimestamps.length; i++) {
            const diff = loggedTimestamps[i] - loggedTimestamps[i - 1]

            if (diff > threshold) {
                success = false
                break
            }
        }

        // Set status
        statusElement.innerText = success ? "Tab has always been active :)" : "Tab became inactive :("
        statusElement.style.color = success ? "green" : "red"
    }

    // Log every second
    setInterval(log, 1000)
}

start()

注意:background.js作为运行在非窗口环境的Service Worker,发送持续消息、操作焦点元素、DOM操作这类常规方案均无效,请问该如何实现需求?


解决方案

核心思路是利用Chrome扩展的调试协议(Chrome DevTools Protocol),通过Service Worker主动控制页面上下文的代码执行与时间生成,绕过页面后台时JS暂停的限制,确保页面的时间戳检测不会触发 inactive 判定。

实现代码(background.js)

// 存储当前活跃标签ID与调试会话ID
let activeTabId = null;
let debuggerSessionId = null;
let simulationInterval = null;

// 监听标签激活状态切换
chrome.tabs.onActivated.addListener(async (activeInfo) => {
  if (activeTabId !== null && debuggerSessionId !== null) {
    await chrome.debugger.detach({ tabId: activeTabId });
    debuggerSessionId = null;
  }
  activeTabId = activeInfo.tabId;
});

// 监听标签可见性与加载状态变化
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (tabId !== activeTabId) return;

  if (changeInfo.status === 'complete' && tab.active) {
    await attachDebugger(tabId);
  } else if (!tab.active && debuggerSessionId) {
    startSimulatingLogs(tabId);
  } else if (tab.active) {
    stopSimulatingLogs();
  }
});

// 附加调试器到目标标签
async function attachDebugger(tabId) {
  try {
    await chrome.debugger.attach({ tabId }, '1.3');
    debuggerSessionId = tabId;
  } catch (e) {
    console.error('调试器附加失败:', e);
  }
}

// 模拟页面log函数执行,维持时间戳间隔
async function startSimulatingLogs(tabId) {
  if (simulationInterval) return;

  simulationInterval = setInterval(async () => {
    try {
      await chrome.debugger.sendCommand(
        { tabId },
        'Runtime.evaluate',
        {
          expression: `
            const lastTs = loggedTimestamps[loggedTimestamps.length - 1];
            const newTs = new Date(lastTs.getTime() + 1000);
            // 临时覆盖Date构造函数,确保时间间隔稳定
            const originalDate = Date;
            Date = () => newTs;
            Date.now = () => newTs.getTime();
            // 执行页面log函数
            log();
            // 恢复原始Date构造
            Date = originalDate;
          `,
          awaitPromise: true
        }
      );
    } catch (e) {
      console.error('模拟log执行失败:', e);
    }
  }, 1000);
}

// 停止模拟,恢复页面正常状态
function stopSimulatingLogs() {
  if (simulationInterval) {
    clearInterval(simulationInterval);
    simulationInterval = null;
  }
}

// 扩展卸载时清理调试会话
chrome.runtime.onSuspend.addListener(async () => {
  if (debuggerSessionId) {
    await chrome.debugger.detach({ tabId: activeTabId });
  }
});

配置说明

需要在manifest.json中声明必要权限:

{
  "manifest_version": 3,
  "name": "Prevent Tab Inactivity Detection",
  "version": "1.0",
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["tabs", "debugger"],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["contentScript.js"]
    }
  ]
}

方案优势

  • 完全符合要求:无需修改contentScript.js,无DOM操作,所有逻辑在Service Worker中实现;
  • 绕过页面后台限制:通过调试协议直接在页面上下文执行代码,不受页面JS暂停影响;
  • 精准控制时间:临时覆盖Date构造函数,确保时间戳间隔始终为1000ms,不会触发阈值检测。

内容的提问来源于stack exchange,提问作者moiz khuzema

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 17:05:01