Отправка сообщения из фонового скрипта в скрипт контента, а затем во внедренный скрипт
Я пытаюсь отправить сообщения с фоновой страницы в сценарий содержимого, а затем отправить сообщение из этого сценария содержимого во вставленный сценарий. Я пробовал это, но это не работает.
Вот как выглядит мой код.
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. Что-то не так с моим кодом?
2 ответа
Ваш сценарий не работает из-за того, как вводятся сценарии содержимого.
проблема
Когда вы (повторно) загружаете свое расширение, вопреки ожиданиям некоторых пользователей, 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
});
}
});
Я также подозреваю, что вы хотите, чтобы он выполнялся при некоторых действиях, а не при загрузке расширений. Например, вы можете использовать действие браузера и обернуть ваш код в 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; }
/* ... */
});
В моем background.js
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
if (tab.url !== undefined && info.status == "complete") {
chrome.tabs.query({active: true, currentWindow: true, status: "complete"}, function (tabs) {
console.log(tabs);
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function (response) {
console.log(response.farewell);
});
});
}
});
Мой манифест.json
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": [
"content_script.js"
],
"run_at": "document_end"
}
Мой "content_sciprt.js" работал после "background.js". поэтому я не могу получить ответ.
Но после того, как я добавил
info.status=="complete"
,status: "complete"
"run_at": "document_end"
в моем manifest.json
Работает нормально