Scripts de conteúdo

Os scripts de conteúdo são arquivos executados no contexto de páginas da Web. Com o Document Object Model (DOM) padrão, eles podem ler detalhes das páginas da Web que o navegador visita, fazer alterações nelas e transmitir informações para a extensão pai.

Entenda os recursos do script de conteúdo

Os scripts de conteúdo podem acessar arquivos de extensão depois de declará-los como recursos acessíveis pela Web. Eles podem acessar as seguintes APIs de extensão diretamente:

Os scripts de conteúdo não podem acessar outras APIs diretamente. Mas eles podem acessá-las indiretamente, trocando mensagens com outras partes da sua extensão.

Trabalhe em mundos isolados

Os scripts de conteúdo residem em um mundo isolado, permitindo que eles façam alterações no ambiente JavaScript sem entrar em conflito com os scripts de conteúdo da página ou de outras extensões.

Uma extensão pode ser executada em uma página da Web com código semelhante ao exemplo a seguir.

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

Essa extensão pode injetar o script de conteúdo a seguir usando uma das técnicas descritas na seção Injetar scripts.

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

Com essa mudança, os dois alertas vão aparecer em sequência quando o botão for clicado.

Injetar scripts

Os scripts de conteúdo podem ser declarados estaticamente, declarados dinamicamente ou injetados programaticamente.

Injetar com declarações estáticas

Use declarações de script de conteúdo estático no manifest.json para scripts que precisam ser executados automaticamente em um conjunto conhecido de páginas.

Os scripts declarados estaticamente são registrados no manifesto na chave "content_scripts". Eles podem incluir arquivos JavaScript, CSS ou ambos. Todos os scripts de conteúdo executados automaticamente precisam especificar padrões de correspondência.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

Nome Tipo Descrição
matches matriz de strings Obrigatório. Especifica em quais páginas este script de conteúdo será injetado. Consulte Padrões de correspondência para mais detalhes sobre a sintaxe dessas strings e Padrões de correspondência e globs para informações sobre como excluir URLs.
css matriz de strings Opcional. A lista de arquivos CSS a serem injetados nas páginas correspondentes. Eles são injetados na ordem em que aparecem nesta matriz, antes de qualquer DOM ser construído ou exibido para a página.
js matriz de strings Opcional. A lista de arquivos JavaScript a serem injetados nas páginas correspondentes. Os arquivos são injetados na ordem em que aparecem nesta matriz. Cada string dessa lista precisa conter um caminho relativo para um recurso no diretório raiz da extensão. As barras iniciais (`/`) são cortadas automaticamente.
run_at RunAt Opcional. Especifica quando o script precisa ser injetado na página. O valor padrão é document_idle.
match_about_blank boolean Opcional. Define se o script precisa injetar em um frame about:blank em que o frame pai ou aberto corresponde a um dos padrões declarados em matches. O padrão é "false".
match_origin_as_fallback boolean Opcional. Define se o script precisa injetar em frames que foram criados por uma origem correspondente, mas cujo URL ou origem pode não corresponder diretamente ao padrão. Isso inclui frames com esquemas diferentes, como about:, data:, blob: e filesystem:. Consulte também Como injetar frames relacionados.
world ExecutionWorld Opcional. O mundo em JavaScript onde um script será executado. O valor padrão é ISOLATED. Consulte também Trabalhar em mundos isolados.

Injetar com declarações dinâmicas

Os scripts de conteúdo dinâmico são úteis quando os padrões de correspondência deles não são conhecidos ou quando os scripts de conteúdo nem sempre precisam ser injetados em hosts conhecidos.

Introduzidas no Chrome 96, as declarações dinâmicas são semelhantes às declarações estáticas, mas o objeto do script de conteúdo é registrado no Chrome usando métodos no namespace chrome.scripting em vez de em manifest.json. A API Scripting também permite que os desenvolvedores de extensões:

Assim como as declarações estáticas, as declarações dinâmicas podem incluir arquivos JavaScript, CSS ou ambos.

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

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

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

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

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

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

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

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

Injetar programaticamente

Use a injeção programática para scripts de conteúdo que precisam ser executados em resposta a eventos ou em ocasiões específicas.

