【问题标题】:Pass a variable from content script to popup将变量从内容脚本传递到弹出窗口
【发布时间】:2015-06-29 09:06:00
【问题描述】:

我正在乱搞(试图学习)如何制作 chrome 扩展。现在我只是在做一个超级简单的,它计算页面上某个单词的实例。我有这部分工作。

我想做的就是将此信息发送给pop,以便我可以用它来做其他事情。

这是我目前所拥有的:

manifest.json

{
    "manifest_version": 2,
    "name": "WeedKiller",
    "description": "Totally serious $100% legit extension",
    "version": "0.1",

    "background": {
        "persistent": false,
        "scripts": ["background.js"]
    },

    "permissions":[
        "tabs",
        "storage"
    ],
    "browser_action": {
    "default_icon": "icon.png",
    "default_title": "WeedKiller",
    "default_popup": "popup.html"
    },
    "content_scripts": [
        {
            "matches": [
                "http://*/*",
                "https://*/*"
            ],
            "js": [
                "content.js"
            ],
            "run_at": "document_end"
        }
    ]
}

content.js

var elements = document.getElementsByTagName('*');
var count = 0;

function tokeCounter(){
    for (var i = 0; i < elements.length; i++) {
        var element = elements[i];

        for (var j = 0; j < element.childNodes.length; j++) {
            var node = element.childNodes[j];

            if (node.nodeType === 3) {
                var text = node.nodeValue;
                if(text == '420'){
                    count++; 
                }

                var replacedText = text.replace(/420/, '+1');

                if (replacedText !== text) {
                    element.replaceChild(document.createTextNode(replacedText), node);
                }
            }
        }    
    }
}

tokeCounter();

所以我想要发生的是将count 变量发送到弹出窗口,以便我可以在那里使用它。

我环顾四周,发现我需要对chrome.runtime.sendMessage 做点什么。

我有,所以我将这一行添加到 content.js 的末尾:

 chrome.runtime.sendMessage(count);

然后在 background.js 中:

chrome.runtime.onMessage.addListener(
    function(response, sender, sendResponse){      
       temp = response;
    }
);

我有点卡在这里,因为我不确定如何将此信息发送到弹出窗口并使用它。

