Przekazywanie wiadomości

Skrypty treści działają w kontekście strony internetowej, a nie rozszerzenia, więc często potrzebują jakiegoś sposobu komunikacji z resztą rozszerzenia. Na przykład rozszerzenie czytnika RSS może używać skryptów treści do wykrywania obecności kanału RSS na stronie, a następnie powiadamiać stronę w tle, aby wyświetlić jej ikonę działań.

Komunikacja między rozszerzeniami i ich skryptami treści polega na przekazywaniu wiadomości. Każda ze stron może odsłuchiwać wiadomości wysyłane przez drugą stronę i odpowiadać na tym samym kanale. Wiadomość może zawierać dowolny prawidłowy obiekt JSON (wartość null, wartość logiczna, liczba, ciąg, tablicę lub obiekt). Istnieje prosty interfejs API do żądań jednorazowych i bardziej złożony interfejs API, który umożliwia stosowanie długotrwałych połączeń w celu wymiany wielu komunikatów ze wspólnym kontekstem. Możesz też wysłać wiadomość do innego rozszerzenia, jeśli znasz jego identyfikator – znajdziesz go w sekcji wiadomości z różnymi rozszerzeniami.

Proste żądania jednorazowe

Jeśli chcesz wysłać tylko jedną wiadomość do innej części rozszerzenia (i opcjonalnie otrzymać odpowiedź), użyj uproszczonego formatu runtime.sendMessage lub tabs.sendMessage . Dzięki temu możesz wysłać jednorazową wiadomość możliwą do sserializowania w formacie JSON ze skryptu treści do rozszerzenia lub na odwrót . Opcjonalny parametr wywołania zwrotnego umożliwia obsługę odpowiedzi z drugiej strony, jeśli taka istnieje.

Wysłanie żądania ze skryptu dotyczącego treści wygląda tak:

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

Wysyłanie żądania z rozszerzenia do skryptu treści wygląda bardzo podobnie z tą różnicą, że trzeba określić, na którą kartę je wysłać. Ten przykład pokazuje wysłanie wiadomości do skryptu treści na wybranej karcie.

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
    console.log(response.farewell);
  });
});

Po stronie odbiorcy musisz skonfigurować odbiornik zdarzenia runtime.onMessage do obsługi wiadomości. Wygląda to tak samo w przypadku skryptu dotyczącego treści lub strony z rozszerzeniem.

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"});
  }
);

W powyższym przykładzie obiekt sendResponse został wywołany synchronicznie. Jeśli chcesz asynchronicznie używać polecenia sendResponse, dodaj dyrektywę return true; do modułu obsługi zdarzeń onMessage.

Uwaga: jeśli wiele stron nasłuchuje zdarzeń onMessage, tylko pierwsza z nich wywoła metodę sendResponse() w przypadku danego zdarzenia. Wszystkie pozostałe odpowiedzi na to zdarzenie będą ignorowane.
Uwaga: wywołanie zwrotne sendResponse jest prawidłowe tylko wtedy, gdy jest używane synchronicznie lub wtedy, gdy moduł obsługi zdarzeń zwraca true, co wskazuje, że odpowie asynchronicznie. Wywołanie zwrotne funkcji sendMessage jest wywoływane automatycznie, jeśli żaden moduł obsługi nie zwróci wartości prawda lub jeśli wywołanie zwrotne sendResponse zostało usunięte w wyniku czyszczenia pamięci.

Długotrwałe połączenia

Czasami dobrze jest prowadzić wątek, który trwa dłużej niż jedno żądanie i odpowiedź. W takim przypadku możesz otworzyć długotrwały kanał ze skryptu treści na stronę rozszerzenia lub na odwrót, używając odpowiednio runtime.connect lub tabs.connect . Kanał może opcjonalnie mieć nazwę, która pozwala rozróżniać typy połączeń.

