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

Chrome扩展:弹窗切换按钮控制Content脚本及驻留问题

Chrome扩展:解决标签页闲置后content.js失效及事件重复绑定问题

需求与已实现功能

我正在开发首个Chrome扩展,目标是通过弹窗中的切换按钮,控制当前域名下content.js的执行(而非完全禁用扩展)。目前已实现以下功能:

  • 从弹窗切换当前域名的扩展启用/禁用状态;
  • 通过chrome.storage.sync存储状态,重装扩展后无需重复设置;
  • 切换状态时同步更新同域名其他标签页;
  • 实时切换扩展图标(禁用时显示灰色图标);
  • 重新打开网站时自动恢复状态与对应图标。

现有代码

const toggleBtn = document.querySelector(".toggle");

toggleBtn.addEventListener("click", function(){
    if(toggleBtn.checked === true){
        console.log('inject content.js');
    }else{
        console.log('remove content.js');
    }
});

注入脚本方式

chrome.scripting.executeScript({
    target: {tabId},
    files: ['content.js'],
});

background.js

const icons = {
    enabled: {
        '16': 'icon-16.png',
        '19': 'icon-19.png',
        '38': 'icon-38.png',
        '48': 'icon-48.png',
        '128': 'icon-128.png'
    },
    disabled: {
        '16': 'icon-16-off.png',
        '19': 'icon-19-off.png',
        '38': 'icon-38-off.png',
        '48': 'icon-48-off.png',
        '128': 'icon-128-off.png'
    }
};

const getExtensionStatus = host => new Promise((resolve, reject) => {
    chrome.storage.sync.get(host, result => {
        if (result[host] !== undefined) {
            resolve(result[host]);
        } else {
            setExtensionStatus(host, true);
        }
    });
});

const setExtensionStatus = (host, toggleBtnStatus) => {
    const data = { [host]: toggleBtnStatus };
    chrome.storage.sync.set(data, () => {});
};


chrome.runtime.onInstalled.addListener((details) => {
    if (details.reason === "install") {
        chrome.action.setIcon({ path: icons.enabled });
    }
});

const init = async tab => {
    const url = new URL(tab.url);
    if (url.protocol !== "chrome-extension:") {
        const host = url.host;
        const status = await getExtensionStatus(host);
        const icon = status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });
    }
};

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === "complete") init(tab);
});

chrome.tabs.onActivated.addListener(info => {
    chrome.tabs.get(info.tabId, init);
});

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
    if (request.type === "getExtensionStatus") {
        const status = await getExtensionStatus(request.host);
        sendResponse({ status });
        return true;
    } else if (request.type === "setExtensionStatus") {
        setExtensionStatus(request.host, request.status);
        const icon = request.status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });
    }
});
document.addEventListener("DOMContentLoaded", function () {
    const toggleBtn = document.querySelector(".toggle");

    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        const host = new URL(tabs[0].url).host;

        chrome.runtime.sendMessage({ type: "getExtensionStatus", host }, (response) => {
            toggleBtn.checked = response.status;
        });

        toggleBtn.addEventListener("click", () => {
            chrome.runtime.sendMessage({ type: "setExtensionStatus", host, status: toggleBtn.checked });
        });
    });
});

content.js

document.addEventListener("keydown", listenEnterkey);

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.type === "setExtensionStatus") {
        console.log(request.status);
        if (request.status) {
            // Add event listener here
            document.addEventListener("keydown", listenEnterkey);
        } else {
            // Remove event listener here
            document.removeEventListener("keydown", listenEnterkey);
        }
    }
});

当前问题

标签页闲置一段时间后content.js失效,如何重新注入脚本且避免事件重复绑定?

解决方案

1. 强化background.js的脚本注入与状态同步

修改background.js中的init函数,在标签页加载/激活时自动检查并注入content.js,同时同步当前域名的状态:

const init = async tab => {
    const url = new URL(tab.url);
    if (url.protocol !== "chrome-extension:") {
        const host = url.host;
        const status = await getExtensionStatus(host);
        const icon = status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });

        // 强制注入content.js,确保脚本存在
        chrome.scripting.executeScript({
            target: { tabId: tab.id },
            files: ['content.js'],
            injectImmediately: true // 跳过文档加载等待,立即注入
        }).catch(err => console.log('注入失败:', err));

        // 向content.js发送当前状态
        chrome.tabs.sendMessage(tab.id, {
            type: "syncStatus",
            status: status
        }).catch(err => console.log('同步状态失败:', err));
    }
};

2. 改造content.js,避免重复绑定事件

在content.js中添加初始化标记,确保事件只绑定一次,同时通过消息监听动态同步状态:

let isInitialized = false;
let isEnabled = true;

function listenEnterkey(e) {
    // 替换为你的实际按键处理逻辑
    console.log('Enter键触发:', e);
}

// 初始化函数:仅执行一次事件绑定
function init(status) {
    if (!isInitialized) {
        isEnabled = status;
        if (isEnabled) document.addEventListener("keydown", listenEnterkey);
        isInitialized = true;
    } else {
        updateStatus(status);
    }
}

// 更新状态:动态添加/移除事件监听
function updateStatus(status) {
    isEnabled = status;
    if (isEnabled) {
        document.addEventListener("keydown", listenEnterkey);
    } else {
        document.removeEventListener("keydown", listenEnterkey);
    }
}

// 接收外部消息
chrome.runtime.onMessage.addListener((request) => {
    switch(request.type) {
        case "syncStatus":
            init(request.status);
            break;
        case "setExtensionStatus":
            updateStatus(request.status);
            break;
    }
});

// 页面加载时主动请求当前状态
chrome.runtime.sendMessage(
    { type: "getExtensionStatus", host: window.location.host },
    (response) => init(response.status)
);

3. 优化状态同步逻辑,覆盖所有同域名标签页

修改background.js中的消息监听,当切换状态时,自动同步所有同域名标签页,若脚本失效则重新注入:

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
    if (request.type === "getExtensionStatus") {
        const status = await getExtensionStatus(request.host);
        sendResponse({ status });
        return true;
    } else if (request.type === "setExtensionStatus") {
        setExtensionStatus(request.host, request.status);
        const icon = request.status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });

        // 同步所有同域名标签页的状态
        chrome.tabs.query({ url: `*://${request.host}/*` }, (tabs) => {
            tabs.forEach(tab => {
                chrome.tabs.sendMessage(tab.id, {
                    type: "setExtensionStatus",
                    status: request.status
                }).catch(() => {
                    // 若消息发送失败,重新注入脚本后再同步
                    chrome.scripting.executeScript({
                        target: { tabId: tab.id },
                        files: ['content.js'],
                        injectImmediately: true
                    }).then(() => {
                        chrome.tabs.sendMessage(tab.id, {
                            type: "setExtensionStatus",
                            status: request.status
                        });
                    });
                });
            });
        });
    }
});

4. 利用Chrome标签页事件自动恢复

Chrome标签页闲置唤醒时会触发tabs.onActivated事件,此时init函数会自动执行,重新注入脚本并同步状态,确保content.js恢复正常工作。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 15:25:24