Есть два варианта – простой API для однократной передачи сообщений (самое-то для рассмотренного примера) и более сложный, позволяющий создать постоянный канал обмена сообщениями.
Простая передача сообщений
Если нужно просто отправить сообщение и, опционально, получить ответ, используется вызов методов runtime.sendMessage или tabs.sendMessage.Из контентного скрипта фоновой странице:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) { console.log(response.farewell); });
Из фоновой страницы контекстному скрипту:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) { console.log(response.farewell); }); });
На принимающей стороне должен быть установлен обработчик runtime.onMessage. Следующий код будет работать и в контентном скрипте и в фоновой странице:
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { console.log(sender.tab ? "from a content script:" + sender.tab.url : "from the extension"); if (request.greeting == "hello") sendResponse({farewell: "goodbye"}); });
Примечание: если много страниц имеют обработчики runtime.onMessage и в них не предусмотрен механизм отделения “своих” сообщений от “чужих”, то это кончится плохо – отработают все обработчики, но только первый из них успешно отправит ответ. Остальные обломаются.
Постоянное соединение
Иногда сообщениями нужно обмениваться постоянно. Чтобы не создавать лишнюю нагрузку на процессор – каждое сообщение вызывает активацию обработчиков на всех страницах, есть другой механизм. Для этого используются методы runtime.connect и tabs.connect
var port = chrome.runtime.connect({name: "knockknock"}); port.postMessage({joke: "Knock knock"}); port.onMessage.addListener(function(msg) { if (msg.question == "Who's there?") port.postMessage({answer: "Madame"}); else if (msg.question == "Madame who?") port.postMessage({answer: "Madame... Bovary"}); });
И на другой стороне:
chrome.runtime.onConnect.addListener(function(port) { console.assert(port.name == "knockknock"); port.onMessage.addListener(function(msg) { if (msg.joke == "Knock knock") port.postMessage({question: "Who's there?"}); else if (msg.answer == "Madame") port.postMessage({question: "Madame who?"}); else if (msg.answer == "Madame... Bovary") port.postMessage({question: "I don't get it."}); }); });
Таким образом создается именованный канал и все последующие сообщения передаются в рамках этого канала. Напомню – пока канал открыт, фоновая страница не может быть деактивирована.Так что если сообщения передаются редко, то лучше все же использовать первый механизм. А когда необходимость в канале отпала – закрывать его вызовом runtime.Port.disconnect. Этот метод может быть вызван с любой стороны, при этом на другой стороне возникает событие runtime.Port.onDisconnect.
Обмен сообщениями между расширениями
Кроме обмена сообщениями между компонентами вашего расширения, можно обмениваться сообщениями и с другими расширениями. Для приема таких сообщений есть события runtime.onMessageExternal и runtime.onConnectExternal, соответственно для разовых сообщений и для создания постоянного канала:
// For simple requests: chrome.runtime.onMessageExternal.addListener( function(request, sender, sendResponse) { if (sender.id == blacklistedExtension) return; // don't allow this extension access else if (request.getTargetData) sendResponse({targetData: targetData}); else if (request.activateLasers) { var success = activateLasers(); sendResponse({activateLasers: success}); } }); // For long-lived connections: chrome.runtime.onConnectExternal.addListener(function(port) { port.onMessage.addListener(function(msg) { // See other examples for sample onMessage handlers. }); });
А вот чтобы отправить сообщение, нужно знать идентификатор расширения:
// The ID of the extension we want to talk to. var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc"; // Make a simple request: chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true}, function(response) { if (targetInRange(response.targetData)) chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true}); }); // Start a long-running conversation: var port = chrome.runtime.connect(laserExtensionId); port.postMessage(...);
Если я ничего не путаю, идентификатор расширения фиксируется при его загрузке в Google Store. Значит для расширений не опубликованных там возможность использования данного механизма под вопросом.
Отправка сообщений с веб-страницы
Подобным же образом расширение может получать сообщения от веб-страницы. Для этого нужно прописать такую возможность в манифесте:
"externally_connectable": { "matches": ["*://*.example.com/*"] }
Чтобы обмениваться сообщениями с расширением веб страница должна знать его ID:
// The ID of the extension we want to talk to. var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc"; // Make a simple request: chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url}, function(response) { if (!response.success) handleError(url); });
А расширение принимает их через тот же интерфейс, что и от других расширений:
chrome.runtime.onMessageExternal.addListener( function(request, sender, sendResponse) { if (sender.url == blacklistedWebsite) return; // don't allow this web page access if (request.openUrlInEditor) openUrl(request.openUrlInEditor); });
Примечание: как быть с тем, что нам заранее не известен ID расширения (жаба душит отдавать 5$ за регистрацию на ГуглСтор)? Можно передать странице ID расширения контентным скриптом, через DOM.
Обмен сообщениями с нативными приложениями
Нативное приложение должно зарегистрировать соответствующую возможность (native messaging host) и указать с какими расширениями оно может работать:
{ "name": "com.my_company.my_application", "description": "My Application", "path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe", "type": "stdio", "allowed_origins": [ "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/" ] }
Манифест приложения должен содержать следующие параметры:
Имя | Описание |
---|---|
name | Имя хоста. Клиентское расширение передает это значение в методы runtime.connectNative или runtime.sendNativeMessage. |
description | Краткое описание приложения |
path | Путь к бинарному файлу приложения. В системах Linux and OSX путь должен быть абсолютным. На Windows все не как у людей – путь может быть относительным, указывать месторасположение бинарного файла относительно каталога где лежит манифест. |
type | Тип интерфейса для обмена сообщениями. На текущий момент он единственный: stdio. |
allowed_origins | Список ID расширений которым дозволено работать с этим приложением. Фактически это означает что нельзя предоставить публичный интерфейс приложения всем и каждому. Разработчик приложения должен явно предоставить доступ к своему приложению разработчику расширения. |
Местонахождение манифеста зависит от платформы:
- Windows: где угодно. Приложение должно создать ключ реестра HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application или HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application, и установить в значении ключа по умолчанию абсолютный путь к файлу манифеста.
- OSX: /Library/Google/Chrome/NativeMessagingHosts/com.my_company.my_application.json, или ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.my_company.my_application.json
- Linux: /etc/opt/chrome/native-messaging-hosts/com.my_company.my_application.json или ~/.config/chrome/NativeMessagingHosts/com.my_company.my_application.json.
Chrome запускает каждый нативный хост в отдельном процессе и взаимодействует с ним через стандартный ввод/вывод. Сообщения сериализуются в формате JSON, кодировка UTF-8, в начале сообщения 32битная длина, порядок байтов соответствует платформе.
Когда используется runtime.connectNative, Chrome запускает нативный хост и держит его активным пока соединение не будет закрыто. Если используется runtime.sendNativeMessage, то браузер запускает новый процесс для каждого сообщения. От хоста в этом случае ожидается ответ на исходное сообщение, а все последующие сообщения, если таковые будут – игнорируются.
Пример использования runtime.connectNative:
var port = chrome.runtime.connectNative('com.my_company.my_application'); port.onMessage.addListener(function(msg) { console.log("Received" + msg); }); port.onDisconnect.addListener(function() { console.log("Disconnected"); }); port.postMessage({ text: "Hello, my_application" });
Пример использования runtime.sendNativeMessage:
chrome.runtime.sendNativeMessage('com.my_company.my_application', { text: "Hello" }, function(response) { console.log("Received " + response); });
не пашет ваш обмен сообщениями
ОтветитьУдалитьМне ошибку на принимающей стороне сайта выдает.
ОтветитьУдалитьUncaught TypeError: Cannot read property 'addListener' of undefinedai_on вот такую, в чем может быть причина?
ОтветитьУдалитьСкорее всего разрешений не хватает в manifest.json
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьСпасибо. Теперь яснее стало. Дока какая-то мутная по обработчикам на сайте.
ОтветитьУдалитьCould not establish connection. Receiving end does not exist.
ОтветитьУдалитьВ чём может быть ошибка, в externally_connectable url неправильный или id не подходит?
id меняется при установке распакованного расширения на разных рабочих машинах
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьтакая же фигня
ОтветитьУдалитьvar port = browsers.runtime.chromePort = chrome.runtime.connect({name: "bus"});
port.onMessage.addListener(function(a) {...});
...
var d = browsers.runtime.chromePort;
d.postMessage(b)
Ошибка extensions::messaging:78 Uncaught Error: Attempting to use a disconnected port object
почему то порт закрывается
очень круто всё работает узнал много нового,спс автору огромное!!
ОтветитьУдалить
ОтветитьУдалитьПостоянно пользовался отсылкой сообщений на фоновую страницу, но вот в первый раз понадобилось использовать коллбэк. Код просто примитивнейший:
chrome.extension.sendMessage({ тут параметры },
function(backMessage){
console.log("in callback: backMessage: ", backMessage);
window.alert('Возвращено: ' + backMessage);
}
);
В фоновой странице:
chrome.extension.onMessage.addListener(function(request, sender, callback) {
// Тут творятся тёмные дела
if(callback) callback(counter);
}
И стабильно получаю ошибку:
Unchecked runtime.lastError: The message port closed before a response was received.
Кто виноват, и что делать?
А в принципе, пофиг, заменил вызов коллбэка в фоновой странице на:
ОтветитьУдалитьchrome.tabs.sendRequest(sender.tab.id,{ count: counter }); //запрос на сообщение
добавил обработчик на вызывающую страницу, и все завертелось.