Jednym z przykładów może być automatyczne rozszerzenie wypełniania formularza. Skrypt treści może otworzyć kanał na stronie rozszerzenia dla danego loginu i wysłać do rozszerzenia wiadomość dotyczącą każdego elementu wejściowego na stronie z prośbą o wypełnienie formularza. Udostępnione połączenie umożliwia rozszerzeniu zachowanie stanu udostępniania łączącego kilka wiadomości pochodzących ze skryptu treści.

Podczas nawiązywania połączenia każdy koniec otrzymuje obiekt runtime.Port, który służy do wysyłania i odbierania wiadomości przez to połączenie.

Oto jak otworzyć kanał ze scenariusza treści i wysyłać wiadomości oraz ich odsłuchiwać:

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"});
});

Wysyłanie żądania z rozszerzenia do skryptu treści wygląda bardzo podobnie z tą różnicą, że trzeba określić kartę, z którą chcesz się połączyć. Wystarczy zastąpić połączenie z przykładu powyżej tabs.connect.

Aby obsługiwać połączenia przychodzące, musisz skonfigurować odbiornik zdarzeń runtime.onConnect. Wygląda to tak samo w przypadku skryptu treści lub strony rozszerzenia. Gdy inna część rozszerzenia wywołuje metodę „connect()”, wywoływane jest to zdarzenie wraz z obiektem runtime.Port, którego możesz użyć do wysyłania i odbierania komunikatów przez połączenie. Oto jak wygląda reakcja na połączenia przychodzące:

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."});
  });
});

Czas eksploatacji portu

Porty zostały zaprojektowane jako dwukierunkowa metoda komunikacji między różnymi częściami rozszerzenia, gdzie ramka (najwyższego poziomu) jest postrzegana jako najmniejsza część. Po wywołaniu metody tabs.connect, runtime.connect lub runtime.connectNative tworzony jest port. Ten port możesz natychmiast używać do wysyłania wiadomości na drugi koniec za pomocą postMessage.

Jeśli na karcie znajduje się wiele klatek, wywołanie metody tabs.connect powoduje wiele wywołań zdarzenia runtime.onConnect (po jednym razie dla każdej klatki na karcie). Podobnie, jeśli używana jest usługa runtime.connect, zdarzenie onConnect może być wywoływane wiele razy (raz na każdą klatkę w procesie rozszerzenia).

Możesz się dowiedzieć, kiedy połączenie jest zamknięte, na przykład jeśli stosujesz oddzielny stan dla każdego otwartego portu. W tym celu możesz nasłuchiwać zdarzenia runtime.Port.onDisconnect. Zdarzenie to jest wywoływane, gdy po drugiej stronie kanału nie ma prawidłowych portów. Dzieje się tak w następujących sytuacjach:

  • Na drugim końcu nie ma detektorów runtime.onConnect.
  • Karta zawierająca port zostanie wyładowana z pamięci (np. gdy należy po niej poruszać się).
  • Ramka, z której pochodziło żądanie connect, została wyładowana z pamięci.
  • Wszystkie klatki, które odebrały ten port (za pomocą runtime.onConnect), zostały wyładowane.
  • Interfejs runtime.Port.disconnect jest wywoływany przez drugą stronę. Pamiętaj, że jeśli wywołanie connect prowadzi do wielu portów po stronie odbiorcy, a disconnect() jest wywoływany na dowolnym z tych portów, zdarzenie onDisconnect jest wywoływane tylko na porcie nadawcy, a nie w innych portach.

Przesyłanie wiadomości w różnych rozszerzeniach

Oprócz wysyłania komunikatów między różnymi komponentami rozszerzenia możesz używać interfejsu Komunikator API do komunikowania się z innymi rozszerzeniami. Pozwoli Ci to udostępnić publiczny interfejs API, z którego mogą korzystać inne rozszerzenia.

Nasłuchiwanie żądań i połączeń przychodzących wygląda podobnie jak w przypadku zgłoszenia wewnętrznego, z tą różnicą, że używasz metod runtime.onMessageExternal lub runtime.onConnectExternal. Oto przykład każdego z nich:

