Message transmis

Les API de messagerie vous permettent de communiquer entre différents scripts s'exécutant dans des contextes associés à votre extension. Cela inclut la communication entre votre service worker, les pages chrome-extension://et les scripts de contenu. 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 notifier le service worker pour qu'il mette à jour l'icône d'action pour cette page.

Il existe deux API de transfert de messages : l'une pour les requêtes ponctuelles et l'autre, plus complexe, pour les connexions de longue durée qui permettent d'envoyer plusieurs messages.

Pour en savoir plus sur l'envoi de messages entre extensions, consultez la section Messages entre extensions.

Demandes ponctuelles

Pour envoyer un message unique à une autre partie de votre extension et, éventuellement, obtenir une réponse, appelez runtime.sendMessage() ou tabs.sendMessage(). Ces méthodes vous permettent d'envoyer un message sérialisable au format JSON ponctuel depuis un script de contenu vers l'extension, ou depuis l'extension vers un script de contenu. Les deux API renvoient une promesse qui se résout en la réponse fournie par un destinataire.

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

content-script.js :

(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

Réponses

Pour écouter un message, utilisez l'événement chrome.runtime.onMessage :

// Event listener
function handleMessages(message, sender, sendResponse) {
  fetch(message.url)
    .then((response) => sendResponse({statusCode: response.status}))

  // Since `fetch` is asynchronous, must return an explicit `true`
  return true;
}

chrome.runtime.onMessage.addListener(handleMessages);

// From the sender's context...
const {statusCode} = await chrome.runtime.sendMessage({
  url: 'https://example.com'
});

Lorsque l'écouteur d'événements est appelé, une fonction sendResponse est transmise en tant que troisième paramètre. Il s'agit d'une fonction qui peut être appelée pour fournir une réponse. Par défaut, le rappel sendResponse doit être appelé de manière synchrone. Si vous souhaitez effectuer un travail asynchrone pour obtenir la valeur transmise à sendResponse, vous devez renvoyer un littéral true (et pas seulement une valeur "truthy") à partir de l'écouteur d'événements. Cela permettra de maintenir le canal de message ouvert à l'autre extrémité jusqu'à ce que sendResponse soit appelé.

Si vous appelez sendResponse sans aucun paramètre, null est envoyé en réponse.

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.

Connexions de longue durée

Pour créer un canal de transfert de messages réutilisable et de longue durée, appelez :

  • runtime.connect() pour transmettre des messages d'un script de contenu à une page d'extension
  • tabs.connect() pour transmettre des messages d'une page d'extension à un script de contenu.

Vous pouvez nommer votre canal en transmettant un paramètre d'options avec une clé name pour faire la distinction entre les différents types de connexions :

const port = chrome.runtime.connect({name: "example"});

Un cas d'utilisation potentiel pour une connexion de longue durée est une extension de remplissage automatique de formulaires. Le script de contenu peut ouvrir un canal vers la page d'extension pour une connexion spécifique et envoyer un message à l'extension pour chaque élément d'entrée sur la page afin de demander les données du formulaire à remplir. La connexion partagée permet à l'extension de partager l'état entre les composants de l'extension.

Lors de l'établissement d'une connexion, chaque extrémité se voit attribuer un objet runtime.Port pour l'envoi et la réception de messages via cette connexion.

Utilisez le code suivant pour ouvrir un canal à partir d'un script de contenu, et envoyer et écouter des messages :

content-script.js :

const port = chrome.runtime.connect({name: "knockknock"});
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"});
  }
});
port.postMessage({joke: "Knock knock"});

Pour envoyer une requête de l'extension à un script de contenu, remplacez l'appel à runtime.connect() dans l'exemple précédent par tabs.connect().

Pour gérer les connexions entrantes pour un script de contenu ou une page d'extension, configurez un écouteur d'événements runtime.onConnect. Lorsqu'une autre partie de votre extension appelle connect(), cet événement et l'objet runtime.Port sont activés. Le code permettant de répondre aux connexions entrantes se présente comme suit :

service-worker.js :

chrome.runtime.onConnect.addListener(function(port) {
  if (port.name !== "knockknock") {
    return;
  }
  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."});
    }
  });
});

Sérialisation

Dans Chrome, les API de transfert de messages utilisent la sérialisation JSON. Cela signifie qu'un message (et les réponses fournies par les destinataires) peut contenir n'importe quelle valeur JSON valide (null, booléen, nombre, chaîne, tableau ou objet). Les autres valeurs seront forcées en valeurs sérialisables.

Il est important de noter que cela diffère des autres navigateurs qui implémentent les mêmes API avec l'algorithme structured clone.

Durée de vie du port

Les ports sont conçus comme un mécanisme de communication bidirectionnel entre les différentes parties d'une extension. Lorsqu'une partie d'une extension appelle tabs.connect(), runtime.connect() ou runtime.connectNative(), elle crée un port qui peut envoyer des messages immédiatement à l'aide de postMessage().

