Scripts de conteúdo

Scripts de conteúdo são arquivos executados no contexto de páginas da Web. Ao usar o Modelo de Objeto de Documento (DOM, na sigla em inglês) padrão, elas podem ler detalhes das páginas da Web visitadas pelo navegador, fazer mudanças nelas e transmitir informações para a extensão principal.

Entender os recursos do script de conteúdo

Os scripts de conteúdo podem acessar as APIs do Chrome usadas pela extensão principal trocando mensagens com ela. Eles também podem acessar o URL do arquivo de uma extensão com chrome.runtime.getURL() e usar o resultado da mesma forma que outros URLs.

// Code for displaying EXTENSION_DIR/images/myimage.png:
var imgURL = chrome.runtime.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

Além disso, o script de conteúdo pode acessar diretamente as seguintes APIs do Chrome:

Os scripts de conteúdo não podem acessar outras APIs diretamente.

Trabalhar em mundos isolados

Os scripts de conteúdo vivem 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.

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

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

Essa extensão pode injetar o seguinte script de conteúdo.

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

Os dois alertas aparecem se o botão for pressionado.

Os mundos isolados não permitem que scripts de conteúdo, a extensão e a página da Web acessem variáveis ou funções criadas pelos outros. Isso também dá aos scripts de conteúdo a capacidade de ativar funcionalidades que não devem estar acessíveis à página da Web.

Injetar scripts

Os scripts de conteúdo podem ser injetados programaticamente ou declarativamente.

Injetar de forma programática

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

Para injetar um script de conteúdo programático, forneça a permissão activeTab no manifesto. Isso concede acesso seguro ao host do site ativo e acesso temporário à permissão de abas, permitindo que o script de conteúdo seja executado na aba ativa atual sem especificar permissões de origem cruzada.

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab"
  ],
  ...
}

Os scripts de conteúdo podem ser injetados como código.

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "changeColor"){
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="orange"'
      });
    }
  });

Ou um arquivo inteiro pode ser injetado.

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "runContentScript"){
      chrome.tabs.executeScript({
        file: 'contentScript.js'
      });
    }
  });

Injeção declarativa

Use a injeção declarativa para scripts de conteúdo que precisam ser executados automaticamente em páginas específicas.

Os scripts injetados de forma declarativa são registrados no manifesto no campo "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.

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["http://*.nytimes.com/*"],
     "css": ["myStyles.css"],
     "js": ["contentScript.js"]
   }
 ],
 ...
}
Nome Tipo Descrição
matches {: #matches } matriz de strings Obrigatório. Especifica em quais páginas o 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 {: #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 {: #js } matriz de strings Opcional. A lista de arquivos JavaScript a serem injetados nas páginas correspondentes. Eles são injetados na ordem em que aparecem nessa matriz.
match_about_blank {: #match_about_blank } booleano Opcional. Se o script deve 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 é false.

Excluir correspondências e globs

A correspondência de página especificada pode ser personalizada incluindo os seguintes campos no registro do manifesto.

Nome Tipo Descrição
exclude_matches {: #exclude_matches } matriz de strings Opcional. Exclui páginas em que o script de conteúdo seria injetado. Consulte Padrões de correspondência para mais detalhes sobre a sintaxe dessas strings.
include_globs {: #include_globs } matriz de strings Opcional. Aplicado após matches para incluir apenas os URLs que também correspondem a este glob. Destinado a emular a palavra-chave @include do Greasemonkey.
exclude_globs {: #exclude_globs } matriz de string Opcional. Aplicado depois de matches para excluir URLs que correspondem a esse glob. Destinado a emular a palavra-chave @excludedo Greasemonkey.

O script de conteúdo será injetado em uma página se o URL dela corresponder a um padrão matches e a um padrão include_globs, desde que o URL também não corresponda 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 injetaria o script de conteúdo em http://www.nytimes.com/ health, mas não em http://www.nytimes.com/ business .

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*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 tamanho, incluindo a string vazia, enquanto o ponto de interrogação ? corresponde a qualquer caractere único.

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

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

No entanto, ela não corresponde ao seguinte:

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

Essa extensão injetaria o script de conteúdo em http:/www.nytimes.com/ arts /index.html e http://www.nytimes.com/ jobs /index.html, mas não em http://www.nytimes.com/ sports /index.html.

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

Essa extensão injetaria o script de conteúdo em http:// history .nytimes.com e http://.nytimes.com/ history, mas não em http:// science .nytimes.com ou http://www.nytimes.com/ science .

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

Um, todos ou alguns deles podem ser incluídos para alcançar o escopo correto.

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.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 campo preferido e padrão é "document_idle", mas também pode ser especificado como "document_start" ou "document_end", se necessário.

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}
Nome Tipo Descrição
document_idle {: #document_idle } string Boa opção. Use "document_idle" sempre que possível.

O navegador escolhe um momento para injetar scripts entre "document_end" e imediatamente após o evento windowonload ser disparado. O momento exato da injeção depende da complexidade do documento e do tempo que ele leva para carregar, sendo otimizado para a velocidade de carregamento de página.

Os scripts de conteúdo executados em "document_idle" não precisam detectar o evento window.onload. Eles são executados depois que o DOM é concluído. Se um script precisar ser executado depois de window.onload, a extensão poderá verificar se onload já foi disparado usando a propriedade document.readyState.
document_start {: #document_start } string Os scripts são injetados depois de todos os arquivos de css, mas antes que qualquer outro DOM seja construído ou qualquer outro script seja executado.
document_end {: #document_end } string Os scripts são injetados imediatamente após a conclusão do DOM, mas antes que sub-recursos como imagens e frames sejam carregados.

Especificar frames

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

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}
Nome Tipo Descrição
all_frames {: #all_frames } booleano Opcional. O padrão é false, o que significa que apenas o frame superior é correspondido.

Se true for especificado, a injeção será feita em todos os frames, mesmo que o frame não seja o mais alto na guia. Cada frame é verificado de forma independente quanto aos requisitos para URLs. Ele não será injetado em frames filhos se os requisitos para URLs não forem atendidos.

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 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 usando o script de conteúdo, ela precisará fazer isso pelo DOM compartilhado.

Um exemplo pode ser feito usando window.postMessage:

var port = chrome.runtime.connect();

window.addEventListener("message", function(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);
document.getElementById("theButton").addEventListener("click",
    function() {
  window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);

A página sem extensão, example.html, envia 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.

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 ao fazer um XMLHttpRequest, filtre o conteúdo contra ataques de scripting entre sites antes de injetá-lo. Comunique-se apenas por HTTPS para evitar ataques "man-in-the-middle".

Não se esqueça de filtrar páginas da Web maliciosas. Por exemplo, os seguintes padrões são perigosos:

var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var 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:

var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
  animate(elmt_id);
}, 200);