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 que os executa, eles geralmente 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 notificar o service worker para exibir um ícone de ação para essa página.

Essa comunicação usa a transmissão de mensagens, o que permite que as extensões e os scripts de conteúdo ouçam as mensagens uns dos outros 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 uma mais complexa para conexões de longa duração que permitem o envio de várias mensagens. Para 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 a outra parte da extensão e, opcionalmente, receber uma resposta, chame runtime.sendMessage() ou tabs.sendMessage(). Esses métodos permitem enviar uma mensagem única e serializável 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 versões anteriores de extensões mais antigas, você pode transmitir um callback como o último argumento. Não é possível usar uma promessa e um callback na mesma chamada.

Para saber mais 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 tem a seguinte aparência:

content-script.js:

(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

Para enviar uma solicitação a um script de conteúdo, especifique a qual guia 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 eventos 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() era 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 detectando eventos onMessage, somente a primeira que chamar sendResponse() para um evento específico terá êxito ao 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. É possível nomear seu canal para distinguir entre 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ário. O script de conteúdo pode abrir um canal na página da extensão para 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 compartilhe o estado entre os 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 a seguir para abrir um canal a partir de um script de conteúdo e enviar e ouvir 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 as conexões de entrada 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 é semelhante a este:

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

Ciclo de vida da porta

As portas são projetadas como um método de comunicação bidirecional entre partes diferentes 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() 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 disparado uma vez para cada frame no processo de extensão.

Você pode querer descobrir quando uma conexão é encerrada, por exemplo, se está mantendo estados separados para cada porta aberta. Para fazer isso, detecte o evento runtime.Port.onDisconnect. Esse evento é disparado 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 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 chamada connect() resultar em várias portas no final do receptor e disconnect() for chamado em qualquer uma dessas portas, o evento onDisconnect será disparado apenas 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 expor uma API pública para outras extensões usarem.

Para detectar conexões e solicitações recebidas de outras extensões, use os métodos runtime.onMessageExternal ou runtime.onConnectExternal. Aqui está um exemplo de cada uma:

service-worker.js (link em inglês)

// 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 código 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 com quais sites você quer 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, padrões de nome do host como "*", "*.com", "*.co.uk" e "*.appspot.com" não são compatíveis. A partir do Chrome 107, você pode 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 o utilizam podem levar mais tempo.

Use as APIs runtime.sendMessage() ou runtime.connect() para enviar uma mensagem a uma extensão ou aplicativo 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 extensão, ouça mensagens de páginas da Web usando as APIs runtime.onMessageExternal ou runtime.onConnectExternal, como nas mensagens entre extensões. Veja um exemplo:

service-worker.js (link em inglês)

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

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

Estas são algumas considerações de segurança relacionadas a mensagens.

Os scripts de conteúdo são menos confiáveis

Os scripts de conteúdo são menos confiáveis que o service worker de 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 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

Proteja seus scripts contra scripting em vários sites. Ao receber dados de uma fonte não confiável, como entradas de usuários, outros sites por um script de conteúdo ou API, evite interpretá-los como HTML ou usá-los de forma a permitir a execução de códigos inesperados.

Métodos mais seguros

Use APIs que não executam scripts sempre que possível:

service-worker.js (link em inglês)

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 (link em inglês)

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});
Métodos não seguros

Evite usar os seguintes métodos que tornam 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 (link em inglês)

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});