// For simple requests:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id == blocklistedExtension)
      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.
  });
});

Podobnie wysłanie wiadomości do innego rozszerzenia jest podobne do wysyłania wiadomości z własnego rozszerzenia. Jedyna różnica polega na tym, że musisz przekazać identyfikator rozszerzenia, z którym chcesz się komunikować. Przykład:

// 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(...);

Wysyłanie wiadomości ze stron internetowych

Podobnie jak w przypadku wiadomości z różnych rozszerzeń aplikacja lub rozszerzenie może odbierać wiadomości ze zwykłych stron internetowych i na nie odpowiadać. Aby korzystać z tej funkcji, musisz najpierw określić w pliku manifest.json witryny, z którymi chcesz się komunikować. Na przykład:

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

Spowoduje to udostępnienie interfejsu API do przesyłania wiadomości każdej stronie pasującej do podanych przez Ciebie wzorców adresów URL. Wzorzec adresu URL musi zawierać co najmniej domenę drugiego poziomu, czyli niedozwolone są wzorce nazw hostów, takie jak „*”, „*.com”, „*.co.uk” i „*.appspot.com”. Na stronie internetowej użyj interfejsów API runtime.sendMessage lub runtime.connect, aby wysłać komunikat do określonej aplikacji lub rozszerzenia. Na przykład:

// 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);
  });

Możesz odsłuchiwać wiadomości ze stron internetowych w aplikacji lub rozszerzeniu, korzystając z interfejsów API runtime.onMessageExternal lub runtime.onConnectExternal, podobnie jak w przypadku wiadomości między rozszerzeniami. Połączenie może inicjować tylko strona internetowa. Oto przykład:

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.url == blocklistedWebsite)
      return;  // don't allow this web page access
    if (request.openUrlInEditor)
      openUrl(request.openUrlInEditor);
  });

Wiadomości natywne

Rozszerzenia i aplikacje mogą wymieniać wiadomości z aplikacjami natywnymi zarejestrowanymi jako host natywnego przesyłania wiadomości. Więcej informacji o tej funkcji znajdziesz w artykule Komunikaty natywne.

Kwestie bezpieczeństwa

Skrypty treści są mniej wiarygodne

Skrypty treści są mniej wiarygodne niż strona rozszerzenia w tle (np. złośliwa strona internetowa może naruszyć mechanizm renderowania, w którym działają skrypty treści). Zakładaj, że wiadomości ze skryptu dotyczącego treści mogły zostać utworzone przez atakującego, i zweryfikuj i przekaż wszystkie dane wejściowe. Załóżmy, że jakiekolwiek dane wysłane do skryptu dotyczącego treści mogą wyciec na stronę internetową. Ogranicz zakres działań z podwyższonymi uprawnieniami, które mogą być aktywowane przez wiadomości otrzymane ze skryptów treści.

Cross-site scripting

Gdy otrzymasz wiadomość ze skryptu treści lub innego rozszerzenia, pamiętaj, aby zachować ostrożność, aby nie paść ofiarą skryptów w witrynach. Ta porada dotyczy skryptów działających na stronie rozszerzenia w tle, a także skryptów treści działających w innych źródłach internetowych. W szczególności unikaj korzystania z niebezpiecznych interfejsów API, takich jak:

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating an evil script!
  var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});

Zamiast tego wybierz bezpieczniejsze interfejsy API, które nie uruchamiają skryptów:

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // JSON.parse does not evaluate the attacker's scripts.
  var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});

Przykłady

Proste przykłady komunikacji za pomocą wiadomości znajdziesz w katalogu examples/api/messaging. Przykładowy komunikator pokazuje, jak aplikacja Chrome może komunikować się z aplikacją natywną. Więcej przykładów i pomoc w wyświetlaniu kodu źródłowego znajdziesz w sekcji Przykłady.