Si un onglet contient plusieurs frames, l'appel de tabs.connect() appelle l'événement runtime.onConnect une fois pour chaque frame de l'onglet. De même, si runtime.connect() est appelé, l'événement onConnect peut se déclencher une fois pour chaque frame du processus d'extension.

Vous pouvez avoir besoin de savoir quand une connexion est fermée, par exemple si vous gérez des états distincts pour chaque port ouvert. Pour ce faire, écoutez l'événement runtime.Port.onDisconnect. Cet événement se déclenche lorsqu'il n'y a pas de ports valides à l'autre extrémité du canal, ce qui peut avoir l'une des causes suivantes :

  • Il n'y a pas d'écouteurs pour runtime.onConnect à l'autre bout.
  • L'onglet contenant le port est déchargé (par exemple, si l'utilisateur y accède).
  • Le frame dans lequel connect() a été appelé a été déchargé.
  • Tous les cadres ayant reçu le port (via runtime.onConnect) ont été déchargés.
  • runtime.Port.disconnect() est appelé par l'autre extrémité. Si un appel connect() génère plusieurs ports côté récepteur et que disconnect() est appelé sur l'un de ces ports, l'événement onDisconnect ne se déclenche que sur le port d'envoi, et non sur les autres ports.

Messagerie multi-extensions

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

Pour écouter les requêtes et les connexions entrantes d'autres extensions, utilisez les méthodes runtime.onMessageExternal ou runtime.onConnectExternal. Voici un exemple de chacun :

service-worker.js

// For a single request:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id !== allowlistedExtension) {
      return; // don't allow this extension access
    }
    if (request.getTargetData) {
      sendResponse({ targetData: targetData });
    } else if (request.activateLasers) {
      const 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.
  });
});

Pour envoyer un message à une autre extension, transmettez l'ID de l'extension avec laquelle vous souhaitez communiquer comme suit :

service-worker.js

// The ID of the extension we want to talk to.
const laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// For a minimal request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  }
);

// For a long-lived connection:
const port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

Envoyer des messages depuis des pages Web

Les extensions peuvent également recevoir des messages de pages Web et y répondre. Pour envoyer des messages d'une page Web à une extension, spécifiez dans votre manifest.json les sites Web à partir desquels vous souhaitez autoriser les messages à l'aide de la clé de fichier manifeste "externally_connectable". Exemple :

manifest.json

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

L'API de messagerie est ainsi exposée à toutes les pages correspondant aux formats 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" ne sont pas acceptés. Vous pouvez utiliser <all_urls> pour accéder à tous les domaines.

Utilisez les API runtime.sendMessage() ou runtime.connect() pour envoyer un message à une extension spécifique. Exemple :

webpage.js

// The ID of the extension we want to talk to.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';

// Check if extension is installed
if (chrome && chrome.runtime) {
  // Make a request:
  chrome.runtime.sendMessage(
    editorExtensionId,
    {
      openUrlInEditor: url
    },
    (response) => {
      if (!response.success) handleError(url);
    }
  );
}

À partir de votre extension, écoutez les messages des pages Web à l'aide des API runtime.onMessageExternal ou runtime.onConnectExternal, comme dans la messagerie inter-extensions. Exemple :

service-worker.js

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

Il n'est pas possible d'envoyer un message d'une extension à une page Web.

Messagerie native

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

Considérations de sécurité

Voici quelques points à prendre en compte concernant la sécurité des messages.

Les scripts de contenu sont moins fiables

Les scripts de contenu sont moins fiables que le service worker de l'extension. Par exemple, une page Web malveillante peut compromettre le processus de rendu qui exécute les scripts de contenu. Partez du principe que les messages provenant d'un script de contenu ont pu être créés par un pirate informatique et assurez-vous de valider et d'assainir toutes les entrées. Considérez que toutes les données envoyées au script de contenu peuvent être divulguées sur 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

Veillez à protéger vos scripts contre le script intersite. Lorsque vous recevez des données d'une source non fiable, comme une saisie utilisateur, d'autres sites Web via un script de contenu ou une API, veillez à ne pas les interpréter comme du code HTML ni à les utiliser d'une manière qui pourrait permettre l'exécution de code inattendu.

Méthodes plus sûres

Utilisez des API qui n'exécutent pas de scripts lorsque cela est possible :

service-worker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // JSON.parse doesn't evaluate the attacker's scripts.
  const resp = JSON.parse(response.farewell);
});

service-worker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});
Méthodes non sécurisées

Évitez d'utiliser les méthodes suivantes qui rendent votre extension vulnérable :

service-worker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating a malicious script!
  const resp = eval(`(${response.farewell})`);
});

service-worker.js

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