Transmisión de mensajes

Dado que las secuencias de comandos de contenido se ejecutan en el contexto de una página web y no de la extensión, a menudo necesitan alguna forma de comunicarse con el resto de la extensión. Por ejemplo, una extensión de lector de RSS podría usar secuencias de comandos de contenido para detectar la presencia de un feed RSS en una página y, luego, notificar a la página en segundo plano para mostrar un ícono de acción de página para esa página.

La comunicación entre las extensiones y sus secuencias de comandos de contenido funciona mediante la transmisión de mensajes. Cualquiera de los extremos puede escuchar los mensajes enviados desde el otro extremo y responder en el mismo canal. Un mensaje puede contener cualquier objeto JSON válido (nulo, booleano, número, cadena, array u objeto). Hay una API simple para solicitudes únicas y una API más compleja que te permite tener conexiones duraderas para intercambiar varios mensajes con un contexto compartido. También es posible enviar un mensaje a otra extensión si conoces su ID, que se explica en la sección de mensajes entre extensiones.

Solicitudes simples y únicas

Si solo necesitas enviar un mensaje a otra parte de tu extensión (y, de manera opcional, obtener una respuesta), debes usar runtime.sendMessage o tabs.sendMessage simplificados . Esto te permite enviar un mensaje serializable en formato JSON único desde una secuencia de comandos de contenido a la extensión o viceversa, respectivamente. Un parámetro de devolución de llamada opcional te permite controlar la respuesta del otro lado, si la hay.

El envío de una solicitud desde una secuencia de comandos de contenido se ve de la siguiente manera:

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

El envío de una solicitud desde la extensión a una secuencia de comandos de contenido es muy similar, excepto que debes especificar a qué pestaña enviarla. En este ejemplo, se muestra cómo enviar un mensaje a la secuencia de comandos de contenido en la pestaña seleccionada.

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

En el extremo receptor, debes configurar un objeto de escucha de eventos runtime.onMessage para controlar el mensaje. Se ve igual desde una secuencia de comandos de contenido o una página de extensión.

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

En el ejemplo anterior, se llamó a sendResponse de forma síncrona. Si deseas usar sendResponse de forma asíncrona, agrega return true; al controlador de eventos onMessage.

Nota: Si varias páginas están escuchando eventos onMessage, solo la primera que llame a sendResponse() para un evento en particular podrá enviar la respuesta. Se ignorarán todas las demás respuestas a ese evento.
Nota: La devolución de llamada sendResponse solo es válida si se usa de forma síncrona o si el controlador de eventos muestra true para indicar que responderá de forma asíncrona. Se invocará automáticamente la devolución de llamada de la función sendMessage si ningún controlador muestra el valor true o si se recopila la devolución de llamada sendResponse como elemento no utilizado.

Conexiones duraderas

A veces, es útil tener una conversación que dure más que una sola solicitud y respuesta. En este caso, puedes abrir un canal duradero desde tu secuencia de comandos de contenido a una página de extensión o viceversa, usando runtime.connect o tabs.connect, respectivamente . El canal puede tener un nombre de manera opcional, lo que te permite distinguir entre diferentes tipos de conexiones.

Un caso de uso podría ser una extensión de autocompletado de formularios. La secuencia de comandos de contenido podría abrir un canal a la página de extensión para un acceso en particular y enviar un mensaje a la extensión para cada elemento de entrada de la página para solicitar los datos del formulario que se deben completar. La conexión compartida permite que la extensión mantenga el estado compartido que vincula los varios mensajes que provienen de la secuencia de comandos de contenido.

Cuando se establece una conexión, cada extremo recibe un objeto runtime.Port que se usa para enviar y recibir mensajes a través de esa conexión.

A continuación, se muestra cómo abrir un canal desde una secuencia de comandos de contenido, y enviar y escuchar mensajes:

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

El envío de una solicitud desde la extensión a una secuencia de comandos de contenido es muy similar, excepto que debes especificar a qué pestaña conectarte. Simplemente reemplaza la llamada a connect en el ejemplo anterior por tabs.connect.

Para controlar las conexiones entrantes, debes configurar un objeto de escucha de eventos runtime.onConnect. Se ve igual desde una secuencia de comandos de contenido o una página de extensión. Cuando otra parte de tu extensión llama a "connect()", se activa este evento, junto con el objeto runtime.Port que puedes usar para enviar y recibir mensajes a través de la conexión. Así se ve la respuesta a las conexiones 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."});
  });
});

Tiempo de actividad del puerto

Los puertos están diseñados como un método de comunicación bidireccional entre diferentes partes de la extensión, en el que un marco (de nivel superior) se considera la parte más pequeña. Cuando se llama a tabs.connect, runtime.connect o runtime.connectNative, se crea un puerto. Este puerto se puede usar de inmediato para enviar mensajes al otro extremo a través de postMessage.

