Transmisión de mensajes

Dado que las secuencias de comandos de contenido se ejecutan en el contexto de una página web y no en 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 puede 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 a fin de mostrar un ícono de acción de la página para esa página.

La comunicación entre las extensiones y sus secuencias de comandos de contenido funciona mediante la transferencia 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, string, objeto o matriz). Hay una API simple para solicitudes únicas y una API más compleja que te permite tener conexiones de larga duración para intercambiar varios mensajes con un contexto compartido. También es posible enviar un mensaje a otra extensión si conoces su ID, lo cual se describe en la sección mensajes de extensión cruzada.

Solicitudes únicas y sencillas

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

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

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

Enviar una solicitud de 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. Esto se ve igual desde una página de extensión o secuencia de comandos de contenido.

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 detectan eventos onMessage, solo la primera que llame a sendResponse() para un evento en particular podrá enviar la respuesta de manera correcta. 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 un valor verdadero o si la devolución de llamada sendResponse es de recolección de elementos no utilizados.

Conexiones de larga duración

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 de larga duración desde tu secuencia de comandos de contenido a una página de extensión , o viceversa, con runtime.connect o tabs.connect, respectivamente . De manera opcional, el canal puede tener un nombre, lo que te permite distinguir entre diferentes tipos de conexiones.

Un caso de uso podría ser una extensión automática de formulario completado. La secuencia de comandos de contenido podría abrir un canal a la página de la extensión para un acceso en particular y enviar un mensaje a la extensión para cada elemento de entrada en la página a fin de solicitar que se completen los datos del formulario. La conexión compartida permite que la extensión mantenga el estado compartido vinculando los varios mensajes provenientes de la secuencia de comandos del contenido.

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

Sigue estos pasos para 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"});
});

Enviar una solicitud de 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 para conectarte en el ejemplo anterior por tabs.connect.

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

Vida útil del puerto

Los puertos están diseñados como un método de comunicación bidireccional entre las diferentes partes de la extensión, en la 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 mediante postMessage.

Si hay varios fotogramas en una pestaña, llamar a tabs.connect generará varias invocaciones del evento runtime.onConnect (una vez por cada fotograma de la pestaña). Del mismo modo, si se usa runtime.connect, es posible que el evento onConnect se active varias veces (una vez por cada fotograma del proceso de extensión).

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

  • No hay objetos de escucha para runtime.onConnect en el otro extremo.
  • La pestaña que contiene el puerto está descargada (p.ej., si se navega por la pestaña).
  • Se descargó el fotograma 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 a connect da como resultado varios puertos en el extremo del receptor y se llama a disconnect() en cualquiera de estos puertos, el evento onDisconnect solo se activa en el puerto del emisor 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 mensajería para comunicarte con otras extensiones. Esto te permite exponer una API pública que otras extensiones pueden aprovechar.

Detectar solicitudes y conexiones entrantes es similar al caso interno, excepto que usas los métodos runtime.onMessageExternal o runtime.onConnectExternal. A continuación, se muestra un ejemplo de cada una:

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

Enviar mensajes desde páginas web

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

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

Esta acción expondrá la API de mensajería 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, los patrones de nombre de host como "*", "*.com", "*.co.uk" y "*.appspot.com" están prohibidos. Desde la página web, usa las APIs 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 APIs runtime.onMessageExternal o runtime.onConnectExternal, 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 apps pueden intercambiar mensajes con aplicaciones nativas que están registradas como hosts 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 del 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 comprometer el proceso del procesador donde se ejecutan las secuencias de comandos del contenido). Supongamos que un atacante podría haber creado los mensajes de una secuencia de comandos de contenido y asegúrate de validar y limpiar todas las entradas. Supongamos que cualquier dato enviado a la secuencia de comandos del contenido se pueda filtrar a la página web. Limita el alcance de las acciones con privilegios que pueden activarse los mensajes recibidos desde 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, estas deben tener cuidado de no ser víctimas de secuencias de comandos entre sitios. Esta recomendación 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. Específicamente, 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, es preferible usar 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 demuestra cómo una app de Chrome puede comunicarse con una app nativa. Si quieres ver más ejemplos y ayuda para ver el código fuente, consulta Muestras.