Como os scripts de conteúdo são executados no contexto de uma página da Web e não da extensão, eles geralmente precisam de alguma forma 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 notificar a página em segundo plano para mostrar um ícone de ação da página.
A comunicação entre extensões e scripts de conteúdo funciona usando a transmissão de mensagens. Qualquer lado pode ouvir mensagens enviadas da outra extremidade e responder no mesmo canal. Uma mensagem pode conter qualquer objeto JSON válido (nulo, booleano, número, string, matriz ou objeto). Há uma API simples para solicitações únicas e uma API mais complexa que permite ter conexões de longa duração para trocar várias mensagens com um contexto compartilhado. Também é possível enviar uma mensagem para outra extensão se você souber o ID dela, o que é abordado na seção mensagens entre extensões.
Solicitações simples e únicas
Se você só precisar enviar uma única mensagem para outra parte da extensão (e, opcionalmente, receber uma resposta), use o runtime.sendMessage ou tabs.sendMessage simplificado . Isso permite enviar uma mensagem única serializável em JSON de um script de conteúdo para a extensão ou vice-versa, respectivamente. Um parâmetro de callback opcional permite processar a resposta do outro lado, se houver.
Enviar uma solicitação de um script de conteúdo é assim:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
Enviar uma solicitação da extensão para um script de conteúdo é muito semelhante, mas você precisa especificar para qual guia enviar. Este exemplo demonstra o envio de uma mensagem para o script de conteúdo na guia selecionada.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
Na extremidade de recebimento, você precisa configurar um listener de eventos runtime.onMessage para processar a mensagem. Isso parece igual em um script de conteúdo ou página de extensão.
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 acima, sendResponse foi chamado de forma síncrona. Se você quiser usar sendResponse de forma assíncrona, adicione return true; ao manipulador de eventos onMessage.
sendResponse só é válido se usado de forma síncrona ou se o manipulador de eventos retornar true para indicar que vai responder de forma assíncrona. O callback da função sendMessage será invocado automaticamente se nenhum manipulador retornar "true" ou se o callback sendResponse for coletado como lixo.Conexões de longa duração
Às vezes, é útil ter uma conversa que dure mais do que uma única solicitação e resposta. Nesse caso, você pode abrir um canal de longa duração do script de conteúdo para uma página de extensão ou vice-versa, usando runtime.connect ou tabs.connect, respectivamente . O canal pode ter um nome, o que permite distinguir entre diferentes tipos de conexões.
Um caso de uso pode ser 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 para solicitar o preenchimento dos dados do formulário. A conexão compartilhada permite que a extensão mantenha o estado compartilhado vinculando as várias mensagens do script de conteúdo.
Ao estabelecer uma conexão, cada extremidade recebe um objeto runtime.Port, que é usado para enviar e receber mensagens por essa conexão.
Saiba como abrir um canal de um script de conteúdo e enviar e ouvir mensagens:
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 uma solicitação da extensão para um script de conteúdo é muito semelhante, exceto que você precisa especificar a guia a que se conectar. Basta substituir a chamada para conectar no exemplo acima por tabs.connect.
Para processar conexões de entrada, é necessário configurar um listener de eventos runtime.onConnect. Isso parece igual em um script de conteúdo ou em uma página de extensão. Quando outra parte da extensão chama "connect()", esse evento é disparado, junto com o objeto runtime.Port que você pode usar para enviar e receber mensagens pela conexão. Veja como responder a conexões recebidas:
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."});
});
});
Ciclo de vida da porta
As portas são projetadas como um método de comunicação bidirecional entre diferentes partes da extensão, em que um frame (de nível superior) é visto como a menor parte. Ao chamar tabs.connect, runtime.connect ou runtime.connectNative, uma Port é criada. Essa porta pode ser usada imediatamente para enviar mensagens à outra extremidade via postMessage.
Se houver vários frames em uma guia, chamar tabs.connect resultará em várias invocações do evento runtime.onConnect (uma vez para cada frame na guia). Da mesma forma, se runtime.connect for usado, o evento onConnect poderá ser disparado várias vezes (uma vez para cada frame no processo de extensão).
Talvez você queira saber quando uma conexão é fechada, por exemplo, se estiver mantendo um estado separado para cada porta aberta. Para isso, detecte o evento runtime.Port.onDisconnect. Esse evento é acionado quando não há portas válidas do outro lado do canal. Isso acontece nas seguintes situações:
- 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 de onde
connectfoi chamado foi descarregado. - Todos os frames que receberam a porta (via runtime.onConnect) foram descarregados.
- runtime.Port.disconnect é chamado pela outra extremidade. Note que se uma chamada
connectresultar em várias portas na extremidade do receptor, edisconnect()for chamado em qualquer uma dessas portas, o eventoonDisconnectserá disparado apenas na porta do remetente, e não nas outras portas.
Mensagens entre extensões
Além de enviar mensagens entre diferentes componentes na sua extensão, é possível usar a API Messaging para se comunicar com outras extensões. Isso permite expor uma API pública que outras extensões podem aproveitar.
A escuta de solicitações e conexões recebidas é semelhante ao caso interno, exceto que você usa os métodos runtime.onMessageExternal ou runtime.onConnectExternal. Confira um exemplo de cada um:
// 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.
});
});
Da mesma forma, enviar uma mensagem para outra extensão é semelhante a enviar uma dentro da sua extensão. A única diferença é que você precisa transmitir o ID da extensão com que quer se comunicar. Por exemplo:
// 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(...);
Como enviar mensagens de páginas da Web
Assim como o envio de mensagens entre extensões, seu app ou extensão pode receber e responder a mensagens de páginas da Web comuns. Para usar esse recurso, primeiro especifique no manifest.json com quais sites você quer se comunicar. Exemplo:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Isso vai expor 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 de host como "*", "*.com", "*.co.uk" e "*.appspot.com" são proibidos. Na página da Web, use as APIs runtime.sendMessage ou runtime.connect para enviar uma mensagem a um app ou extensão específica. Exemplo:
// 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);
});
No seu app ou extensão, você pode ouvir mensagens de páginas da Web usando as APIs runtime.onMessageExternal ou runtime.onConnectExternal, semelhante ao envio de mensagens entre extensões. Somente a página da Web pode iniciar uma conexão. Confira um exemplo:
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 e os apps 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
Scripts de conteúdo são menos confiáveis
Os scripts de conteúdo são menos confiáveis do que a página em segundo plano da extensão. Por exemplo, uma página da Web maliciosa pode comprometer o processo de renderização em que os scripts de conteúdo são executados. 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
Ao receber uma mensagem de um script de conteúdo ou outra extensão, seus scripts precisam ter cuidado para não serem vítimas de scripting em vários sites. Essa orientação se aplica a scripts executados na página em segundo plano da extensão e a scripts de conteúdo executados em outras origens da Web. Especificamente, evite usar APIs perigosas, como as abaixo:
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;
});
Em vez disso, prefira APIs mais seguras que não executam scripts:
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;
});
Exemplos
Você encontra exemplos simples de comunicação por mensagens no diretório examples/api/messaging. O exemplo de mensagens nativas mostra como um app do Chrome pode se comunicar com um app nativo. Para mais exemplos e ajuda para ver o código-fonte, consulte Exemplos.