Si hay varios marcos en una pestaña, llamar a tabs.connect genera varias invocaciones de l evento runtime.onConnect (una para cada marco de la pestaña). Del mismo modo, si se usa runtime.connect, el evento onConnect puede activarse varias veces (una para cada marco del proceso de extensión).

Es posible que desees saber cuándo se cierra una conexión, por ejemplo, si mantienes un estado independiente para cada puerto abierto. Para ello, puedes escuchar el evento runtime.Port.onDisconnect. Este evento se activa cuando no hay puertos válidos en el otro lado del canal. Esto sucede en las siguientes situaciones:

  • No hay objetos de escucha para runtime.onConnect en el otro extremo.
  • Se descarga la pestaña que contiene el puerto (p.ej., si se navega por la pestaña).
  • Se descargó el marco desde el que se llamó a connect.
  • Se descargaron todos los marcos que recibieron el puerto (a través de runtime.onConnect).
  • El otro extremo llama a runtime.Port.disconnect. Ten en cuenta que, si una llamada connect genera varios puertos en el extremo del receptor y se llama a disconnect() en cualquiera de estos puertos, entonces el evento onDisconnect solo se activa en el puerto del remitente y no en los otros puertos.

Mensajes entre extensiones

Además de enviar mensajes entre diferentes componentes de tu extensión, puedes usar la API de Messaging para comunicarte con otras extensiones. Esto te permite exponer una API pública que otras extensiones pueden aprovechar.

La escucha de solicitudes y conexiones entrantes es similar al caso interno, excepto que usas los runtime.onMessageExternal o runtime.onConnectExternal. A continuación, se muestra un ejemplo de cada uno:

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

Del mismo modo, enviar un mensaje a otra extensión es similar a enviar uno dentro de tu extensión. La única diferencia es que debes pasar el ID de la extensión con la que deseas comunicarte. Por ejemplo:

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

Envía mensajes desde páginas web

Al igual que los mensajes entre extensiones, tu app o extensión puede recibir mensajes de páginas web normales y responderlos. Para usar esta función, primero debes especificar en tu manifest.json con qué sitios web deseas comunicarte. Por ejemplo:

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

Esto expondrá la API de Messaging a cualquier página que coincida con los patrones de URL que especifiques. El patrón de URL debe contener al menos un dominio de segundo nivel, es decir, se prohíben los patrones de nombres de host como "*", "*.com", "*.co.uk" y "*.appspot.com". Desde la página web, usa las APIs de runtime.sendMessage o runtime.connect para enviar un mensaje a una app o extensión específica. Por ejemplo:

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

Desde tu app o extensión, puedes escuchar mensajes de páginas web a través de las runtime.onMessageExternal o runtime.onConnectExternal APIs, de forma similar a los mensajes entre extensiones. Solo la página web puede iniciar una conexión. A continuación, se muestra un ejemplo:

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

Mensajería nativa

Las extensiones y las apps pueden intercambiar mensajes con aplicaciones nativas registradas como un host de mensajería nativa. Para obtener más información sobre esta función, consulta Mensajería nativa.

Consideraciones de seguridad

Las secuencias de comandos de contenido son menos confiables

Las secuencias de comandos de contenido son menos confiables que la página en segundo plano de la extensión (p.ej., una página web maliciosa podría poner en riesgo el proceso de renderizador en el que se ejecutan las secuencias de comandos de contenido). Supón que un atacante podría haber creado mensajes de una secuencia de comandos de contenido y asegúrate de validar y desinfectar todas las entradas. Supón que cualquier dato enviado a la secuencia de comandos de contenido podría filtrarse a la página web. Limita el alcance de las acciones con privilegios que pueden activar los mensajes recibidos de las secuencias de comandos de contenido.

Secuencia de comandos entre sitios

Cuando recibas un mensaje de una secuencia de comandos de contenido o de otra extensión, tus secuencias de comandos deben tener cuidado de no ser víctimas de secuencias de comandos entre sitios. Este consejo se aplica a las secuencias de comandos que se ejecutan dentro de la página en segundo plano de la extensión, así como a las secuencias de comandos de contenido que se ejecutan dentro de otros orígenes web. En particular, evita usar APIs peligrosas como las siguientes:

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

En su lugar, prefiere APIs más seguras que no ejecuten secuencias de comandos:

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

Ejemplos

Puedes encontrar ejemplos simples de comunicación a través de mensajes en el directorio examples/api/messaging. En el ejemplo de mensajería nativa, se muestra cómo una app de Chrome puede comunicarse con una aplicación nativa. Para obtener más ejemplos y ayuda para ver el código fuente, consulta Ejemplos.