Scripts de conteúdo são arquivos executados no contexto de páginas da Web. Usando o Modelo de Objeto de Documento (DOM) padrão, eles 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 as funcionalidades dos scripts de conteúdo
Os scripts de conteúdo podem acessar diretamente as seguintes APIs de extensão:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Os scripts de conteúdo não podem acessar outras APIs diretamente. No entanto, é possível acessar esses recursos indiretamente trocando mensagens com outras partes da extensão.
Também é possível acessar outros arquivos na extensão de um script de conteúdo usando
APIs como fetch()
. Para isso, declare-os como
recursos acessíveis pela Web. Isso também expõe os recursos a scripts próprios ou de terceiros em execução no mesmo site.
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 os scripts de conteúdo de outras 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 de forma estática, declarados dinamicamente ou injetados por programação.
Injetar com declarações estáticas
Use declarações estáticas de script de conteúdo em manifest.json para scripts que devem ser executados automaticamente em um conjunto conhecido de páginas.
Os scripts declarados de forma estática 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 o 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 inseridos na ordem em que aparecem nessa matriz, antes que qualquer DOM seja construído ou exibido para a página. |
js |
|
Opcional. A lista de arquivos JavaScript a serem injetados nas 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 cortadas 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. 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 é "falso". |
match_origin_as_fallback |
booleano |
Opcional. Se o script deve 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 Como injetar em frames relacionados.
|
world |
ExecutionWorld |
Opcional. O mundo JavaScript em que um script é executado. O padrão é ISOLATED . Consulte também
Trabalhar em mundos isolados.
|
Fazer injeção 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 não devem ser sempre injetados em hosts conhecidos.
Introduzidas no Chrome 96, as declarações dinâmicas são semelhantes às estáticas, mas o objeto de script de conteúdo é registrado no Chrome usando métodos no namespace chrome.scripting
em vez de manifest.json. A API Scripting também permite que os desenvolvedores de extensões
façam o seguinte:
- Registre scripts de conteúdo.
- Receba uma lista de scripts de conteúdo registrados.
- Atualize a lista de scripts de conteúdo registrados.
- Remova os scripts de conteúdo registrados.
Assim como as declarações estáticas, as 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 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, sua extensão precisa de permissões de host para
a página em que ela está tentando injetar scripts. As permissões de host podem ser concedidas solicitando-as como parte do manifesto da extensão ou usando temporariamente "activeTab"
.
A seguir, há 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ágina especificada, inclua os seguintes campos 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. 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 este glob. Isso tem como objetivo emular a palavra-chave @include
do Greasemonkey. |
exclude_globs |
matriz de strings | Opcional. Aplicado depois de matches para excluir URLs que correspondem a este
glob. Destinado a emular a palavra-chave @exclude
do Greasemonkey. |
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
einclude_globs
. - O URL também não corresponde a um padrão
exclude_matches
ouexclude_globs
. Como a propriedadematches
é obrigatória,exclude_matches
,include_globs
eexclude_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 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, ela 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 preferido 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 | 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 window.onload
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 da 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 acionado usando a propriedade document.readyState . |
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 |
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
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 devem 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"]
}
],
...
}
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 deve ser injetado em todos os frames que correspondem aos
requisitos de URL especificados ou apenas no frame superior de uma guia. 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" ],
}]);
Inserir em frames relacionados
As extensões podem querer executar scripts em frames relacionados a um frame correspondente, mas que não correspondem. Um cenário comum quando isso acontece é para frames com URLs 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
têm esquemas about:
, data:
, blob:
e filesystem:
. Nesses casos, o URL não vai corresponder ao padrão do script de conteúdo e, no caso de about:
e data:
, nem incluir o URL ou a origem principal 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 fazer a injeção nesses frames, as extensões podem especificar 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 analisa a origem do
iniciador do frame para determinar se ele corresponde, em vez de analisar
o URL do próprio frame. 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 até o frame de destino. Embora esse seja geralmente o pai ou o abridor direto, isso pode não acontecer, como no caso de um frame navegando em um iframe dentro de um iframe.
Como isso compara a origem do frame iniciador, ele pode estar em qualquer caminho dessa origem. Para deixar isso claro, 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 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 por meio dele, 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 sem 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 da 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, como 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 da 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 executados em mundos isolados têm a seguinte Política de Segurança de Conteúdo (CSP):
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 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 ao chamar fetch()
, filtre o conteúdo contra ataques de
scripts 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 padrões a seguir são perigosos e não são permitidos no Manifest V3:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
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:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);