Przekazywanie wiadomości

Skrypty treści działają w kontekście strony internetowej, a nie rozszerzenia, więc często potrzebują sposobu na komunikowanie się z pozostałą częścią 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ć ikonę działania na stronie.

Komunikacja między rozszerzeniami a ich skryptami treści odbywa się za pomocą przekazywania wiadomości. Każda ze stron może nasłuchiwać wiadomości wysyłanych z drugiego końca i odpowiadać na tym samym kanale. Wiadomość może zawierać dowolny prawidłowy obiekt JSON (wartość null, wartość logiczną, liczbę, ciąg znaków, tablicę lub obiekt). Dostępny jest prosty interfejs API do jednorazowych żądań oraz bardziej złożony interfejs API, który umożliwia długotrwałe połączenia do wymiany wielu wiadomości we wspólnym kontekście. Możesz też wysłać wiadomość do innego rozszerzenia, jeśli znasz jego identyfikator. Więcej informacji znajdziesz w sekcji Wiadomości między rozszerzeniami.

Proste żądania jednorazowe

Jeśli chcesz wysłać tylko jedną wiadomość do innej części rozszerzenia (i opcjonalnie otrzymać odpowiedź), użyj uproszczonej funkcji runtime.sendMessage lub tabs.sendMessage . Umożliwia to wysłanie jednorazowej wiadomości, którą można serializować do formatu JSON, ze skryptu treści do rozszerzenia lub odwrotnie. Opcjonalny parametr wywołania zwrotnego umożliwia obsługę odpowiedzi z drugiej strony, jeśli taka istnieje.

Wysyłanie żądania ze skryptu 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 tym że musisz określić, do której karty ma zostać wysłane. Ten przykład pokazuje wysył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ć detektor zdarzeń runtime.onMessage, aby obsługiwać wiadomość. Wygląda to tak samo w przypadku skryptu treści i strony rozszerzenia.

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 przykładzie powyżej wywołanie sendResponse nastąpiło synchronicznie. Jeśli chcesz używać sendResponse asynchronicznie, dodaj return true; do modułu obsługi zdarzeń onMessage.

Uwaga: jeśli wiele stron nasłuchuje zdarzeń onMessage, tylko pierwsza z nich, która wywoła funkcję sendResponse() dla danego zdarzenia, zdoła wysłać odpowiedź. Wszystkie inne odpowiedzi na to zdarzenie zostaną zignorowane.
Uwaga: wywołanie zwrotne sendResponse jest prawidłowe tylko wtedy, gdy jest używane synchronicznie lub gdy moduł obsługi zdarzeń zwraca wartość true, aby wskazać, że będzie odpowiadać asynchronicznie. Wywołanie zwrotne funkcji sendMessage zostanie wywołane automatycznie, jeśli żadna z funkcji obsługi nie zwróci wartości „true” lub jeśli wywołanie zwrotne sendResponse zostanie usunięte z pamięci.

Połączenia długotrwałe

Czasami przydatna jest rozmowa, która trwa dłużej niż pojedyncza prośba i odpowiedź. W takim przypadku możesz otworzyć długotrwały kanał ze skryptu treści do strony rozszerzenia lub odwrotnie, używając odpowiednio runtime.connect lub tabs.connect . Kanał może mieć nazwę, która pozwala odróżniać różne typy połączeń.

Jednym z przykładów może być rozszerzenie do automatycznego wypełniania formularzy. Skrypt treści może otworzyć kanał na stronie rozszerzenia dla konkretnego loginu i wysłać do rozszerzenia wiadomość dla każdego elementu wejściowego na stronie, aby poprosić o dane formularza do wypełnienia. Wspólne połączenie umożliwia rozszerzeniu zachowanie wspólnego stanu łączącego kilka wiadomości pochodzących ze skryptu treści.

Podczas nawiązywania połączenia każdy punkt końcowy otrzymuje obiekt runtime.Port, który służy do wysyłania i odbierania wiadomości za pomocą tego połączenia.

Aby otworzyć kanał ze skryptu treści oraz wysyłać i odbierać wiadomości, wykonaj te czynności:

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

Wysłanie żądania z rozszerzenia do skryptu treści wygląda bardzo podobnie, z tym że musisz określić, z którą kartą chcesz się połączyć. W powyższym przykładzie zastąp wywołanie połączenia funkcją 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 i strony rozszerzenia. Gdy inna część rozszerzenia wywoła funkcję „connect()”, zostanie wywołane to zdarzenie wraz z obiektem runtime.Port, którego możesz używać do wysyłania i odbierania wiadomości przez połączenie. Oto jak wygląda odpowiadanie 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 życia portu

