Transmissão de mensagens

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ão
  • tabs.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 chamada connect() resultar em várias portas na extremidade do receptor e disconnect() for chamada em qualquer uma dessas portas, o evento onDisconnect 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.

Métodos mais seguros

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;
});
Métodos inseguros

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