Para injetar um script de conteúdo programaticamente, sua extensão precisa de permissões de host para a página em que está tentando injetar scripts. As permissões de host podem ser concedidas solicitando-as como parte do manifesto da sua extensão ou usando temporariamente "activeTab".

Confira a seguir as versões diferentes de uma extensão baseada em activeTab.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

Os scripts de conteúdo podem ser injetados como arquivos.

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

Ou um corpo de função pode ser injetado e executado como um script de conteúdo.

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

A função injetada é uma cópia da função referenciada na chamada chrome.scripting.executeScript(), não a função original em si. Como resultado, o corpo da função precisa ser independente. As referências a variáveis fora da função farão com que o script de conteúdo gere uma ReferenceError.

Ao injetar como uma função, você também pode transmitir argumentos para a função.

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

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

Excluir correspondências e globs

Para personalizar a correspondência de página especificada, inclua os campos a seguir em um registro declarativo.

Nome Tipo Descrição
exclude_matches matriz de strings Opcional. Exclui páginas em que o script de conteúdo seria injetado de outra forma. Consulte Padrões de correspondência para detalhes sobre a sintaxe dessas strings.
include_globs matriz de strings Opcional. Aplicado após matches para incluir apenas os URLs que também correspondem a este glob. O objetivo disso é emular a palavra-chave Greasemonkey @include.
exclude_globs matriz de strings Opcional. Aplicado após matches para excluir URLs que correspondem a este glob. Destina-se a emular a palavra-chave Greasemonkey @exclude.

O script de conteúdo será injetado em uma página se as duas condições a seguir forem verdadeiras:

  • O URL corresponde a qualquer padrão matches e a qualquer padrão include_globs.
  • O URL também não corresponde a um padrão exclude_matches ou exclude_globs. Como a propriedade matches é obrigatória, exclude_matches, include_globs e exclude_globs só podem ser usados para limitar quais páginas serão afetadas.

A extensão a seguir injeta o script de conteúdo em https://www.nytimes.com/health, mas não em https://www.nytimes.com/business .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

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

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

As propriedades glob seguem uma sintaxe diferente e mais flexível do que os padrões de correspondência. As strings glob aceitáveis são URLs que podem conter asteriscos "caractere curinga" e pontos de interrogação. O asterisco (*) corresponde a qualquer string de qualquer tamanho, incluindo a string vazia, enquanto o ponto de interrogação (?) corresponde a qualquer caractere único.

Por exemplo, o glob https://???.example.com/foo/\* corresponde a qualquer um dos seguintes:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

No entanto, ele não corresponde ao seguinte:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Esta extensão injeta o script de conteúdo em https://www.nytimes.com/arts/index.html e https://www.nytimes.com/jobs/index.htm*, mas não em https://www.nytimes.com/sports/index.html:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Esta extensão injeta o script de conteúdo em https://history.nytimes.com e https://.nytimes.com/history, mas não em https://science.nytimes.com ou https://www.nytimes.com/science:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

É possível incluir apenas um, todos ou alguns deles para atingir o escopo correto.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Ambiente de execução

O campo run_at controla quando arquivos JavaScript são injetados na página da Web. O valor preferencial e padrão é "document_idle". Consulte o tipo RunAt para ver outros valores possíveis.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

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

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
Nome Tipo Descrição
document_idle string Preferida. Use "document_idle" sempre que possível.

O navegador escolhe um horário para injetar scripts entre "document_end" e imediatamente após o disparo do evento window.onload. O momento exato da injeção depende da complexidade do documento e do tempo que ele está demorando para carregar, e é otimizado para a velocidade de carregamento da página.

Os scripts de conteúdo em execução em "document_idle" não precisam detectar o evento window.onload, eles têm a garantia de serem executados depois que o DOM for concluído. Se um script realmente precisar ser executado após window.onload, a extensão poderá verificar se onload já foi disparado usando a propriedade document.readyState.
document_start string Os scripts são injetados depois de qualquer arquivo de css, mas antes de qualquer outro DOM ser construído ou de qualquer outro script ser executado.
document_end string Os scripts são injetados imediatamente após a conclusão do DOM, mas antes do carregamento de sub-recursos, como imagens e frames.

