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 d'arrière-plan afin d'afficher une icône d'action de page pour cette page.

La communication entre les extensions et leurs scripts de contenu s'effectue à l'aide de la transmission de messages. Chaque partie peut écouter les messages envoyés par l'autre et répondre sur le même canal. Un message peut contenir n'importe quel objet JSON valide (null, booléen, nombre, chaîne, tableau ou objet). Il existe une simple API pour les requêtes ponctuelles et une API plus complexe qui vous permet d'avoir des connexions durables pour échanger plusieurs messages avec un contexte partagé. Il est également possible d'envoyer un message à une autre extension si vous connaissez son ID, comme indiqué dans la section Messages entre extensions.

Requêtes ponctuelles simples

Si vous n'avez besoin d'envoyer qu'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 JSON sérialisable ponctuel d'un script de contenu à l'extension, ou inversement. Un paramètre de rappel facultatif vous permet de gérer la réponse de l'autre côté, le cas échéant.

L'envoi d'une requête à partir d'un script de contenu se présente comme suit :

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

L'envoi d'une requête de l'extension à un script de contenu est très similaire, sauf que vous devez spécifier l'onglet auquel 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é du destinataire, vous devez configurer un écouteur d'événements runtime.onMessage pour gérer le message. L'apparence est la même à partir d'un script de contenu ou d'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 sendResponse() pour un événement particulier réussira à 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 est appelé automatiquement si aucun gestionnaire ne renvoie la valeur "true" ou si le rappel sendResponse est collecté en tant que déchet.

Connexions durables

Il est parfois utile d'avoir une conversation qui dure plus longtemps qu'une seule requête et une seule réponse. Dans ce cas, vous pouvez ouvrir un canal durable de votre script de contenu vers une page d'extension , ou inversement, à l'aide de runtime.connect ou tabs.connect, respectivement . Le canal peut éventuellement avoir un nom, ce qui vous permet de distinguer différents types de connexions.

Un cas d'utilisation peut être une extension de remplissage automatique de formulaire. Le script de contenu peut ouvrir un canal vers la page d'extension pour une connexion particulière, et envoyer un message à l'extension pour chaque élément d'entrée de la page afin de demander les données du formulaire à remplir. La connexion partagée permet à l'extension de conserver un état partagé liant 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 est utilisé pour envoyer et recevoir des messages via cette connexion.

Voici comment ouvrir un canal à 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 de l'extension à un script de contenu est très similaire, sauf que vous devez spécifier l'onglet auquel vous connecter. Il vous suffit de remplacer l'appel à connect dans l'exemple ci-dessus par tabs.connect.

Pour gérer les connexions entrantes, vous devez configurer un écouteur d'événements runtime.onConnect. L'apparence est la même à partir d'un script de contenu ou d'une page d'extension. Lorsqu'une autre partie de votre extension appelle "connect()", cet événement est déclenché, ainsi que l'objet runtime.Port que vous pouvez utiliser pour envoyer et recevoir des messages via la connexion. Voici à 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ù un frame (de premier niveau) est considéré comme la plus petite partie. Lors de l'appel de tabs.connect, runtime.connect ou runtime.connectNative, un Port est créé. Ce port peut être utilisé immédiatement pour envoyer des messages à l'autre extrémité via postMessage.

S'il existe plusieurs frames dans un onglet, 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 pouvez déterminer quand une connexion est fermée, par exemple si vous conservez un état distinct pour chaque port ouvert. Pour cela, vous pouvez écouter l'événement runtime.Port.onDisconnect. Cet événement est déclenché lorsqu'il n'y a pas de ports valides de l'autre côté du canal. Cela se produit dans les situations suivantes :

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

Messagerie entre 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 que d'autres extensions peuvent utiliser.

L'écoute des requêtes et des connexions entrantes est semblable au cas interne, sauf que vous utilisez les runtime.onMessageExternal ou runtime.onConnectExternal méthodes. Voici un exemple pour chacune d'elles :

// 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 même, l'envoi d'un message à une autre extension est semblable à l'envoi d'un message dans votre extension. La seule différence est que vous devez transmettre l'ID de l'extension avec laquelle vous souhaitez 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(...);

Envoi de messages à partir de pages Web

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

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

Cela exposera l'API de messagerie à toutes les pages correspondant aux modèles d'URL que vous spécifiez. Le modèle d'URL doit contenir au moins un domaine de deuxième niveau. Autrement dit, les modèles de nom d'hôte tels que "*", "*.com", "*.co.uk" et "*.appspot.com" sont interdits. À partir de la page Web, utilisez les 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 extension, vous pouvez écouter les messages provenant de pages Web via les runtime.onMessageExternal ou runtime.onConnectExternal, comme pour la messagerie entre extensions. Seule la page Web peut lancer 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);
  });

Messagerie native

Les extensions et les applications peuvent échanger des messages avec des applications natives enregistrées en tant qu' hôte de messagerie native. 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 être en mesure de compromettre le processus de rendu dans lequel les scripts de contenu s'exécutent). Partez du principe que les messages provenant d'un script de contenu ont peut-être été créés par un pirate informatique et veillez à valider et assainir toutes les entrées. Partez du principe que toutes les données envoyées au script de contenu peuvent être divulguées à la page Web. Limitez le champ d'application des actions privilégiées qui peuvent être déclenchées par les messages reçus des scripts de contenu.

Script intersites

Lorsque vous recevez un message d'un script de contenu ou d'une autre extension, vos scripts doivent veiller à ne pas être victimes de scripts intersites. Ce conseil s'applique aux scripts exécutés dans la page d'arrière-plan de l'extension, ainsi qu'aux scripts de contenu exécutés dans d'autres origines Web. Plus précisément, évitez d'utiliser des API dangereuses telles que celles 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;
});

Préférez plutôt des API plus sécurisées 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 par 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 pour afficher le code source, consultez Exemples.