问题描述
我正在尝试从后台页面向内容脚本发送消息,然后从该内容脚本向注入的脚本发送消息.我试过这个,但它不起作用.
I'm trying to send messages from the background page to a content script, then send a message from that content script to an injected script. I've tried this, but it isn't working.
这是我的代码的样子.
manifest.json
{
"manifest_version": 2,
"name": "NAME",
"description": ":D",
"version": "0.0",
"permissions": [
"tabs","<all_urls>"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
],
"web_accessible_resources": [
"injected.js"
],
"background":{
"scripts":["background.js"]
}
}
background.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response){});
});
content_script.js
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function(){
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
document.dispatchEvent(new CustomEvent('Buffer2Remote', {todo: "LOL"}));
});
injected.js
document.addEventListener('Buffer2Remote', function(e){
alert(e.todo);
});
从第一部分开始发送消息不起作用,背景-> content_script.我的代码有问题吗?
The message sending doesn't work from the first part, background -> content_script.Is there anything wrong with my code?
推荐答案
由于内容脚本的注入方式,您的脚本无法运行.
Your script doesn't work because of how content scripts are injected.
当您(重新)加载您的扩展程序时,与某些人的预期相反,Chrome 不会将内容脚本注入现有标签中,这些脚本与清单中的模式匹配.只有在加载扩展程序后,任何导航才会检查 URL 是否匹配并注入代码.
When you (re)load your extension, contrary to what some people expect, Chrome will not inject content scripts into existing tabs that match patterns from the manifest. Only after the extension is loaded, any navigation will check the URL for matching and will inject the code.
所以,时间线:
- 您打开了一些标签页.那里没有内容脚本.
- 您加载了扩展程序.它的顶级代码被执行:它尝试将消息传递给当前选项卡.
- 由于那里还没有侦听器,所以它失败了.(这可能是
chrome://extensions/
页面,无论如何你都不能在那里注入) - 如果之后您尝试导航/打开一个新选项卡,侦听器会被注入,但您的顶级代码不再被执行.
- You open some tabs. No content scripts there.
- You load your extension. Its top level code gets executed: it tries to pass a message to the current tab.
- Since there can be no listener there yet, it fails. (Which is probably the
chrome://extensions/
page and you can't inject there anyway) - If, afterwards, you try to navigate/open a new tab, the listener gets injected, but your top level code no longer gets executed.
- 如果您重新加载扩展程序,也会发生这种情况.如果注入了内容脚本,它会继续处理其事件/不会被卸载,但无法再与扩展进行通信.(详情见文末附录)
- This also happens if you reload your extension. If there was a content script injected, it continues to handle its events / doesn't get unloaded, but can no longer communicate with the extension. (for details, see addendum at the end)
解决方案 1:您可以先询问您要发送消息的选项卡是否准备就绪,然后在静音时以编程方式注入脚本.考虑:
Solution 1: you can first ask the tab you're sending a message to whether it's ready, and upon silence inject the script programmatically. Consider:
// Background
function ensureSendMessage(tabId, message, callback){
chrome.tabs.sendMessage(tabId, {ping: true}, function(response){
if(response && response.pong) { // Content script ready
chrome.tabs.sendMessage(tabId, message, callback);
} else { // No listener on the other end
chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
throw Error("Unable to inject script into tab " + tabId);
}
// OK, now it's injected and ready
chrome.tabs.sendMessage(tabId, message, callback);
});
}
});
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
ensureSendMessage(tabs[0].id, {greeting: "hello"});
});
和
// Content script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.ping) { sendResponse({pong: true}); return; }
/* Content script action */
});
解决方案 2:始终注入脚本,但确保它只执行一次.
// Background
function ensureSendMessage(tabId, message, callback){
chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
throw Error("Unable to inject script into tab " + tabId);
}
// OK, now it's injected and ready
chrome.tabs.sendMessage(tabId, message, callback);
});
}
和
// Content script
var injected;
if(!injected){
injected = true;
/* your toplevel code */
}
这更简单,但在扩展重新加载时会很复杂.重新加载扩展后,旧脚本仍然存在 但它不再是您的"上下文 - 所以 injected
将是未定义的.注意可能会执行两次脚本的副作用.
This is simpler, but has complications on extension reload. After an extension is reloaded, the old script is still there but it's not "your" context anymore - so injected
will be undefined. Beware of side effects of potentially executing your script twice.
解决方案 3:在初始化时不加选择地注入您的内容脚本.仅当可以安全地运行相同的内容脚本两次或在页面完全加载后运行它时,这才是安全的.
Solution 3: just indiscriminately inject your content script(s) on initialization. This is only safe to do if it's safe to run the same content script twice, or run it after the page is fully loaded.
chrome.tabs.query({}, function(tabs) {
for(var i in tabs) {
// Filter by url if needed; that would require "tabs" permission
// Note that injection will simply fail for tabs that you don't have permissions for
chrome.tabs.executeScript(tabs[i].id, {file: "content_script.js"}, function() {
// Now you can use normal messaging
});
}
});
我还怀疑您希望它在某些操作上运行,而不是在扩展负载上运行.例如,您可以使用 Browser Action 并将您的代码包装在 chrome.browserAction.onClicked
监听器.
I also suspect that you want it to run on some action, and not on extension load. For example, you can employ a Browser Action and wrap your code in a chrome.browserAction.onClicked
listener.
当一个扩展程序被重新加载时,人们会期望 Chrome 清理所有内容脚本.但显然情况并非如此.内容脚本的侦听器未被禁用.但是,任何带有父扩展的消息都将失败.这可能应该被视为一个错误,并且可能在某个时候得到修复.我将称这种状态为孤立"
When an extension gets reloaded, one would expect Chrome to clean up all content scripts. But apparently this is not the case; content scripts' listeners are not disabled. However, any messaging with parent extension will fail. This should probably be considered a bug and may at some point be fixed. I'm going to call this state "orphaned"
这在两种情况下都不是问题:
This is not a problem in either of two cases:
- 内容脚本没有页面事件监听器(例如只执行一次,或者只监听来自后台的消息)
- 内容脚本不会对页面做任何事情,只会向背景发送有关事件的消息.
但是,如果情况并非如此,您就会遇到问题:内容脚本可能正在执行某些操作,但会失败或干扰其自身的另一个非孤立实例.
However, if that's not the case, you've got a problem: the content script might be doing something but failing or interfering with another, non-orphaned instance of itself.
对此的解决方案是:
- 跟踪所有可以被页面触发的事件监听器
- 在对这些事件采取行动之前,向后台发送心跳"消息.3a.如果后台响应,我们很好,应该执行操作.3b.如果消息传递失败,我们就是孤儿,应该停止;忽略该事件并取消注册所有侦听器.
代码、内容脚本:
function heartbeat(success, failure) {
chrome.runtime.sendMessage({heartbeat: true}, function(reply){
if(chrome.runtime.lastError){
failure();
} else {
success();
}
});
}
function handler() {
heartbeat(
function(){ // hearbeat success
/* Do stuff */
},
function(){ // hearbeat failure
someEvent.removeListener(handler);
console.log("Goodbye, cruel world!");
}
);
}
someEvent.addListener(handler);
后台脚本:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.heartbeat) { sendResponse(request); return; }
/* ... */
});
这篇关于将消息从后台脚本发送到内容脚本,然后发送到注入脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!