As APIs de mensagens permitem a comunicação entre diferentes scripts em execução em contextos associados à extensão. Isso inclui a comunicação entre o service worker, chrome-extension://pages e scripts de conteúdo. Por exemplo, uma extensão de leitor de RSS pode usar scripts de conteúdo para detectar a presença de um feed RSS em uma página e notificar o service worker para atualizar o ícone de ação dessa página.
Há duas APIs de transmissão de mensagens: uma para solicitações únicas e outra mais complexa para conexões de longa duração que permitem o envio de várias mensagens.
Para informações sobre como enviar mensagens entre extensões, consulte a seção Mensagens entre extensões.
Solicitações únicas
Para enviar uma única mensagem a outra parte da extensão e, opcionalmente, receber uma
resposta, chame runtime.sendMessage() ou tabs.sendMessage().
Esses métodos permitem enviar uma mensagem serializável em JSON única de um script de conteúdo para a extensão ou da extensão para um script de conteúdo. As duas APIs retornam uma promessa que é resolvida para a resposta fornecida por um destinatário.
O envio de uma solicitação de um script de conteúdo é assim:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Respostas
Para detectar uma mensagem, use o evento chrome.runtime.onMessage:
// Event listener
function handleMessages(message, sender, sendResponse) {
if (message !== 'get-status') return;
fetch('https://example.com')
.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('get-status');
Quando o listener de eventos é chamado, uma função sendResponse é transmitida como o terceiro parâmetro. Essa é uma função que pode ser chamada para fornecer uma resposta. Por padrão, o callback sendResponse precisa ser chamado de forma síncrona.
Se você chamar sendResponse sem parâmetros, null será enviado como resposta.
Para enviar uma resposta de forma assíncrona, você tem duas opções: retornar true ou retornar uma promessa.
Retornar true
Para responder de forma assíncrona usando sendResponse(), retorne um literal true (não apenas um valor verdadeiro) do listener de eventos. Isso vai manter o canal de mensagens aberto para a outra extremidade até que sendResponse seja chamado, permitindo que você o chame mais tarde.
Retornar uma promessa
A partir do Chrome 148, é possível retornar uma promessa de um listener de mensagens para responder de forma assíncrona. Essa atualização está sendo lançada gradualmente. Portanto, talvez ela ainda não esteja disponível nos navegadores de todos os usuários. Verifique se a extensão pode processar se esse recurso está ativado ou não. O uso de return true; vai continuar funcionando para respostas assíncronas, independentemente de esse recurso estar ativado ou não.
Se a promessa for resolvida, o valor resolvido será enviado como resposta.
Se a promessa for rejeitada, a chamada sendMessage() do remetente será rejeitada com a mensagem de erro. Consulte a seção
Tratamento de erros para mais detalhes e exemplos.
Um exemplo que mostra o retorno de uma promessa que pode ser resolvida ou rejeitada:
// Event listener
function handleMessages(message, sender, sendResponse) {
// Return a promise that wraps fetch
// If the response is OK, resolve with the status. If it's not OK then reject
// with the network error that prevents the fetch from completing.
return new Promise((resolve, reject) => {
fetch('https://example.com')
.then(response => {
if (!response.ok) {
reject(response);
} else {
resolve(response.status);
}
})
.catch(error => {
reject(error);
});
});
}
chrome.runtime.onMessage.addListener(handleMessages);
Também é possível declarar um listener como async para retornar uma promessa:
chrome.runtime.onMessage.addListener(async function(message, sender) {
const response = await fetch('https://example.com');
if (!response.ok) {
// rejects the promise returned by `async function`.
throw new Error(`Fetch failed: ${response.status}`);
}
// resolves the promise returned by `async function`.
return {statusCode: response.status};
});
Retornar uma promessa: armadilhas da função async
Lembre-se de que uma função async como listener sempre retornará uma promessa, mesmo sem uma instrução return. Se um listener async não retornar um valor, a promessa será resolvida implicitamente como undefined, e null será enviado como resposta ao remetente. Isso pode causar um comportamento inesperado quando há vários listeners:
// content_script.js
function handleResponse(message) {
// The first listener promise resolves to `undefined` before the second
// listener can respond. When a listener responds with `undefined`, Chrome
// sends null as the response.
console.assert(message === null);
}
function notifyBackgroundPage() {
const sending = chrome.runtime.sendMessage('test');
sending.then(handleResponse);
}
notifyBackgroundPage();
// background.js
chrome.runtime.onMessage.addListener(async (message) => {
// This just causes the function to pause for a millisecond, but the promise
// is *not* returned from the listener so it doesn't act as a response.
await new Promise(resolve => {
setTimeout(resolve, 1, 'OK');
});
// `async` functions always return promises. So once we
// reach here there is an implicit `return undefined;`. Chrome translates
// `undefined` responses to `null`.
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
return new Promise((resolve) => {
setTimeout(resolve, 1000, 'response');
});
});
Tratamento de erros
A partir do Chrome 146, se um listener onMessage gerar um erro (de forma síncrona ou assíncrona retornando uma promessa que rejeita), a promessa retornada por sendMessage() no remetente será rejeitada com a mensagem de erro. Essa atualização está sendo lançada gradualmente. Portanto, talvez essa mudança ainda não esteja em vigor nos navegadores de todos os usuários. Verifique se a extensão pode processar se essa mudança está ativada ou não.
Se um listener tentar retornar uma resposta que não pode ser
serializada (sem detectar o TypeError resultante), isso
também será considerado como um erro de listener.
Se um listener retornar uma promessa que rejeita, ela precisará ser rejeitada com uma instância Error para que o remetente receba essa mensagem de erro. Se a promessa for rejeitada com qualquer outro valor (como null ou undefined), sendMessage() será rejeitada com uma mensagem de erro genérica.
Se vários listeners forem registrados para onMessage, apenas o primeiro listener a responder, rejeitar ou gerar um erro afetará o remetente. Todos os outros listeners serão executados, mas os resultados serão ignorados.
Exemplos
Se um listener retornar uma promessa que rejeita, sendMessage() será rejeitada:
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "some error"
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
return Promise.reject(new Error('some error'));
});
Se um listener responder com um valor que não pode ser serializado, sendMessage() será rejeitada:
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "Error: Could not serialize message."
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse(() => {}); // Functions are not serializable
return true; // Keep channel open for async sendResponse
});
Se um listener gerar um erro de forma síncrona antes que qualquer outro listener responda, a promessa sendMessage() do listener será rejeitada:
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "error!"
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
throw new Error('error!');
});
No entanto, se um listener responder antes que outro gere um erro, sendMessage() será bem-sucedida:
// sender.js
const response = await chrome.runtime.sendMessage('test');
console.log(response); // "OK"
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse('OK');
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
throw new Error('error!');
});
Conexões de longa duração
Para criar um canal de transmissão de mensagens reutilizável de longa duração, chame:
runtime.connect()para transmitir mensagens de um script de conteúdo para uma página de extensãotabs.connect()para transmitir mensagens de uma página de extensão para um script de conteúdo.
É possível nomear o canal transmitindo um parâmetro de opções com uma chave name para distinguir entre diferentes tipos de conexões:
const port = chrome.runtime.connect({name: "example"});
Um caso de uso possível para uma conexão de longa duração é uma extensão de preenchimento automático de formulários. O script de conteúdo pode abrir um canal para a página de extensão de um login específico e enviar uma mensagem para a extensão de cada elemento de entrada na página para solicitar os dados do formulário a serem preenchidos. A conexão compartilhada permite que a extensão compartilhe o estado entre os componentes da extensão.
Ao estabelecer uma conexão, cada extremidade recebe um runtime.Port objeto para
enviar e receber mensagens por essa conexão.
Use o código a seguir para abrir um canal de um script de conteúdo e enviar e detectar mensagens:
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"});
Para enviar uma solicitação da extensão para um script de conteúdo, substitua a chamada para runtime.connect()
no exemplo anterior por tabs.connect().
Para processar conexões recebidas de um script de conteúdo ou de uma página de extensão, configure um runtime.onConnect listener de eventos. Quando outra parte da sua
extensão chama connect(), ela ativa esse evento e o runtime.Port
objeto. O código para responder a conexões recebidas é assim:
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."});
}
});
});
Serialização
No Chrome, as APIs de transmissão de mensagens usam a serialização JSON. Isso é diferente de outros navegadores que implementam as mesmas APIs com o algoritmo de clone estruturado.
Isso significa que uma mensagem (e as respostas fornecidas pelos destinatários) pode conter qualquer
valor
JSON.stringify()
válido. Outros valores serão forçados a valores serializáveis (principalmente undefined será serializado como null);
Limites de tamanho de mensagens
O tamanho máximo de uma mensagem é de 64 MiB.
Ciclo de vida da porta
As portas são projetadas como um mecanismo de comunicação bidirecional entre diferentes partes de uma extensão. Quando parte de uma extensão chama
tabs.connect(), runtime.connect() ou
runtime.connectNative(), ela cria uma
Porta que pode enviar mensagens imediatamente usando
postMessage().
Se houver vários frames em uma guia, chamar tabs.connect() vai invocar
o runtime.onConnect evento uma vez para cada frame na guia. Da mesma forma, se
runtime.connect() for chamado, o evento onConnect poderá ser acionado uma vez para cada
frame no processo de extensão.
Talvez você queira descobrir quando uma conexão é fechada, por exemplo, se estiver mantendo estados separados para cada porta aberta. Para fazer isso, detecte o runtime.Port.onDisconnect evento. Esse evento é acionado quando não há portas válidas na outra extremidade do canal, o que pode ter qualquer uma das seguintes causas:
- Não há listeners para
runtime.onConnectna outra extremidade. - A guia que contém a porta é descarregada (por exemplo, se a guia for navegada).
- O frame em que
connect()foi chamado foi descarregado. - Todos os frames que receberam a porta (via
runtime.onConnect) foram descarregados. runtime.Port.disconnect()é chamado pela outra extremidade. Se umaconnect()chamada resultar em várias portas na extremidade do receptor edisconnect()for chamada em qualquer uma dessas portas, o eventoonDisconnectserá acionado apenas na porta de envio, não nas outras portas.
Mensagens entre extensões
Além de enviar mensagens entre diferentes componentes da extensão, você pode usar a API de mensagens para se comunicar com outras extensões. Isso permite expor uma API pública para outras extensões.
Para detectar solicitações e conexões recebidas de outras extensões, use os
runtime.onMessageExternal
ou runtime.onConnectExternal métodos. Confira um exemplo de cada:
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.
});
});
Para enviar uma mensagem a outra extensão, transmita o ID da extensão com que você quer se comunicar da seguinte maneira:
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(...);
Enviar mensagens de páginas da Web
As extensões também podem receber e responder a mensagens de páginas da Web. Para enviar
mensagens de uma página da Web para uma extensão, especifique no manifest.json quais
sites você quer permitir mensagens usando a
"externally_connectable" chave de manifesto. Por exemplo:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Isso expõe a API de mensagens a qualquer página que corresponda aos padrões de correspondência especificados.
Use as APIs runtime.sendMessage() ou runtime.connect() para enviar
uma mensagem a uma extensão específica. Por exemplo:
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);
}
);
}
Na extensão, detecte mensagens de páginas da Web usando as
runtime.onMessageExternal ou runtime.onConnectExternal
APIs, como nas mensagens entre extensões. Confira um exemplo:
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);
});
Não é possível enviar uma mensagem de uma extensão para uma página da Web.
Envio de mensagens nativo
As extensões podem trocar mensagens com aplicativos nativos registrados como um host de mensagens nativas. Para saber mais sobre esse recurso, consulte Envio de mensagens nativo.
Considerações sobre segurança
Confira algumas considerações de segurança relacionadas a mensagens.
Scripts de conteúdo são menos confiáveis
Os scripts de conteúdo são menos confiáveis do que o service worker da extensão. Por exemplo, uma página da Web maliciosa pode comprometer o processo de renderização que executa os scripts de conteúdo. Suponha que as mensagens de um script de conteúdo possam ter sido criadas por um invasor e valide e limpe todas as entradas. Suponha que todos os dados enviados ao script de conteúdo possam vazar para a página da Web. Limite o escopo de ações privilegiadas que podem ser acionadas por mensagens recebidas de scripts de conteúdo.
Scripting em vários locais
Proteja seus scripts contra scripting em vários locais. Ao receber dados de uma fonte não confiável, como entrada do usuário, outros sites por um script de conteúdo ou uma API, evite interpretar isso como HTML ou usá-lo de uma maneira que possa permitir a execução de código inesperado.
Use APIs que não executam scripts sempre que possível:
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; });
Evite usar os seguintes métodos que tornam sua extensão vulnerável:
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; });