Transmissão de mensagens

Como os scripts de conteúdo são executados no contexto de uma página da Web e não na extensão, eles geralmente precisam de alguma maneira 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 exibir um ícone de ação de página para ela.

A comunicação entre as extensões e os scripts de conteúdo delas funciona com a transmissão de mensagens. Qualquer um dos lados pode ouvir mensagens enviadas da outra ponta 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 código dela, abordado na seção mensagens entre extensões.

Solicitações únicas simples

Se você só precisa enviar uma única mensagem para outra parte da extensão e, opcionalmente, receber uma resposta, use runtime.sendMessage ou tabs.sendMessage simplificados . Isso permite enviar uma mensagem única e 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.

O envio de uma solicitação de um script de conteúdo é assim:

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

O envio de uma solicitação da extensão para um script de conteúdo é muito semelhante, exceto pela necessidade de especificar para qual guia enviá-la. Confira neste exemplo como enviar 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 é o mesmo que aparece 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.

Observação: se várias páginas estiverem detectando eventos onMessage, somente a primeira a chamar sendResponse() para um evento específico terá êxito ao enviar a resposta. Todas as outras respostas a esse evento serão ignoradas.
Observação:o callback sendResponse só é válido se usado de maneira síncrona ou se o manipulador de eventos retornar true para indicar que ele responderá de maneira assíncrona. O callback da função sendMessage será invocado automaticamente se nenhum gerenciador retornar "true" ou se o callback sendResponse for coletado da lixeira.

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, é possível 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 automática de preenchimento de formulário. O script de conteúdo pode abrir um canal na 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 os dados do formulário a serem preenchidos. A conexão compartilhada permite que a extensão mantenha o estado compartilhado vinculando as várias mensagens provenientes 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.

Veja como abrir um canal a partir de um script de conteúdo, além de 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"});
});

O envio de uma solicitação da extensão para um script de conteúdo é muito semelhante, exceto pela necessidade de especificar a qual guia se conectar. Basta substituir a chamada de conexão no exemplo acima por tabs.connect.

Para lidar com conexões de entrada, você precisa configurar um listener de eventos runtime.onConnect. É semelhante a isso em um script de conteúdo ou em uma página de extensão. Quando outra parte da extensão chamar "connect()", esse evento será disparado com o objeto runtime.Port, que pode ser usado para enviar e receber mensagens pela conexão. Veja como é a resposta a conexões de entrada:

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) é considerado a menor parte. Ao chamar tabs.connect, runtime.connect ou runtime.connectNative, uma Porta é criada. Essa porta pode ser usada imediatamente para enviar mensagens para a 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).

É possível descobrir quando uma conexão é encerrada, por exemplo, se você está mantendo o estado separado para cada porta aberta. Para isso, é possível detectar o evento runtime.Port.onDisconnect. Esse evento é acionado quando não há portas válidas no 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 é 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 chamada connect resultar em várias portas no receptor e disconnect() for chamado em qualquer uma dessas portas, o evento onDisconnect será acionado somente na porta do remetente, e 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 expor uma API pública que pode ser aproveitada por outras extensões.

A detecção de solicitações e conexões recebidas é semelhante ao caso interno, exceto pelo uso dos métodos runtime.onMessageExternal ou runtime.onConnectExternal. Veja um exemplo de cada uma:

// 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.
  });
});

Do mesmo modo, enviar uma mensagem para outra extensão é semelhante a enviar uma mensagem dentro da sua extensão. A única diferença é que você precisa transmitir o ID da extensão com a qual deseja se comunicar. 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

Semelhante às mensagens entre extensões, seu app ou extensão pode receber e responder a mensagens de páginas da Web normais. Para usar esse recurso, especifique no manifesto.json com quais sites você quer se comunicar. Exemplo:

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

Dessa forma, a API de mensagens será exibida em todas as páginas que corresponderem 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" são proibidos. Na página da Web, use as APIs runtime.sendMessage ou runtime.connect para enviar uma mensagem a um aplicativo 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 app ou extensão, é possível detectar mensagens de páginas da Web usando as APIs runtime.onMessageExternal ou runtime.onConnectExternal, semelhante às 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);
  });

Mensagens nativas

Extensões e 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

Os 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 do renderizador 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 certifique-se de validar e limpar todas as entradas. Suponha que os dados enviados ao script de conteúdo possam 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 de outra extensão, seus scripts precisam ter cuidado para não serem vítimas de scripts entre sites. Esse conselho se aplica a scripts executados dentro da página de segundo plano da extensão, bem como a scripts de conteúdo executados em outras origens da Web. Especificamente, evite o uso de APIs perigosas como as seguintes:

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

Veja exemplos simples de comunicação por mensagens no diretório examples/api/messaging. O exemplo de mensagem nativa demonstra como um app do Chrome pode se comunicar com um app nativo. Para acessar mais exemplos e ajuda para visualizar o código-fonte, consulte Exemplos.