Gli script di contenuti sono file che vengono eseguiti nel contesto delle pagine web. Utilizzando il modello Document Object Model (DOM) standard, sono in grado di leggere i dettagli delle pagine web visitate dal browser, apportare modifiche e trasmettere informazioni all'estensione principale.
Comprendere le funzionalità degli script di contenuti
Gli script di contenuti possono accedere direttamente alle seguenti API delle estensioni:
domi18nstorageruntime.connect()runtime.getManifest()runtime.getURL()runtime.idruntime.onConnectruntime.onMessageruntime.sendMessage()
Gli script di contenuti non possono accedere direttamente ad altre API. Tuttavia, possono accedervi indirettamente scambiando messaggi con altre parti dell'estensione.
Puoi anche accedere ad altri file dell'estensione da uno script di contenuti utilizzando API come fetch(). Per farlo, devi dichiararli come
risorse accessibili dal web. Tieni presente che in questo modo le risorse vengono esposte anche a tutti gli script proprietari o di terze parti in esecuzione sullo stesso sito.
Lavorare in mondi isolati
Gli script di contenuti risiedono in un mondo isolato, il che consente a uno script di contenuti di apportare modifiche al proprio ambiente JavaScript senza entrare in conflitto con la pagina o con gli script di contenuti di altre estensioni.
Un'estensione può essere eseguita in una pagina web con codice simile all'esempio seguente.
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>
Questa estensione potrebbe inserire il seguente script di contenuti utilizzando una delle tecniche descritte nella sezione Inserire script.
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Con questa modifica, entrambi gli avvisi vengono visualizzati in sequenza quando si fa clic sul pulsante.
Inserire script
Gli script di contenuti possono essere dichiarati staticamente, dichiarati dinamicamente, o inseriti a livello di programmazione.
Inserire con dichiarazioni statiche
Utilizza le dichiarazioni statiche degli script di contenuti in manifest.json per gli script che devono essere eseguiti automaticamente su un insieme di pagine ben noto.
Gli script dichiarati staticamente vengono registrati nel manifest sotto la chiave "content_scripts".
Possono includere file JavaScript, file CSS o entrambi. Tutti gli script di contenuti a esecuzione automatica devono specificare
i pattern di corrispondenza.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
| Nome | Tipo | Descrizione |
|---|---|---|
matches |
array di stringhe | Obbligatorio. Specifica le pagine in cui verrà inserito questo script di contenuti. Per i dettagli sulla sintassi di queste stringhe, consulta Pattern di corrispondenza e per informazioni su come escludere gli URL, consulta Pattern di corrispondenza e glob. |
css |
array di stringhe | Facoltativo. L'elenco dei file CSS da inserire nelle pagine corrispondenti. Questi vengono inseriti nell'ordine in cui appaiono in questo array, prima che venga costruito o visualizzato per la pagina. |
js |
|
Facoltativo. L'elenco dei file JavaScript da inserire nelle pagine corrispondenti. I file vengono inseriti nell'ordine in cui appaiono in questo array. Ogni stringa di questo elenco deve contenere un percorso relativo a una risorsa nella directory principale dell'estensione. Le barre iniziali (`/`) vengono troncate automaticamente. |
run_at |
RunAt | Facoltativo. Specifica quando lo script deve essere inserito nella pagina. Il valore predefinito è
document_idle. |
match_about_blank |
booleano | Facoltativo. Indica se lo script deve essere inserito in un frame about:blank dove il frame principale o di apertura corrisponde a uno dei pattern dichiarati in matches. Il valore predefinito è false. |
match_origin_as_fallback |
booleano |
Facoltativo. Indica se lo script deve essere inserito nei frame creati da un'origine corrispondente, ma il cui URL o origine potrebbe non corrispondere direttamente al pattern. Sono inclusi i frame con schemi diversi, come
about:, data:, blob: e
filesystem:. Vedi anche
Inserire nei frame correlati.
|
world |
ExecutionWorld |
Facoltativo. Il mondo JavaScript in cui eseguire uno script. Il valore predefinito è ISOLATED. Vedi anche
Lavorare in mondi isolati.
|
In una determinata fase del ciclo di vita del documento, gli script di contenuti dichiarati staticamente nel manifest sono i primi a essere inseriti, prima degli script di contenuti registrati in qualsiasi altro modo. Vengono inseriti nell'ordine in cui sono specificati nel manifest.
Inserire con dichiarazioni dinamiche
Gli script di contenuti dinamici sono utili quando i pattern di corrispondenza per gli script di contenuti non sono ben noti o quando gli script di contenuti non devono essere sempre inseriti negli host noti.
Introdotte in Chrome 96, le dichiarazioni dinamiche sono simili alle dichiarazioni
statiche, ma l'oggetto script di contenuti viene registrato in Chrome utilizzando i
metodi nello spazio dei nomi chrome.scripting anziché in
manifest.json. L'API Scripting consente inoltre agli sviluppatori di estensioni di:
- Registrare gli script di contenuti.
- Ottieni un elenco di script di contenuti registrati.
- Aggiornare l'elenco degli script di contenuti registrati.
- Rimuovere gli script di contenuti registrati.
Come le dichiarazioni statiche, le dichiarazioni dinamiche possono includere file JavaScript, file CSS o entrambi.
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"));
Inserire a livello di programmazione
Utilizza l'inserimento a livello di programmazione per gli script di contenuti che devono essere eseguiti in risposta a eventi o in occasioni specifiche.
Per inserire uno script di contenuti a livello di programmazione, l'estensione deve disporre delle autorizzazioni host per
la pagina in cui sta tentando di inserire gli script. Le autorizzazioni host possono essere concesse richiedendole come parte del manifest dell'estensione o temporaneamente utilizzando "activeTab".
Di seguito sono riportate le diverse versioni di un'estensione basata su activeTab.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Gli script di contenuti possono essere inseriti come file.
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"]
});
});
In alternativa, è possibile inserire ed eseguire il corpo di una funzione come script di contenuti.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Tieni presente che la funzione inserita è una copia della funzione a cui viene fatto riferimento nella chiamata chrome.scripting.executeScript(), non la funzione originale stessa. Di conseguenza, il corpo della funzione deve essere autonomo; i riferimenti a variabili esterne alla funzione faranno sì che lo script di contenuti generi un ReferenceError.
Quando inserisci una funzione, puoi anche passare argomenti alla funzione.
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" ],
});
});
Escludere corrispondenze e glob
Per personalizzare la corrispondenza delle pagine specificate, includi i seguenti campi in una registrazione dichiarativa.
| Nome | Tipo | Descrizione |
|---|---|---|
exclude_matches |
array di stringhe | Facoltativo. Esclude le pagine in cui altrimenti verrebbe inserito questo script di contenuti. Per i dettagli sulla sintassi di queste stringhe, consulta Pattern di corrispondenza. |
include_globs |
array di stringhe | Facoltativo. Viene applicato dopo matches per includere solo gli URL che corrispondono anche a questo glob. Lo scopo è emulare la @include
Greasemonkey keyword. |
exclude_globs |
array di stringhe | Facoltativo. Viene applicato dopo matches per escludere gli URL che corrispondono a questo
glob. Lo scopo è emulare la @exclude
Greasemonkey keyword. |
Lo script di contenuti verrà inserito in una pagina se sono vere entrambe le seguenti condizioni:
- Il suo URL corrisponde a qualsiasi pattern
matchese a qualsiasi patterninclude_globs. - L'URL non corrisponde anche a un pattern
exclude_matchesoexclude_globs. Poiché la proprietàmatchesè obbligatoria,exclude_matches,include_globs, eexclude_globspossono essere utilizzate solo per limitare le pagine interessate.
La seguente estensione inserisce lo script di contenuti in https://www.nytimes.com/health, ma non in 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" ],
}]);
Le proprietà glob seguono una sintassi diversa e più flessibile rispetto ai pattern di corrispondenza. Le stringhe glob accettabili sono URL che possono contenere asterischi e punti interrogativi "con caratteri jolly". L'asterisco (*) corrisponde a qualsiasi stringa di qualsiasi lunghezza, inclusa la stringa vuota, mentre il punto interrogativo (?) corrisponde a qualsiasi singolo carattere.
Ad esempio, il glob https://???.example.com/foo/\* corrisponde a uno dei seguenti:
https://www.example.com/foo/barhttps://the.example.com/foo/
Tuttavia, non corrisponde a quanto segue:
https://my.example.com/foo/barhttps://example.com/foo/https://www.example.com/foo
Questa estensione inserisce lo script di contenuti in https://www.nytimes.com/arts/index.html e https://www.nytimes.com/jobs/index.htm*, ma non in 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"]
}
],
...
}
Questa estensione inserisce lo script di contenuti in https://history.nytimes.com e https://.nytimes.com/history, ma non in https://science.nytimes.com o https://www.nytimes.com/science:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
È possibile includere uno, tutti o alcuni di questi elementi per ottenere l'ambito corretto.
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"]
}
],
...
}
Durata esecuzione
Il campo run_at controlla quando i file JavaScript vengono inseriti nella pagina web. Il valore preferito e
predefinito è "document_idle". Per altri valori possibili, consulta il tipo RunAt.
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 | Descrizione |
|---|---|---|
document_idle |
stringa | Preferito. Utilizza "document_idle" quando possibile.Il browser sceglie un momento per inserire gli script tra "document_end" e immediatamente dopo
l'attivazione dell'evento window.onload. Il momento esatto dell'inserimento dipende dalla complessità del documento e da quanto tempo impiega a caricarsi ed è ottimizzato per la velocità di caricamento pagina.Gli script di contenuti in esecuzione in "document_idle" non devono rimanere in attesa dell'evento window.onload, perché è garantito che vengano eseguiti dopo il completamento del DOM. Se uno
script deve essere eseguito dopo window.onload, l'estensione può verificare se
onload è già stato attivato utilizzando la document.readyState
proprietà. |
document_start |
stringa | Gli script vengono inseriti dopo tutti i file di css, ma prima che venga costruito qualsiasi altro DOM o che venga eseguito qualsiasi altro script. |
document_end |
stringa | Gli script vengono inseriti immediatamente dopo il completamento del DOM, ma prima che vengano caricate le sottorisorse come immagini e frame. |
Specificare i frame
Per gli script di contenuti dichiarativi specificati nel manifest, il "all_frames" campo consente all'estensione di specificare se i file JavaScript e CSS devono essere inseriti in tutti i frame che soddisfano i requisiti per gli URL specificati o solo nel frame principale di una
scheda:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Quando registri gli script di contenuti a livello di programmazione utilizzando chrome.scripting.registerContentScripts(...), puoi utilizzare il parametro allFrames per specificare se lo script di contenuti deve essere inserito in tutti i frame che soddisfano i requisiti per gli URL specificati o solo nel frame principale di una scheda. Può essere utilizzato solo con tabId e non può essere utilizzato se vengono specificati frameIds o documentIds:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Inserire nei frame correlati
Le estensioni potrebbero voler eseguire script nei frame correlati a un frame corrispondente, ma che non corrispondono. Un caso comune in cui ciò si verifica è quello dei frame con URL creati da un frame corrispondente, ma i cui URL non corrispondono ai pattern specificati dello script.
Questo è il caso in cui un'estensione vuole inserire frame con URL che hanno schemi about:, data:, blob: e filesystem:. In questi casi, l'
URL non corrisponderà al pattern dello script di contenuti (e, nel caso di about: e
data:, non include nemmeno l'URL o l'origine principale nell'URL
affatto, come in about:blank o data:text/html,<html>Hello, World!</html>).
Tuttavia, questi frame possono comunque essere associati al frame di creazione.
Per inserire questi frame, le estensioni possono specificare la
"match_origin_as_fallback" proprietà in una specifica dello script di contenuti nel
manifest.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Se specificato e impostato su true, Chrome esaminerà l'origine dell'iniziatore del frame per determinare se il frame corrisponde, anziché l'URL del frame stesso. Tieni presente che questa potrebbe essere diversa anche dall'origine del frame di destinazione (ad es. gli URL data: hanno un'origine nulla).
L'iniziatore del frame è il frame che ha creato o navigato nel frame di destinazione. Sebbene sia in genere il frame principale o di apertura diretto, potrebbe non esserlo (come nel caso di un frame che naviga in un iframe all'interno di un iframe).
Poiché viene confrontata l'origine del frame iniziatore, il frame iniziatore potrebbe trovarsi in qualsiasi percorso da quell'origine. Per chiarire questa implicazione, Chrome
richiede che tutti gli script di contenuti specificati con "match_origin_as_fallback"
impostato su true specifichino anche un percorso di *.
Quando vengono specificati sia "match_origin_as_fallback" sia "match_about_blank",
"match_origin_as_fallback" ha la priorità.
Comunicazione con la pagina di incorporamento
Sebbene gli ambienti di esecuzione degli script di contenuti e delle pagine che li ospitano siano isolati l'uno dall'altro, condividono l'accesso al DOM della pagina. Se la pagina vuole comunicare con lo script di contenuti o con l'estensione tramite lo script di contenuti, deve farlo tramite il DOM condiviso.
Un esempio può essere eseguito utilizzando 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);
La pagina non di estensione, example.html, pubblica messaggi a se stessa. Questo messaggio viene intercettato e ispezionato dallo script di contenuti e poi pubblicato nel processo di estensione. In questo modo, la pagina stabilisce una linea di comunicazione con il processo di estensione. Il contrario è possibile con mezzi simili.
Accedere ai file di estensione
Per accedere a un file di estensione da uno script di contenuti, puoi chiamare
chrome.runtime.getURL() per ottenere l'URL assoluto dell'asset dell'estensione, come mostrato nell'esempio seguente (content.js):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Per utilizzare caratteri o immagini in un file CSS, puoi utilizzare @@extension_id per creare un URL come mostrato nell'esempio seguente (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');
}
Tutti gli asset devono essere dichiarati come risorse accessibili dal web nel manifest.json file:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Content Security Policy
Gli script di contenuti in esecuzione in mondi isolati hanno i seguenti Content Security Policy (CSP):
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Analogamente alle restrizioni applicate ad altri contesti di estensione, ciò impedisce l'utilizzo di eval() e il caricamento di script esterni.
Per le estensioni non pacchettizzate, i CSP includono anche localhost:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Quando uno script di contenuti viene inserito nel mondo principale, vengono applicati i CSP della pagina.
Rafforza la tua sicurezza
Sebbene i mondi isolati forniscano un livello di protezione, l'utilizzo di script di contenuti può creare vulnerabilità in un'estensione e nella pagina web. Se lo script di contenuti riceve contenuti da un
sito web separato, ad esempio chiamando fetch(), fai attenzione a filtrare i contenuti per evitare attacchi di
cross-site scripting prima di inserirli. Comunica solo tramite HTTPS per
evitare "man-in-the-middle" attacchi.
Assicurati di filtrare le pagine web dannose. Ad esempio, i seguenti pattern sono pericolosi e non consentiti in 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);
In alternativa, preferisci API più sicure che non eseguono script:
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);