Chrome扩展程序:检查是否已注入内容脚本

时间:2015-12-30 11:36:03

标签: javascript google-chrome google-chrome-extension

我正在开发Chrome扩展程序。我没有使用manifest.json来匹配所有网址的内容脚本,而是在用户点击扩展程序图标时,通过调用chrome.tabs.executeScript来懒惰地注入内容脚本。

我正在尝试避免多次执行脚本。所以我在我的内容脚本中有以下代码:

if (!window.ALREADY_INJECTED_FLAG) {
    window.ALREADY_INJECTED_FLAG = true
    init() // <- All side effects go here
}

问题#1 ,每次点击扩展程序图标时,这是否足以安全地呼叫chrome.tabs.executeScript?换句话说,这是幂等的吗?

问题#2 chrome.tabs.insertCSS有类似的方法吗?

似乎无法在backgroud脚本中检查内容脚本注入状态,因为它无法访问网页的DOM。我已经尝试了ping / pong方法来检查任何内容脚本实例是否存活。但这会带来设计ping超时的开销和复杂性。

问题#3 ,是后台脚本检查内容脚本注入状态的更好方法,所以每次用户点击图标时我都可以阻止调用chrome.tabs.executeScript

提前致谢!

3 个答案:

答案 0 :(得分:9)

  

这足够安全,可以天真地每次呼叫chrome.tabs.executeScript   点击扩展图标的时间?换句话说,就是这样   幂等?

  1. 是的,除非您的内容脚本修改了页面的DOM并且重新加载了扩展名(通过设置页面,通过更新等重新加载)。在这种情况下,您的旧内容脚本将不再在扩展程序的上下文中运行,因此它不能使用扩展API,也不能直接与您的扩展程序通信。
  2.   

    chrome.tabs.insertCSS是否有类似的方法?

    1. 不,chrome.tabs.insertCSS没有任何包容性保护。但是再次插入相同的样式表并不会改变页面的外观,因为所有规则都具有相同的CSS特性,并且在这种情况下最后一个样式表优先。但是如果样式表与您的扩展名紧密耦合,那么您可以使用executeScript简单地注入脚本,检查它是否是第一次注入,如果是,请插入样式表(参见下面的示例)。
    2.   

      任何更好的后台脚本方法来检查注入状态   内容脚本,所以我可以阻止调用   每次用户点击图标时都会chrome.tabs.executeScript

      1. 您可以向选项卡(chrome.tabs.sendMessage)发送消息,如果您没有收到回复,请假设选项卡中没有内容脚本并插入内容脚本。
      2. 2

        的代码示例

        在弹出/后台脚本中:

        chrome.tabs.executeScript(tabId, {
            file: 'contentscript.js',
        }, function(results) {
            if (chrome.runtime.lastError || !results || !results.length) {
                return;  // Permission error, tab closed, etc.
            }
            if (results[0] !== true) {
                // Not already inserted before, do your thing, e.g. add your CSS:
                chrome.tabs.insertCSS(tabId, { file: 'yourstylesheet.css' });
            }
        });
        

        contentscript.js:

        // Wrapping in a function to not leak/modify variables if the script
        // was already inserted before.
        (function() {
            if (window.hasRun === true)
                return true;  // Will ultimately be passed back to executeScript
            window.hasRun = true;
            // rest of code ... 
            // No return value here, so the return value is "undefined" (without quotes).
        })(); // <-- Invoke function. The return value is passed back to executeScript
        

        注意,明确检查window.hasRun的值(上例中的true)非常重要,否则它可以是id="hasRun"的DOM元素的自动创建的全局变量属性,请参阅Is there a spec that the id of elements should be made global variable?

答案 1 :(得分:4)

Rob W的选项3对我很有帮助。基本上后台脚本会ping内容脚本,如果没有响应,它将添加所有必需的文件。我只在激活选项卡时执行此操作,以避免必须添加到后台中的每个打开选项卡的复杂性:

<强> background.js

chrome.tabs.onActivated.addListener(function(activeInfo){
  tabId = activeInfo.tabId

  chrome.tabs.sendMessage(tabId, {text: "are_you_there_content_script?"}, function(msg) {
    msg = msg || {};
    if (msg.status != 'yes') {
      chrome.tabs.insertCSS(tabId, {file: "css/mystyle.css"});
      chrome.tabs.executeScript(tabId, {file: "js/content.js"});
    }
  });
});

<强> content.js

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
    if (msg.text === 'are_you_there_content_script?') {
      sendResponse({status: "yes"});
    }
});

答案 2 :(得分:1)

只是对Rob的好回答的旁注。

我发现Pocket的Chrome扩展程序使用了类似的方法。在动态注入的脚本中:

if (window.thePKT_BM)
    window.thePKT_BM.save();
else {
    var PKT_BM_OVERLAY = function(a) {
        // ... tons of code
    },
    $(document).ready(function() {
        if (!window.thePKT_BM) {
            var a = new PKT_BM;
            window.thePKT_BM = a,
            a.init()
        }
        window.thePKT_BM.save()
    }
    )
}