Scripts de conteúdo

Os scripts de conteúdo são arquivos executados no contexto de páginas da Web. Usando o Modelo de Objeto de Documento (DOM, na sigla em inglês) padrão, eles podem ler detalhes das páginas da Web que o navegador visita, fazer mudanças nelas e transmitir informações para a extensão mãe.

Entender os recursos do script de conteúdo

Os scripts de conteúdo podem acessar as seguintes APIs de extensão diretamente:

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

Você também pode acessar outros arquivos na extensão de um script de conteúdo usando APIs como fetch(). Para fazer isso, declare-os como recursos acessíveis pela Web. Isso também expõe os recursos a qualquer script próprio ou de terceiros em execução no mesmo site.

Trabalhar em mundos isolados

Os scripts de conteúdo ficam em um mundo isolado, permitindo que um script de conteúdo faça mudanças no ambiente JavaScript sem entrar em conflito com a página ou outros scripts de conteúdo de extensões.

Uma extensão pode ser executada em uma página da Web com um 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 seguinte script de conteúdo 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 aparecem em sequência quando o botão é clicado.

Injetar scripts

Os scripts de conteúdo podem ser declarados estaticamente, declarados dinamicamente, ou injetados de forma programática.

Injetar com declarações estáticas

Use declarações estáticas de script de conteúdo 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, arquivos CSS ou ambos. Todos os scripts de conteúdo de execução automática 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 esse script de conteúdo será injetado. Consulte Padrões de correspondência para 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 em páginas correspondentes. Eles são injetados na ordem em que aparecem nessa matriz, antes que qualquer DOM seja construído ou exibido para a página.
js matriz de strings Opcional. A lista de arquivos JavaScript a serem injetados em páginas correspondentes. Os arquivos são injetados na ordem em que aparecem nessa matriz. Cada string nessa lista precisa conter um caminho relativo para um recurso no diretório raiz da extensão. As barras iniciais (`/`) são automaticamente cortadas.
run_at RunAt Opcional. Especifica quando o script precisa ser injetado na página. O valor padrão é document_idle.
match_about_blank booleano Opcional. Indica se o script precisa ser injetado em um frame about:blank em que o frame pai ou de abertura corresponde a um dos padrões declarados em matches. O padrão é "falso".
match_origin_as_fallback booleano Opcional. Indica se o script precisa ser injetado em frames 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 Injetar em frames relacionados.
world ExecutionWorld Opcional. O mundo JavaScript para um script ser executado. O valor padrão é ISOLATED. Consulte também Trabalhar em mundos isolados.

Em um determinado estágio do ciclo de vida do documento, os scripts de conteúdo declarados estaticamente no manifesto são os primeiros a serem injetados, antes dos scripts de conteúdo registrados de qualquer outra forma. Eles são injetados na ordem em que são especificados no manifesto.

Injetar com declarações dinâmicas

Os scripts de conteúdo dinâmico são úteis quando os padrões de correspondência para scripts de conteúdo não são bem 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 de script de conteúdo é registrado no Chrome usando métodos no namespace chrome.scripting, em vez de no 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, arquivos CSS ou ambos.

service-worker.js

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

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

service-worker.js

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

service-worker.js

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

Injetar de forma programática

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 de forma programática, a 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 extensão ou temporariamente usando "activeTab".

Confira a seguir diferentes versões 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. Como resultado, o corpo da função precisa ser independente. Referências a variáveis fora da função farão com que o script de conteúdo gere um ReferenceError.

Ao injetar como uma função, também é possível transmitir argumentos para ela.

service-worker.js

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áginas especificada, inclua os seguintes campos em um registro declarativo.

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

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

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 e pontos de interrogação "curinga". O asterisco (*) corresponde a qualquer string de qualquer comprimento, 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, não corresponde ao seguinte:

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

Essa 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"]
    }
  ],
  ...
}

Essa 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"]
    }
  ],
  ...
}

Um, todos ou alguns deles podem ser incluídos para alcançar 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"]
    }
  ],
  ...
}

Tempo de execução

O campo run_at controla quando os arquivos JavaScript são injetados na página da Web. O valor preferencial e padrão é "document_idle". Consulte o tipo RunAt para 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

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

O navegador escolhe um horário para injetar scripts entre "document_end" e imediatamente após o window.onload evento ser acionado. O momento exato da injeção depende da complexidade do documento e do tempo necessário 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 window.onload evento. Eles têm a garantia de serem executados após a conclusão do DOM. Se um script precisar ser executado após window.onload, a extensão poderá verificar se onload já foi acionado usando a document.readyState propriedade.
document_start string Os scripts são injetados após qualquer arquivo de css, mas antes que qualquer outro DOM seja construído ou qualquer outro script seja executado.
document_end string Os scripts são injetados imediatamente após a conclusão do DOM, mas antes que os sub-recursos, como imagens e frames, sejam carregados.

Especificar frames

Para scripts de conteúdo declarativos especificados no manifesto, 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 para URLs especificados ou apenas no frame mais alto em uma guia:

manifest.json

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

Ao registrar scripts de conteúdo de forma programática usando chrome.scripting.registerContentScripts(...), o parâmetro allFrames pode ser usado para especificar se o script de conteúdo precisa ser injetado em todos os frames que correspondem aos requisitos para URLs especificados ou apenas no frame mais alto em uma guia. Isso só pode ser usado com tabId e não pode ser usado se frameIds ou documentIds forem especificados:

service-worker.js

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

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

É 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 corresponde ao padrão do script de conteúdo e, no caso de about: e data:, nem mesmo inclui o URL ou a origem pai no URL em absoluto, 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 nesses frames, as extensões podem especificar a "match_origin_as_fallback" propriedade 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 analisa a origem do iniciador do frame para determinar se o frame corresponde, em vez do URL do frame. Observe que isso também pode ser diferente da origem do frame de destino (por exemplo, os URLs data: têm uma origem nula).

O iniciador do frame é o frame que criou ou navegou no frame de destino. Embora seja comum que seja o pai ou o abridor direto, ele pode não ser (como no caso de um frame que navega em um iframe dentro de um iframe).

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

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 de scripts de conteúdo e as páginas que os hospedam sejam isolados uns dos outros, 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, ela precisará fazer isso pelo DOM compartilhado.

Um exemplo pode ser feito 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 não relacionada à extensão, example.html, posta mensagens para si 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 com 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")

Para usar fontes ou imagens em um arquivo CSS, use @@extension_id para criar um URL, conforme mostrado no exemplo a seguir (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/*" ]
   }
 ],
 ...
}

Política de Segurança de Conteúdo

Os scripts de conteúdo em execução em mundos isolados têm a seguinte Política de Segurança de Conteúdo (CSP, na sigla em inglês):

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Semelhante às restrições aplicadas a outros contextos de extensão, isso impede o uso de eval() e o carregamento de scripts externos.

Para extensões descompactadas, a CSP também inclui o localhost:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Quando um script de conteúdo é injetado no mundo principal, a CSP da página é aplicada.

Proteja-se

Embora os mundos isolados ofereç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, como chamando fetch(), filtre o conteúdo contra ataques de scripting em vários sites antes de injetá-lo. Comunique-se apenas por HTTPS para evitar "man-in-the-middle" ataques.

Filtre páginas da Web maliciosas. Por exemplo, os padrões a seguir são perigosos e não são permitidos no Manifest 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);