Есть два варианта – простой 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 }); //запрос на сообщение
добавил обработчик на вызывающую страницу, и все завертелось.