Especificar frames

O campo "all_frames" permite que a extensão especifique se os arquivos JavaScript e CSS precisam ser injetados em todos os frames que correspondem aos requisitos de URL especificados ou apenas no frame superior de uma guia.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

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

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);
Nome Tipo Descrição
all_frames boolean Opcional. O padrão é false, o que significa que apenas o frame superior é correspondido.

Se true for especificado, todos os frames serão injetados, mesmo que o frame não seja o primeiro frame na guia. Cada frame é verificado de forma independente para os requisitos de URL. Ele não será injetado em frames filhos se os requisitos de URL não forem atendidos.

As extensões podem executar scripts em frames relacionados a um frame correspondente, mas que não são correspondentes. Um cenário comum quando isso acontece é os frames com URLs que foram criados por um frame correspondente, mas com URLs que não correspondem aos padrões especificados do script.

Esse é o caso quando uma extensão quer injetar em frames com URLs que têm esquemas about:, data:, blob: e filesystem:. Nesses casos, o URL não corresponderá ao padrão do script de conteúdo. No caso de about: e data:, não inclua o URL pai ou a origem no URL, como em about:blank ou data:text/html,<html>Hello, World!</html>. No entanto, esses frames ainda podem ser associados ao frame de criação.

Para injetar esses frames, as extensões podem definir a propriedade "match_origin_as_fallback" em uma especificação de script de conteúdo no manifesto.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Quando especificado e definido como true, o Chrome analisará a origem do iniciador do frame para determinar se ele corresponde, não no URL do próprio frame. Ela também pode ser diferente da origin do frame de destino (por exemplo, data: URLs têm uma origem nula).

O iniciador é aquele que criou ou navegou pelo frame de destino. Geralmente, ele é o pai ou a abertura direto, mas pode não ser, como no caso de um frame navegando em um iframe dentro de um iframe.

Como isso compara a origin do frame do iniciador, o frame do iniciador pode estar em qualquer caminho a partir dessa origem. Para esclarecer essa implicação, o Chrome exige que todos os scripts de conteúdo especificados com "match_origin_as_fallback" definido como true também especifiquem um caminho *.

Quando "match_origin_as_fallback" e "match_about_blank" são especificados, "match_origin_as_fallback" tem prioridade.

Comunicação com a página de incorporação

Embora os ambientes de execução dos scripts de conteúdo e das páginas que os hospedam estejam isolados entre si, eles compartilham o acesso ao DOM da página. Se a página quiser se comunicar com o script de conteúdo ou com a extensão pelo script de conteúdo, será necessário usar o DOM compartilhado.

Confira um exemplo usando window.postMessage():

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

A página sem extensão, example.html, publica mensagens nela mesma. Essa mensagem é interceptada e inspecionada pelo script de conteúdo e, em seguida, postada no processo de extensão. Dessa forma, a página estabelece uma linha de comunicação para o processo de extensão. O inverso é possível por meios semelhantes.

Acessar arquivos de extensão

Para acessar um arquivo de extensão de um script de conteúdo, chame chrome.runtime.getURL() para receber o URL absoluto do recurso de extensão, conforme mostrado no exemplo a seguir (content.js):

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

Se você quiser usar fontes ou imagens em um arquivo CSS, use @@extension_id para criar um URL, conforme mostrado neste exemplo (content.css):

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

Todos os recursos precisam ser declarados como recursos acessíveis pela Web no arquivo manifest.json:

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

Proteja-se

Embora mundos isolados forneçam uma camada de proteção, o uso de scripts de conteúdo pode criar vulnerabilidades em uma extensão e na página da Web. Se o script de conteúdo receber conteúdo de um site separado, por exemplo, chamando fetch(), tenha cuidado para filtrar o conteúdo contra ataques de scripting em vários sites antes de injetá-lo. Comunique-se apenas por HTTPS para evitar ataques "man-in-the-middle".

Filtre por páginas da Web maliciosas. Por exemplo, os padrões a seguir são perigosos e não permitidos no Manifesto V3:

O que não fazer

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
O que não fazer

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

Em vez disso, prefira APIs mais seguras que não executam scripts:

O que fazer

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
O que fazer

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);