Como os scripts de conteúdo são executados no contexto de uma página da Web, e não da extensão que os executa, muitas vezes eles precisam de maneiras de se comunicar com o restante da extensão. 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, em seguida, notificar o service worker para exibir um ícone de ação para essa página.
Essa comunicação usa a transmissão de mensagens, que permite que extensões e scripts de conteúdo escutem as mensagens umas das outras e respondam no mesmo canal. Uma mensagem pode conter qualquer objeto JSON válido (nulo, booleano, número, string, matriz ou objeto). 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 mais informações sobre o envio de mensagens entre extensões, consulte a seção Mensagens entre extensões.
Solicitações únicas
Para enviar uma única mensagem para 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 única em JSON de um script de conteúdo para a
extensão ou da extensão para um script de conteúdo. Para processar a resposta, use a promessa
retornada. Para compatibilidade com extensões mais antigas, transmita um callback como
o último argumento. Não é possível usar uma promessa e um callback na mesma chamada.
Para informações sobre como converter callbacks em promessas e usá-los em extensões, consulte o guia de migração do Manifest V3.
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);
})();
Se você quiser responder a uma mensagem de forma síncrona, chame sendResponse
quando tiver a resposta e retorne false
para indicar que ela foi concluída. Para responder de forma assíncrona, retorne true
para manter o callback sendResponse
ativo até que você esteja pronto para usá-lo. Não há suporte para funções assíncronas porque elas retornam uma promessa, que não é aceita.
Para enviar uma solicitação a um script de conteúdo, especifique a guia a que a solicitação se aplica, conforme mostrado abaixo. Esse exemplo funciona em service workers, pop-ups e páginas chrome-extension:// abertas como uma guia.
(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 receber a mensagem, configure um listener de evento runtime.onMessage
. Eles
usam o mesmo código em extensões e scripts de conteúdo:
content-script.js ou 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"});
}
);
No exemplo anterior, sendResponse()
foi chamado de forma síncrona. Para usar sendResponse()
de forma assíncrona, adicione return true;
ao manipulador de eventos onMessage
.
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 de longa duração reutilizável, chame runtime.connect()
para transmitir mensagens de um script de conteúdo para uma página de extensão ou tabs.connect()
para transmitir mensagens de uma página de extensão para um script de conteúdo. Você pode nomear seu canal para
distinguir diferentes tipos de conexões.
Um possível caso de uso para uma conexão de longa duração é uma extensão automática de preenchimento de formulários. O script de conteúdo pode abrir um canal para a página da extensão para um login específico e enviar uma mensagem para a extensão para 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 componentes da 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 abaixo para abrir um canal em um script de conteúdo e enviar e detectar mensagens:
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 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 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 às conexões de entrada é semelhante ao seguinte:
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."});
});
});
Duração da porta
As portas são projetadas como um método de comunicação bidirecional entre diferentes partes da extensão. Um frame de nível superior é a menor parte de uma extensão que pode usar uma porta.
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()
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 disparado uma vez para cada frame no processo de extensão.
Talvez você queira descobrir quando uma conexão está fechada, por exemplo, se você está 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 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 por a outra extremidade. Se uma chamadaconnect()
resultar em várias portas no lado do receptor e odisconnect()
for chamado em qualquer uma dessas portas, o eventoonDisconnect
só será acionado na porta de envio, não nas outras.
Mensagens entre extensões
Além de enviar mensagens entre diferentes componentes na sua extensão, você pode usar a API de mensagens para se comunicar com outras extensões. Isso permite que você exponha uma API pública para outras extensões.
Para detectar solicitações e conexões recebidas 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 === 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 uma mensagem a outra extensão, transmita o ID da extensão com a qual você quer se comunicar da seguinte maneira:
service-worker.js (link em inglês)
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a simple 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(...);
Enviar mensagens de páginas da Web
As extensões também podem receber e responder a mensagens de outras páginas da Web, mas não podem enviar mensagens
para páginas da Web. Para enviar mensagens de uma página da Web para uma extensão,
especifique em manifest.json
quais sites você quer usar para se comunicar usando
a chave de manifesto "externally_connectable"
. 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, não é possível usar padrões de nome de host como "*",
"*.com", "*.co.uk" e "*.appspot.com". A partir do Chrome 107, é possível usar
<all_urls>
para acessar todos os domínios. Como isso afeta todos os hosts,
as avaliações da Chrome Web Store para extensões que a usam podem demorar mais.
Use as APIs runtime.sendMessage()
ou runtime.connect()
para enviar
uma mensagem a um app ou extensão específico. Exemplo:
webpage.js
// 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);
});
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);
});
Envio de mensagens nativo
As extensões podem trocar mensagens com aplicativos nativos registrados como um host de mensagens nativo. Para saber mais sobre esse recurso, consulte Mensagens nativas.
Considerações sobre segurança
Confira algumas considerações de segurança relacionadas às mensagens.
Os 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 qualquer dado enviado para o script de conteúdo possa vazar para a página da Web. Limite 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 cross-site scripting. Ao receber dados de uma fonte não confiável, como entradas de usuários, outros sites por meio de um script de conteúdo ou uma API, evite interpretá-los como HTML ou usá-los de uma forma que possa permitir a execução de um 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. 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; });
Evite usar os seguintes métodos que deixem sua extensão vulnerável:
service-worker.js (link em inglês)
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; });