Message transmis

Étant donné que les scripts de contenu s'exécutent dans le contexte d'une page Web et non de l'extension, ils ont souvent besoin d'un moyen de communiquer avec le reste de l'extension. Par exemple, une extension de lecteur RSS peut utiliser des scripts de contenu pour détecter la présence d'un flux RSS sur une page, puis avertir la page en arrière-plan afin d'afficher une icône d'action sur la page.

La communication entre les extensions et leurs scripts de contenu repose sur la transmission de messages. Chaque côté peut écouter les messages envoyés par l'autre côté et répondre sur le même canal. Un message peut contenir tout objet JSON valide (null, booléen, nombre, chaîne, tableau ou objet). Il existe une API simple pour les requêtes ponctuelles et une API plus complexe qui vous permet d'établir des connexions de longue durée afin d'échanger plusieurs messages avec un contexte partagé. Vous pouvez également envoyer un message à une autre extension si vous connaissez son ID, ce qui est expliqué dans la section Messages sur plusieurs extensions.

Requêtes ponctuelles simples

Si vous avez uniquement besoin d'envoyer un seul message à une autre partie de votre extension (et éventuellement de recevoir une réponse), vous devez utiliser les méthodes simplifiées runtime.sendMessage ou tabs.sendMessage . Cela vous permet d'envoyer un message unique sérialisable JSON à partir d'un script de contenu vers l'extension , ou inversement, respectivement . Un paramètre de rappel facultatif vous permet de gérer la réponse de l'autre côté, le cas échéant.

Voici comment envoyer une requête à partir d'un script de contenu:

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

L'envoi d'une requête de l'extension à un script de contenu ressemble beaucoup, sauf que vous devez spécifier l'onglet vers lequel l'envoyer. Cet exemple montre comment envoyer un message au script de contenu dans l'onglet sélectionné.

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

Du côté réception, vous devez configurer un écouteur d'événements runtime.onMessage pour gérer le message. Cela ressemble à un script de contenu ou à une page d'extension.

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

Dans l'exemple ci-dessus, sendResponse a été appelé de manière synchrone. Si vous souhaitez utiliser sendResponse de manière asynchrone, ajoutez return true; au gestionnaire d'événements onMessage.

Remarque:Si plusieurs pages écoutent les événements onMessage, seule la première à appeler la fonction SendResponse() pour un événement particulier parvient à envoyer la réponse. Toutes les autres réponses à cet événement seront ignorées.
Remarque:Le rappel sendResponse n'est valide que s'il est utilisé de manière synchrone ou si le gestionnaire d'événements renvoie true pour indiquer qu'il répondra de manière asynchrone. Le rappel de la fonction sendMessage sera invoqué automatiquement si aucun gestionnaire ne renvoie la valeur "true" ou si le rappel sendResponse a fait l'objet d'une récupération de mémoire.

Connexions de longue durée

Il est parfois utile d'avoir une conversation qui dure plus longtemps qu'une seule demande et réponse. Dans ce cas, vous pouvez ouvrir une chaîne de longue durée à partir de votre script de contenu vers une page d'extension, ou inversement, en utilisant respectivement runtime.connect ou tabs.connect . Le canal peut éventuellement avoir un nom, ce qui vous permet de distinguer les différents types de connexions.

Il peut s'agir, par exemple, d'une extension de remplissage automatique de formulaire. Le script de contenu peut ouvrir un canal vers la page de l'extension pour une connexion spécifique et envoyer un message à l'extension pour chaque élément d'entrée de la page afin de demander les données de formulaire à remplir. La connexion partagée permet à l'extension de maintenir l'état partagé en reliant les différents messages provenant du script de contenu.

Lors de l'établissement d'une connexion, chaque extrémité reçoit un objet runtime.Port, qui permet d'envoyer et de recevoir des messages via cette connexion.

Voici comment ouvrir une chaîne à partir d'un script de contenu, puis envoyer et écouter des messages:

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

L'envoi d'une requête depuis l'extension vers un script de contenu ressemble beaucoup, sauf que vous devez spécifier l'onglet auquel vous connecter. Remplacez simplement l'appel de connexion dans l'exemple ci-dessus par tabs.connect.

Pour gérer les connexions entrantes, vous devez configurer un écouteur d'événements runtime.onConnect. Cela ressemble à un script de contenu ou à une page d'extension. Lorsqu'une autre partie de votre extension appelle "connect()", cet événement est déclenché avec l'objet runtime.Port que vous pouvez utiliser pour envoyer et recevoir des messages via la connexion. Voici ce à quoi ressemble la réponse aux connexions entrantes:

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

Durée de vie du port

Les ports sont conçus comme une méthode de communication bidirectionnelle entre différentes parties de l'extension, où une trame (de niveau supérieur) est considérée comme la plus petite partie. Un port est créé lorsque vous appelez tabs.connect, runtime.connect ou runtime.connectNative. Ce port peut être utilisé immédiatement pour envoyer des messages à l'autre extrémité via postMessage.