Porty są przeznaczone do dwukierunkowej komunikacji między różnymi częściami rozszerzenia, przy czym ramka (najwyższego poziomu) jest traktowana jako najmniejsza część. Po wywołaniu funkcji tabs.connect, runtime.connect lub runtime.connectNative tworzony jest Port. Ten port można od razu wykorzystać do wysyłania wiadomości do drugiego końca za pomocą funkcji postMessage.

Jeśli karta zawiera wiele ramek, wywołanie tabs.connect powoduje wielokrotne wywołanie zdarzenia runtime.onConnect (po jednym dla każdej ramki na karcie). Podobnie jeśli używana jest funkcja runtime.connect, zdarzenie onConnect może być wywoływane wiele razy (raz dla każdej ramki w procesie rozszerzenia).

Możesz chcieć dowiedzieć się, kiedy połączenie zostanie zamknięte, np. jeśli utrzymujesz oddzielny stan dla każdego otwartego portu. W tym celu możesz nasłuchiwać zdarzenia runtime.Port.onDisconnect. To zdarzenie jest wywoływane, gdy po drugiej stronie kanału nie ma prawidłowych portów. Dzieje się tak w tych sytuacjach:

  • Po drugiej stronie nie ma odbiorców zdarzenia runtime.onConnect.
  • Karta zawierająca port jest zwalniana (np. jeśli nastąpi przejście do innej karty).
  • Ramka, z której wywołano connect, została zwolniona.
  • Wszystkie ramki, które otrzymały port (za pomocą runtime.onConnect), zostały zwolnione.
  • Funkcja runtime.Port.disconnect jest wywoływana przez drugą stronę. Pamiętaj, że jeśli wywołanie connect spowoduje utworzenie wielu portów po stronie odbiorcy, a funkcja disconnect() zostanie wywołana w dowolnym z tych portów, zdarzenie onDisconnect zostanie wywołane tylko w porcie nadawcy, a nie w innych portach.

Przesyłanie wiadomości między rozszerzeniami

Oprócz wysyłania wiadomości między różnymi komponentami rozszerzenia możesz używać interfejsu Messaging API do komunikowania się z innymi rozszerzeniami. Dzięki temu możesz udostępnić publiczny interfejs API, z którego mogą korzystać inne rozszerzenia.

Nasłuchiwanie przychodzących żądań i połączeń jest podobne do przypadku wewnętrznego, z tym ż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 wysyłanie wiadomości do innego rozszerzenia jest podobne do wysyłania wiadomości w ramach 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 komunikacji między rozszerzeniami, Twoja 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, z którymi witrynami chcesz się komunikować. Na przykład:

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

Spowoduje to udostępnienie interfejsu Messaging API każdej stronie, która pasuje do określonych przez Ciebie wzorców adresów URL. Wzorzec adresu URL musi zawierać co najmniej domenę drugiego poziomu, czyli wzorce nazw hostów takie jak „*”, „*.com”, „*.co.uk” i „*.appspot.com” są zabronione. Na stronie internetowej użyj interfejsów API runtime.sendMessage lub runtime.connect, aby wysłać wiadomość do konkretnej 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);
  });

W aplikacji lub rozszerzeniu możesz nasłuchiwać wiadomości ze stron internetowych za pomocą interfejsów API runtime.onMessageExternal lub runtime.onConnectExternal, podobnie jak w przypadku komunikacji między rozszerzeniami. Połączenie może zainicjować 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);
  });

Natywne aplikacje do obsługi wiadomości

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

Bezpieczeństwo

Skrypty dotyczące zawartości są mniej wiarygodne

Skrypty treści są mniej wiarygodne niż strona tła rozszerzenia (np. złośliwa strona internetowa może naruszyć proces renderowania, w którym działają skrypty treści). Załóż, że wiadomości ze skryptu treści mogły zostać utworzone przez osobę atakującą, i sprawdź oraz oczyść wszystkie dane wejściowe. Załóż, że wszystkie dane wysyłane do skryptu treści mogą wyciec na stronę internetową. Ograniczanie zakresu działań uprzywilejowanych, które mogą być wywoływane przez wiadomości otrzymywane ze skryptów treści.

Cross-site scripting

Podczas odbierania wiadomości ze skryptu treści lub innego rozszerzenia skrypty powinny zachować ostrożność, aby nie paść ofiarą ataku typu cross-site scripting. Ta rada dotyczy skryptów działających na stronie tła rozszerzenia, 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 te poniżej:

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 używaj bezpieczniejszych interfejsów 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ładowa aplikacja do przesyłania wiadomości 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.