【问题讨论】:

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


    【解决方案1】:

    正如您已经注意到的那样,当弹出窗口关闭时,您无法将数据直接发送到弹出窗口。因此,您正在向后台页面发送数据。

    然后,您打开弹出窗口时,您想要那里的数据。那么,有哪些选择呢?

    请注意:这个答案会先给出不好的建议,然后再改进。由于 OP 正在学习,因此展示思维过程和障碍很重要。


    想到的第一个解决方案如下:询问后台页面,再次使用消息传递。 早期警告:这将不起作用或效果不佳

    首先,确定可以有不同类型的消息。修改您当前的消息代码:

    // content.js
    chrome.runtime.sendMessage({type: "setCount", count: count});
    
    // background.js
    chrome.runtime.onMessage.addListener(
        function(message, sender, sendResponse) {
            switch(message.type) {
                case "setCount":
                    temp = message.count;
                    break;
                default:
                    console.error("Unrecognised message: ", message);
            }
        }
    );
    

    现在,理论上你可以在弹出窗口中提出这个问题:

    // popup.js
    chrome.runtime.sendMessage({type: "getCount"}, function(count) {
        if(typeof count == "undefined") {
            // That's kind of bad
        } else {
            // Use count
        }
    });
    
    // background.js
    chrome.runtime.onMessage.addListener(
        function(message, sender, sendResponse) {
            switch(message.type) {
                case "setCount":
                    temp = message.count;
                    break;
                case "getCount":
                    sendResponse(temp);
                    break;
                default:
                    console.error("Unrecognised message: ", message);
            }
        }
    );
    

    现在,这有什么问题?

    1. temp 的生命周期是多少?您已明确声明"persistent": false in your manifest。这样后台页面就可以随时卸载,擦除状态如temp

      您可以使用 "persistent": true 修复它,但请继续阅读。

    2. 您希望看到哪个标签的计数? temp 将写入最后一个数据,这很可能不是当前选项卡。

      您可以通过保留标签来修复它(看看我在那里做了什么?)哪个标签发送了数据,例如通过使用:

      // background.js
      /* ... */
        case "setCount":
            temp[sender.tab.id] = message.count;
            break;
        case "getCount":
            sendResponse(temp[message.id]);
            break;
      
      // popup.js
      chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
          // tabs is a single-element array after this filtering
          chrome.runtime.sendMessage({type: "getCount", id: tabs[0].id}, function(count) {
              /* ... */
          });
      });
      

      虽然工作量很大,不是吗? 修复 1 后,此解决方案适用于非特定于选项卡的数据。


    需要考虑的下一个改进:我们是否需要后台页面来为我们存储结果?毕竟chrome.storage is a thing;它是所有扩展脚本(包括内容脚本)都可以访问的持久存储。

    这会从图片中删除背景(和消息):

    // content.js
    chrome.storage.local.set({count: count});
    
    // popup.js
    chrome.storage.local.get("count", function(data) {
        if(typeof data.count == "undefined") {
            // That's kind of bad
        } else {
            // Use data.count
        }
    });
    

    这看起来更简洁,并且完全绕过了上面的问题 1,但问题 2 变得更加棘手。 You can't directly set/read 类似于 count[id] 在存储中,您需要将 count 读出来,修改并写回。它可能会变得缓慢而混乱。

    此外,内容脚本并不真正知道它们的标签 ID;你需要消息背景才能学习它。啊。不漂亮。 同样,这对于非特定于标签的数据来说是一个很好的解决方案。


    接下来要问的问题是:为什么我们甚至需要一个中心位置来存储(特定于选项卡的)结果?内容脚本的生命周期就是页面的生命周期。您可以随时直接询问内容脚本。包括从弹出窗口中。

    等等,等等,你不是在最上面说你不能向弹出窗口发送数据吗?嗯,是的,有点:当你不这样做的时候'不知道有没有在听。但是如果弹出窗口询问,那么它必须准备好得到响应,不是吗?

    所以,让我们反转内容脚本逻辑。不要立即发送数据,而是等待并监听请求:

    chrome.runtime.onMessage.addListener(
        function(message, sender, sendResponse) {
            switch(message.type) {
                case "getCount":
                    sendResponse(count);
                    break;
                default:
                    console.error("Unrecognised message: ", message);
            }
        }
    );
    

    然后,在弹出窗口中,我们需要查询包含内容脚本的选项卡。这是一个不同的消息传递函数,我们必须指定标签 ID。

        chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
            chrome.tabs.sendMessage(tabs[0].id, {type: "getCount"}, function(count) {
                /* ... */
            });
        });
    

    现在更简洁了。 问题 2 解决了:我们查询我们想听到的标签。问题1似乎解决了:只要一个脚本算上我们需要什么,它就能回答。

    请注意,作为最后一个复杂因素,内容脚本并不总是在您期望的时候注入:它们仅在导航时开始激活扩展被(重新)加载之后。这是an answer 非常详细地解释了这一点。如果您愿意,可以解决它,但现在只是它的代码路径:

    function(count) {
        if(typeof count == "undefined") {
            // That's kind of bad
            if(chrome.runtime.lastError) {
                // We couldn't talk to the content script, probably it's not there
            }
        } else {
            // Use count
        }
    }
    

    【讨论】:

    • 像往常一样非常彻底
    • 感谢@Xan 的详细回答。我无法通过方法 3 解决的唯一问题是 - 如果我从 popup.js 发送基于选项卡的消息并让内容脚本返回数据,如果用户移动到不同的选项卡并且内容脚本选项卡不再是活动标签?我的弹出窗口永远不会显示任何结果?
    • 如果用户移动到不同的选项卡,弹出窗口将在此过程中关闭,并且无论如何都没有侦听器。此方法适用于内容脚本可以立即回复的内容。如果您认为弹出窗口可以在两者之间关闭,则需要让您的后台页面进行对话,然后弹出窗口从中提取数据。
    • @Xan 对于基于chrome存储API的解决方案,如果在事件监听器中设置了值(通过页面中的注入脚本到内容脚本),则在popup.js中未定义该值。如果在我们使用 storage get API 读取 popup.js 中的数据之前需要一些时间,我们可能需要在 popup.js 中引入 storage API listener。或者可能是 setTimeout,但这不是更好的解决方案。你怎么看?
    • @Xan 我遇到了同样的问题,我尝试了你的解决方案,但它不起作用。你能看看我的问题吗? stackoverflow.com/q/65071740/11850760
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-11
    • 2017-03-31
    • 2011-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多