Si un onglet comporte plusieurs frames, l'appel de tabs.connect entraîne plusieurs appels de l'événement runtime.onConnect (une fois pour chaque frame de l'onglet). De même, si runtime.connect est utilisé, l'événement onConnect peut être déclenché plusieurs fois (une fois pour chaque frame du processus d'extension).

Vous souhaiterez peut-être savoir quand une connexion est fermée, par exemple si vous conservez un état distinct pour chaque port ouvert. Pour ce faire, vous pouvez écouter l'événement runtime.Port.onDisconnect. Cet événement est déclenché lorsqu'il n'y a pas de port valide de l'autre côté du canal. Cela se produit dans les cas suivants:

  • Il n'existe aucun écouteur pour runtime.onConnect à l'autre extrémité.
  • L'onglet contenant le port est déchargé (par exemple, s'il est parcouru).
  • Le frame à partir duquel connect a été appelé a été déchargé.
  • Toutes les trames ayant reçu le port (via runtime.onConnect) ont été déchargées.
  • runtime.Port.disconnect est appelé par l'autre extrémité. Notez que si un appel connect génère plusieurs ports du côté du récepteur et que disconnect() est appelé sur l'un de ces ports, l'événement onDisconnect n'est déclenché que sur le port de l'expéditeur et non sur les autres ports.

Messages sur plusieurs extensions

En plus d'envoyer des messages entre différents composants de votre extension, vous pouvez utiliser l'API de messagerie pour communiquer avec d'autres extensions. Cela vous permet d'exposer une API publique dont d'autres extensions peuvent profiter.

L'écoute des requêtes et des connexions entrantes est semblable au cas interne, à la différence que vous utilisez les méthodes runtime.onMessageExternal ou runtime.onConnectExternal. En voici un exemple:

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

De la même manière, envoyer un message à une autre extension s'apparente à l'envoi d'un message dans une extension. La seule différence est que vous devez transmettre l'ID de l'extension avec laquelle vous voulez communiquer. Exemple :

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

Envoyer des messages à partir de pages Web

Comme pour les messages multi-extensions, votre application ou extension peut recevoir des messages à partir de pages Web standards et y répondre. Pour utiliser cette fonctionnalité, vous devez d'abord spécifier dans le fichier manifest.json les sites Web avec lesquels vous souhaitez communiquer. Exemple :

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

L'API de messagerie sera ainsi exposée sur toutes les pages correspondant aux formats d'URL que vous spécifiez. Le format d'URL doit contenir au moins un domaine de deuxième niveau. Autrement dit, les formats de nom d'hôte tels que "*", "*.com", "*.co.uk" et "*.appspot.com" ne sont pas autorisés. À partir de la page Web, utilisez les API runtime.sendMessage ou runtime.connect pour envoyer un message à une application ou à une extension spécifique. Exemple :

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

À partir de votre application ou de votre extension, vous pouvez écouter les messages des pages Web via les API runtime.onMessageExternal ou runtime.onConnectExternal, comme pour les messages multi-extensions. Seule la page Web peut établir une connexion. Voici un exemple :

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

Messages natifs

Les extensions et les applications peuvent échanger des messages avec des applications natives enregistrées en tant qu'hôte de messagerie natif. Pour en savoir plus sur cette fonctionnalité, consultez Messagerie native.

Points à noter concernant la sécurité

Les scripts de contenu sont moins fiables

Les scripts de contenu sont moins fiables que la page d'arrière-plan de l'extension (par exemple, une page Web malveillante peut compromettre le processus du moteur de rendu dans lequel les scripts de contenu s'exécutent). Supposons que les messages d'un script de contenu aient été créés par un pirate informatique. Veillez à valider et nettoyer toutes les entrées. Supposons que les données envoyées au script de contenu puissent être transmises à la page Web. Limitez le champ d'application des actions privilégiées pouvant être déclenchées par des messages provenant de scripts de contenu.

Script intersites

Lorsque vous recevez un message provenant d'un script de contenu ou d'une autre extension, veillez à ne pas être victime d'un script intersites. Ce conseil s'applique aux scripts exécutés sur la page en arrière-plan de l'extension, ainsi qu'aux scripts de contenu exécutés sur d'autres origines Web. Plus précisément, évitez d'utiliser des API dangereuses telles que celles indiquées ci-dessous:

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

Privilégiez plutôt des API plus sûres qui n'exécutent pas de scripts:

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

Exemples

Vous trouverez des exemples simples de communication via des messages dans le répertoire examples/api/messaging. L'exemple de messagerie native montre comment une application Chrome peut communiquer avec une application native. Pour obtenir d'autres exemples et de l'aide concernant l'affichage du code source, consultez Exemples.