As APIs de mensagens permitem a comunicação entre diferentes scripts em execução em contextos associados à sua 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 para outra parte da sua extensão e, opcionalmente, receber uma
resposta, chame runtime.sendMessage()
ou tabs.sendMessage()
.
Esses métodos permitem enviar uma mensagem única serializável em JSON 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 Promise
que é resolvida com a resposta fornecida por um destinatário.
Enviar 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) {
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'
});
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ê quiser
fazer um trabalho assíncrono para receber o valor transmitido a 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.
Se você chamar sendResponse
sem parâmetros, null
será enviado como resposta.
Se várias páginas estiverem aguardando eventos onMessage
, apenas a primeira a chamar sendResponse()
para um evento específico vai conseguir enviar a resposta. Todas as outras respostas a esse evento serão ignoradas.
Conexões de longa duração
Para criar um canal de transmissão de mensagens reutilizável e 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 seu 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 possível caso de uso 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 à extensão para cada elemento de entrada na página e solicitar os dados do formulário para preenchimento. A conexão compartilhada permite que a extensão compartilhe o estado entre componentes de extensão.
Ao estabelecer uma conexão, cada extremidade recebe um objeto runtime.Port
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 ouvir 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 a 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 listener de eventos runtime.onConnect
. Quando outra parte da
extensão chama connect()
, ela ativa esse evento e o objeto runtime.Port
. O código para responder a conexões de entrada é 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 significa que uma mensagem (e as respostas fornecidas pelos destinatários) pode conter qualquer valor JSON válido (nulo, booleano, número, string, matriz ou objeto). Outros valores serão convertidos em valores serializáveis.
Isso é diferente de outros navegadores que implementam as mesmas APIs com o algoritmo de clonagem estruturada.
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 um
Port que pode enviar mensagens imediatamente usando
postMessage()
.
Se houver vários frames em uma guia, chamar tabs.connect()
vai invocar
o evento runtime.onConnect
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 saber quando uma conexão é fechada, por exemplo, se estiver mantendo estados separados para cada porta aberta. Para fazer isso, detecte o evento runtime.Port.onDisconnect
. 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.onConnect
na 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 uma chamadaconnect()
resultar em várias portas na extremidade do receptor edisconnect()
for chamada em qualquer uma dessas portas, o eventoonDisconnect
será disparado apenas na porta de envio, não nas outras portas.
Mensagens entre extensões
Além de enviar mensagens entre diferentes componentes na sua extensão, você pode usar a API Messaging para se comunicar com outras extensões. Isso permite expor uma API pública para outras extensões usarem.
Para detectar solicitações e conexões de outras extensões, use os métodos
runtime.onMessageExternal
ou runtime.onConnectExternal
. Confira um exemplo de
cada um:
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 para 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 chave
"externally_connectable"
do manifesto. 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 URL especificados. O padrão de URL precisa conter pelo menos um domínio de segundo nível. Ou seja, padrões de nome do host como "*", "*.com", "*.co.uk" e "*.appspot.com" não são aceitos. É possível usar
<all_urls>
para acessar todos os domínios.
Use as APIs runtime.sendMessage()
ou runtime.connect()
para enviar
uma mensagem a uma extensão específica. 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 sua extensão, ouça mensagens de páginas da Web usando as
APIs runtime.onMessageExternal
ou runtime.onConnectExternal
como em mensagens entre extensões. Veja 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 Mensagens nativas.
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. Considere que todos os dados enviados ao script de conteúdo podem vazar para a página da Web. Limitar o escopo das 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 entre sites. 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, tome cuidado para não interpretar isso como HTML ou usar de uma forma que permita 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; });