Debido a que las secuencias de comandos de contenido se ejecutan en el contexto de una página web, no de la extensión que las ejecuta, a menudo necesitan formas 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 al trabajador del servicio para que muestre un ícono de acción para esa página.
Esta comunicación usa el paso de mensajes, lo que permite que las extensiones y las secuencias de comandos de contenido escuchen los mensajes de los demás y respondan en el mismo canal. Un mensaje puede contener cualquier objeto JSON válido (nulo, booleano, número, cadena, array o objeto). Existen dos APIs de transmisión de mensajes: una para solicitudes únicas y una más compleja para conexiones de larga duración que permiten el envío de varios mensajes. Si deseas obtener información para enviar mensajes entre extensiones, consulta la sección Mensajes entre extensiones.
Solicitudes únicas
Para enviar un solo mensaje a otra parte de tu extensión y, de manera opcional, obtener una respuesta, llama a runtime.sendMessage()
o tabs.sendMessage()
.
Estos métodos te permiten enviar un mensaje JSON serializable único desde una secuencia de comandos de contenido a la extensión, o bien de la extensión a una secuencia de comandos de contenido. Para controlar la respuesta, usa la promesa que se muestra. Para lograr la retrocompatibilidad con extensiones anteriores, puedes pasar una devolución de llamada como
el último argumento. No puedes usar una promesa y una devolución de llamada en la misma llamada.
Cuando envías un mensaje, se pasa un tercer argumento opcional, sendResponse
, al objeto de escucha de eventos que controla el mensaje. Esta es una función que toma un objeto serializable JSON que se usa como valor que se muestra a la función que envió el mensaje. De forma predeterminada, se debe llamar a la devolución de llamada sendResponse
de forma síncrona. Si deseas realizar un trabajo asíncrono para obtener el valor que se pasa a sendResponse
, debes mostrar un true
literal (no solo un valor verdadero) desde el objeto de escucha de eventos. De esta manera, el canal de mensajes permanecerá abierto para el otro extremo hasta que se llame a sendResponse
.
// Event listener
function handleMessages(message, sender, sendResponse) {
fetch(message.url)
.then((response) => sendResponse({statusCode: response.status}))
// Since `fetch` is asynchronous, must send an explicit `true`
return true;
}
// Message sender
const {statusCode} = await chrome.runtime.sendMessage({
url: 'https://example.com'
});
Para obtener información sobre cómo convertir devoluciones de llamada en promesas y usarlas en extensiones, consulta la guía de migración de Manifest V3.
El envío de una solicitud desde una secuencia de comandos de contenido se ve de la siguiente manera:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Si quieres responder de forma síncrona a un mensaje, simplemente llama a sendResponse
una vez que tengas la respuesta y muestra false
para indicar que se completó. Para responder de forma asíncrona, muestra true
para mantener activa la devolución de llamada sendResponse
hasta que esté todo listo para usarla. Las funciones asíncronas no son compatibles porque devuelven una promesa, que no es compatible.
Para enviar una solicitud a una secuencia de comandos de contenido, especifica a qué pestaña se aplica la solicitud, como se muestra a continuación. Este ejemplo funciona en service workers, ventanas emergentes y páginas chrome-extension:// abiertas como pestañas.
(async () => {
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Para recibir el mensaje, configura un objeto de escucha de eventos runtime.onMessage
. Estos usan el mismo código en las extensiones y las secuencias de comandos del contenido:
content-script.js o service-worker.js:
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. Para usar sendResponse()
de forma asíncrona, agrega return true;
al controlador de eventos onMessage
.
Si varias páginas escuchan 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.
Conexiones duraderas
Para crear un canal de transferencia de mensajes reutilizable y duradero, llama a runtime.connect()
para pasar mensajes de una secuencia de comandos de contenido a una página de extensión, o bien a tabs.connect()
para pasar mensajes de una página de extensión a una secuencia de comandos de contenido. Puedes asignarle un nombre a tu canal para distinguir entre los diferentes tipos de conexiones.
Un posible caso de uso para una conexión de larga duración es una extensión de llenado automático de formularios. La secuencia de comandos de contenido puede abrir un canal a la página de la extensión para un acceso específico 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 comparta el estado entre los componentes de la extensión.
Cuando se establece una conexión, a cada extremo se le asigna un objeto runtime.Port
para enviar y recibir mensajes a través de esa conexión.
Usa el siguiente código para abrir un canal desde una secuencia de comandos de contenido y enviar y escuchar mensajes:
content-script.js:
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"});
});
Para enviar una solicitud de la extensión a una secuencia de comandos de contenido, reemplaza la llamada a runtime.connect()
en el ejemplo anterior por tabs.connect()
.
Para controlar las conexiones entrantes de una secuencia de comandos de contenido o una página de extensión, configura un objeto de escucha de eventos runtime.onConnect
. Cuando otra parte de tu extensión llama a connect()
, se activa este evento y el objeto runtime.Port
. El código para responder a las conexiones entrantes se ve de la siguiente manera:
service-worker.js:
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."});
});
});
Duración del puerto
Los puertos se diseñaron como un método de comunicación bidireccional entre diferentes partes de la extensión. Una trama de nivel superior es la parte más pequeña de una extensión que puede usar un puerto.
Cuando una parte de una extensión llama a tabs.connect()
, runtime.connect()
o runtime.connectNative()
, se crea un puerto que puede enviar mensajes de inmediato con postMessage()
.
Si hay varios marcos en una pestaña, llamar a tabs.connect()
invoca el evento runtime.onConnect
una vez por cada marco de la pestaña. De manera similar, si se llama a runtime.connect()
, el evento onConnect
puede activarse una vez por cada fotograma en el proceso de extensión.
Es posible que desees saber cuándo se cierra una conexión, por ejemplo, si mantienes estados separados para cada puerto abierto. Para ello, escucha el evento runtime.Port.onDisconnect
. Este
evento se activa cuando no hay puertos válidos en el otro extremo del canal, lo que puede deberse a cualquiera de las siguientes causas:
- No hay objetos de escucha para
runtime.onConnect
en el otro extremo. - Se descarga la pestaña que contiene el puerto (por ejemplo, si se navega por la pestaña).
- Se descargó el marco en el que se llamó a
connect()
. - Se descargaron todas las tramas que recibieron el puerto (a través de
runtime.onConnect
). - El otro extremo llama a
runtime.Port.disconnect()
. Si una llamada aconnect()
genera varios puertos en el extremo del receptor y se llama adisconnect()
en cualquiera de estos puertos, el eventoonDisconnect
solo se activa en el puerto de envío, 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 mensajes para comunicarte con otras extensiones. Esto te permite exponer una API pública para que la usen otras extensiones.
Para detectar solicitudes y conexiones entrantes de otras extensiones, usa los métodos runtime.onMessageExternal
o runtime.onConnectExternal
. A continuación, se muestra un ejemplo de cada uno:
service-worker.js
// For a single request:
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.
});
});
Para enviar un mensaje a otra extensión, pasa el ID de la extensión con la que deseas comunicarte de la siguiente manera:
service-worker.js
// The ID of the extension we want to talk to.
var 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:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Cómo enviar mensajes desde páginas web
Las extensiones también pueden recibir y responder mensajes de otras páginas web, pero no pueden enviar mensajes a páginas web. Para enviar mensajes de una página web a una extensión, especifica en tu manifest.json
con qué sitios web deseas comunicarte con la clave de manifiesto "externally_connectable"
. Por ejemplo:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Esto expone 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, no se admiten patrones de nombres de host como "*", "*.com", "*.co.uk" y "*.appspot.com". A partir de Chrome 107, puedes usar <all_urls>
para acceder a todos los dominios. Ten en cuenta que, como afecta a todos los hosts, las opiniones de Chrome Web Store para las extensiones que lo usan pueden tardar más.
Usa las APIs de runtime.sendMessage()
o runtime.connect()
para enviar un mensaje a una app o extensión específica. Por ejemplo:
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);
}
);
}
Desde tu extensión, escucha los mensajes de las páginas web con las APIs de runtime.onMessageExternal
o runtime.onConnectExternal
, como en los mensajes entre extensiones. Por ejemplo:
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);
});
Mensajería nativa
Las extensiones pueden intercambiar mensajes con aplicaciones nativas que están registradas como un host de mensajería nativa. Para obtener más información sobre esta función, consulta Mensajes nativos.
Consideraciones de seguridad
Estas son algunas consideraciones de seguridad relacionadas con los mensajes.
Las secuencias de comandos de contenido son menos confiables
Las secuencias de comandos de contenido son menos confiables que el trabajador de servicio de la extensión. Por ejemplo, una página web maliciosa podría vulnerar el proceso de renderización que ejecuta las secuencias de comandos de contenido. Supongamos que un atacante pudo haber creado los mensajes de una secuencia de comandos de contenido y asegúrate de validar y limpiar todas las entradas. Supongamos que los datos que se envían a la secuencia de comandos de contenido podrían filtrarse a la página web. Limita el alcance de las acciones privilegiadas que pueden activar los mensajes recibidos de las secuencias de comandos de contenido.
Secuencia de comandos entre sitios
Asegúrate de proteger tus secuencias de comandos contra los ataques dirigidos a aplicaciones. Cuando recibas datos de una fuente no confiable, como la entrada del usuario, otros sitios web a través de una secuencia de comandos de contenido o una API, ten cuidado de no interpretarlos como HTML ni usarlos de una manera que pueda permitir que se ejecute código inesperado.
Usa APIs que no ejecuten secuencias de comandos siempre que sea posible:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. var 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; });
Evita usar los siguientes métodos que hacen que tu extensión sea vulnerable:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be evaluating a malicious script! var 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; });