由于内容脚本的注入方式,您的脚本无法运行。
问题
当您(重新)加载您的扩展程序时,与某些人的预期相反,Chrome 不会将与清单中的模式匹配的内容脚本注入现有选项卡。只有在加载扩展后,任何导航都会检查 URL 是否匹配并注入代码。
所以,时间线:
- 您打开了一些标签。那里没有内容脚本1。
- 您加载了您的扩展程序。它的顶级代码被执行:它尝试将消息传递到当前选项卡。
- 因为那里还没有侦听器,所以它失败了。 (这可能是
chrome://extensions/ 页面,无论如何您都不能在那里注入)
- 如果之后您尝试导航/打开一个新选项卡,侦听器会被注入,但您的顶级代码不再执行。
1 - 如果您重新加载扩展程序,也会发生这种情况。如果注入了内容脚本,它会继续处理其事件/不会被卸载,但不能再与扩展程序通信。 (详情见文末附录)
解决方案
解决方案 1:您可以先询问要发送消息的选项卡是否准备就绪,然后在静默时以编程方式注入脚本。考虑:
// 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 */
}
这更简单,但在扩展重新加载时有复杂性。重新加载扩展后,旧脚本仍然存在1,但它不再是“你的”上下文 - 所以injected 将是未定义的。请注意可能会执行两次脚本的副作用。
解决方案 3:在初始化时不加选择地注入您的内容脚本。只有在两次运行相同的内容脚本或在页面完全加载后运行它是安全的情况下,这样做才是安全的。
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 侦听器中。
关于孤立内容脚本的附录
当重新加载扩展程序时,人们会期望 Chrome 清理所有内容脚本。但显然情况并非如此。内容脚本的侦听器未被禁用。但是,任何带有父分机的消息都会失败。 这可能应该被认为是一个错误,并且可能会在某个时候被修复。我将把这个状态称为“孤立”
这两种情况都不是问题:
- 内容脚本对页面上的事件没有监听器(例如,只执行一次,或者只监听来自后台的消息)
- 内容脚本不对页面做任何事情,只向背景发送有关事件的消息。
但是,如果不是这种情况,您就会遇到问题:内容脚本可能正在做某事,但失败或干扰了另一个非孤立的自身实例。
解决方案是:
- 跟踪可以由页面触发的所有事件侦听器
- 在对这些事件采取行动之前,向后台发送“心跳”消息。
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; }
/* ... */
});