Scripts de conteúdo

Scripts de conteúdo são arquivos executados no contexto de páginas da Web. Com a função Documento Modelo de objeto (DOM), eles podem ler detalhes das páginas da Web que o navegador visita, fazer alterações feitas neles e passar informações para a extensão principal.

Compreender as capacidades 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. Mas eles podem acessá-las indiretamente, trocando mensagens com outras partes da sua extensão.

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

Trabalhe em mundos isolados

Os scripts de conteúdo vivem em um mundo isolado, permitindo que um script de conteúdo faça alterações em seu JavaScript sem conflito com a página ou outras extensões scripts de conteúdo.

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 de forma dinâmica ou injetada de forma programática.

Injetar com declarações estáticas

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

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 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 nas páginas correspondentes. São injetados na ordem em que aparecem nesta matriz, antes de qualquer DOM ser construído ou exibido da página.
js matriz de strings Opcional. A lista de arquivos JavaScript a serem injetados em páginas correspondentes. Arquivos são injetados na ordem em que aparecem nesta matriz. Cada string nessa lista deve conter um caminho relativo para um recurso no diretório raiz da extensão. As barras iniciais (`/`) são cortado automaticamente.
run_at RunAt Opcional. Especifica quando o script deve ser injetado na página. O valor padrão é document_idle.
match_about_blank booleano Opcional. Define se o script precisa injetar 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 é "false".
match_origin_as_fallback booleano Opcional. Se o script deve injetar em frames que foram criadas por uma origem correspondente, mas cujo URL ou origem não pode diretamente correspondem ao padrão. Isso inclui frames com esquemas diferentes, como about:, data:, blob: e filesystem:. Consulte também Injeção de frames relacionados.
world ExecutionWorld Opcional. O mundo JavaScript no qual um script pode ser executado. O padrão é ISOLATED. Consulte também Trabalhe em mundos isolados.

Injetar com declarações dinâmicas

Scripts de conteúdo dinâmico são úteis quando os padrões de correspondência de scripts de conteúdo são não é muito conhecido ou quando os scripts de conteúdo não devem sempre ser injetados em hosts conhecidos.

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

Assim como as declarações estáticas, as declarações dinâmicas podem incluir arquivos JavaScript, 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 programaticamente

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

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

Confira a seguir versões diferentes de uma extensão 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"]
  });
});

Como alternativa, o corpo de uma 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 no chrome.scripting.executeScript(), e não a função original em si. Como resultado, o valor o corpo deve ser autônomo; referências a variáveis fora da função fará com que o conteúdo para gerar uma ReferenceError.

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

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ágina especificada, inclua os seguintes campos em uma declaração registro.

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 entrar. Consulte Padrões de correspondência para detalhes sobre a sintaxe de essas strings.
include_globs matriz de strings Opcional. Aplicado após matches para incluir apenas os URLs que também correspondem a esse glob. O objetivo é emular o @include Palavra-chave "Greasemonkey".
exclude_globs matriz de strings Opcional. Aplicado após matches para excluir URLs correspondentes massa. que serve para emular o @exclude. Palavra-chave "Greasemonkey".

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

  • O URL corresponde a qualquer padrão matches e 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ó pode ser usado para limitar as páginas que 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 dos padrões de correspondência. Globo aceitável strings são URLs que podem conter "caractere curinga" asteriscos 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 itens:

  • 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

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

Uma, todas ou algumas delas podem ser incluídas 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"]
    }
  ],
  ...
}

Tempo de execução

O campo run_at controla quando arquivos JavaScript são injetados na página da Web. As APIs o valor padrão é "document_idle". Veja o tipo RunAt para outras possíveis valores.

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 momento para injetar scripts entre "document_end" e imediatamente após a window.onload é acionado. O momento exato da injeção depende da complexidade do documento e da tempo que leva para carregar e é otimizada para a velocidade de carregamento da página.

Scripts de conteúdo em execução em "document_idle" não precisam ouvir o evento window.onload, eles vão ser executados após a conclusão do DOM. Se um o script precisa ser executado depois de window.onload, a extensão poderá verificar se onload já disparou usando document.readyState .
document_start string Os scripts são injetados após qualquer arquivo de css, mas antes de qualquer outro DOM construído ou qualquer outro script é executado.
document_end string Os scripts são injetados imediatamente após a conclusão do DOM, mas antes dos sub-recursos, como imagens e frames foram carregados.

Especificar frames

O campo "all_frames" permite que a extensão especifique se os arquivos JavaScript e CSS devem ser injetada em todos os frames que correspondem aos requisitos de URL especificados ou somente no frame superior de um .

manifest.json

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

service-worker.js

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

Se true for especificado, todos os frames serão injetados, mesmo se o não é o frame superior da guia. O URL de cada frame é verificado independentemente e cumprimento de requisitos regulatórios. 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 uma mas não correspondem por si só. Um cenário comum quando esse é o caso é para frames com URLs que foram criados por um frame correspondente, mas cujos URLs não correspondem aos padrões especificados do script.

Isso acontece quando uma extensão quer injetar em frames com URLs que tem esquemas about:, data:, blob: e filesystem:. Nesses casos, O URL não corresponderá ao padrão do script de conteúdo (e, no caso de about: e data:, nem mesmo o URL pai ou a origem são incluídos no URL nenhuma, como about:blank ou data:text/html,<html>Hello, World!</html>). No entanto, esses frames ainda podem ser associados ao frame criado.

Para injetar nesses frames, as extensões podem especificar o "match_origin_as_fallback" em uma especificação de script de conteúdo na manifesto do aplicativo.

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 ele corresponde, e não o URL do próprio frame. Ela também pode ser diferente da a origin do frame de destino (por exemplo, data: URL tem uma origem nula).

O iniciador do frame é o frame que criou ou navegou até o destino frame. Embora ele seja comumente o pai direto ou quem abre, pode não ser (como em o caso de um frame navegar em um iframe dentro de um iframe).

Como isso compara a origin do frame iniciador, o frame iniciador poderia estar em qualquer caminho dessa origem. Para esclarecer essa implicação, o Chrome requer scripts de conteúdo especificados com "match_origin_as_fallback" Defina como true para também especificar 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 dos scripts de conteúdo e as páginas que os hospedam estejam isolados entre si, elas 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, ele deverá fazer isso pelo DOM compartilhado.

Um exemplo pode ser alcançado 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. Esta mensagem foi interceptada e inspecionado pelo script de conteúdo e postado no processo de extensão. Dessa forma, a página estabelece uma linha de comunicação com o processo de extensão. O contrário é possível de meios semelhantes.

Acessar arquivos de extensão

Para acessar um arquivo de extensão a partir de um script de conteúdo, você pode chamar chrome.runtime.getURL() para encontrar o URL absoluto do seu 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 construir 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/*" ]
   }
 ],
 ...
}

Proteja-se

Embora os 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, como ao chamar fetch(), tenha cuidado para filtrar o conteúdo de acordo scripting em vários sites antes de injetá-lo. Comunique-se apenas por HTTPS evitar ataques &quot;man-in-the-middle&quot;.

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