Gli script dei contenuti sono file eseguiti nel contesto delle pagine web. Utilizzando il Document Object Model (DOM) standard, sono in grado di leggere i dettagli delle pagine web visitate dal browser, apportare modifiche e passare informazioni all'estensione principale.
Informazioni sulle funzionalità degli script dei contenuti
Gli script di contenuti possono accedere direttamente alle seguenti API di estensione:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Gli script di contenuto non sono in grado di 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 dichiararle come
risorse accessibili tramite il web. Tieni presente che questo espone le risorse anche a eventuali script proprietari o di terze parti in esecuzione sullo stesso sito.
Lavorare in mondi isolati
Gli script di contenuti operano in un ambiente isolato, il che consente a uno script di contenuti di apportare modifiche al proprio ambiente JavaScript senza entrare in conflitto con gli script di contenuti della pagina o 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>
L'estensione potrebbe iniettare il seguente script di contenuti utilizzando una delle tecniche descritte nella sezione Iniettare 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.
Inietta script
Gli script dei contenuti possono essere dichiarati in modo statico, dichiarati dinamicamente o iniettati in modo programmatico.
Eseguire l'iniezione con dichiarazioni statiche
Utilizza le dichiarazioni di script di contenuti statici in manifest.json per gli script che devono essere eseguiti automaticamente su un insieme ben noto di pagine.
Gli script dichiarati in modo statico vengono registrati nel file manifest sotto la chiave "content_scripts"
.
Possono includere file JavaScript, file CSS o entrambi. Tutti gli script di contenuti con esecuzione automatica devono specificare
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 dei contenuti. Per informazioni dettagliate sulla sintassi di queste stringhe, consulta Pattern di corrispondenza e Pattern di corrispondenza e glob per informazioni su come escludere gli URL. |
css |
array di stringhe | Facoltativo. L'elenco dei file CSS da iniettare nelle pagine corrispondenti. Questi vengono iniettati nell'ordine in cui appaiono in questo array, prima che venga costruito o visualizzato un DOM per la pagina. |
js |
|
Facoltativo. L'elenco dei file JavaScript da iniettare nelle pagine corrispondenti. I file vengono inseriti nell'ordine in cui appaiono in questo array. Ogni stringa in questo elenco deve contenere un percorso relativo a una risorsa nella directory principale dell'estensione. Le barre iniziali ("/") vengono tagliate 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
in cui 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 l'origine potrebbero non corrispondere direttamente al pattern. Sono inclusi frame con schemi diversi, ad esempio
about: , data: , blob: e
filesystem: . Vedi anche
Inserzione in frame correlati.
|
world |
ExecutionWorld |
Facoltativo. L'ambiente JavaScript in cui deve essere eseguito uno script. Il valore predefinito è ISOLATED . Vedi anche
Lavorare in mondi isolati.
|
Eseguire l'iniezione 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 non è sempre necessario iniettare gli script di contenuti in host noti.
Introdotte in Chrome 96, le dichiarazioni dinamiche sono simili alle dichiarazioni statiche, ma l'oggetto dello script dei contenuti viene registrato in Chrome utilizzando i metodi nello spazio dei nomi chrome.scripting
anziché in manifest.json. L'API di scripting consente inoltre agli sviluppatori di estensioni di:
- Registra gli script di contenuti.
- Ottieni un elenco degli script di contenuti registrati.
- Aggiorna l'elenco degli script dei contenuti registrati.
- Rimuovi 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"));
Eseguire l'iniezione in modo programmatico
Utilizza l'iniezione programmatica per gli script dei contenuti che devono essere eseguiti in risposta a eventi o in occasioni specifiche.
Per iniettare uno script di contenuti in modo programmatico, l'estensione ha bisogno delle autorizzazioni di host per la pagina in cui sta tentando di iniettare gli script. Le autorizzazioni host possono essere concesse richiedendole come parte del manifest dell'estensione o utilizzando temporaneamente "activeTab"
.
Di seguito sono riportate 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, il corpo di una funzione può essere inserito ed eseguito 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 iniettata è una copia della funzione a cui viene fatto riferimento nella chiamatachrome.scripting.executeScript()
, non la funzione originale stessa. Di conseguenza, il corpo della funzione deve essere autonomo; i riferimenti a variabili esterne alla funzione causeranno l'emissione di un ReferenceError
nello script dei contenuti.
Quando esegui l'iniezione come funzione, puoi anche passare gli 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 declarative.
Nome | Tipo | Descrizione |
---|---|---|
exclude_matches |
array di stringhe | Facoltativo. Esclude le pagine in cui altrimenti verrebbe inserito questo script dei contenuti. Per informazioni dettagliate sulla sintassi di queste stringhe, consulta Pattern di corrispondenza. |
include_globs |
array di stringhe | Facoltativo. Applicato dopo matches per includere solo gli URL che rientrano anche in questo pattern generico. Lo scopo è emulare la parola chiave @include
di Greasemonkey. |
exclude_globs |
array di stringhe | Facoltativo. Viene applicato dopo matches per escludere gli URL che corrispondono a questo
glob. Destinato a emulare la parola chiave @exclude
di Greasemonkey. |
Lo script dei contenuti verrà inserito in una pagina se si verificano entrambe le seguenti condizioni:
- Il suo URL corrisponde a qualsiasi pattern
matches
einclude_globs
. - L'URL non corrisponde nemmeno a un pattern
exclude_matches
oexclude_globs
. Poiché la proprietàmatches
è obbligatoria,exclude_matches
,include_globs
eexclude_globs
possono essere utilizzati 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 di glob accettabili sono URL che possono contenere asterischi e punti interrogativi "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 pattern generico https://???.example.com/foo/\*
corrisponde a uno dei seguenti:
https://www.example.com/foo/bar
https://the.example.com/foo/
Tuttavia, non corrisponde a quanto segue:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Questa estensione inserisce lo script dei 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 dei 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"]
}
],
...
}
Per ottenere l'ambito corretto, puoi includere uno, tutti o alcuni di questi elementi.
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 di esecuzione
Il campo run_at
controlla quando i file JavaScript vengono inseriti nella pagina web. Il valore preferito e predefinito è "document_idle"
. Consulta il tipo RunAt per altri possibili valori.
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 | Preferita. Utilizza "document_idle" , se possibile.Il browser sceglie un momento per iniettare gli script tra "document_end" e subito dopo
l'attivazione dell'evento window.onload . Il momento esatto dell'iniezione dipende dalla complessità del documento e dal tempo necessario per il caricamento ed è ottimizzato per la velocità di caricamento della pagina.Gli script di contenuto eseguiti in "document_idle" non devono ascoltare l'evento window.onload , poiché viene garantito che vengano eseguiti al termine del DOM. Se un
script deve essere eseguito dopo window.onload , l'estensione può verificare se
onload è già stato attivato utilizzando la proprietà document.readyState . |
document_start |
stringa | Gli script vengono iniettati dopo i file di css , ma prima che qualsiasi altro DOM venga
creato o qualsiasi altro script venga eseguito. |
document_end |
stringa | Gli script vengono iniettati immediatamente dopo il completamento del DOM, ma prima del caricamento di risorse secondarie come immagini e frame. |
Specifica i frame
Per gli script di contenuti dichiarativi specificati nel manifest, il campo "all_frames"
consente all'estensione di specificare se i file JavaScript e CSS devono essere inseriti in tutti i frame corrispondenti ai requisiti dell'URL specificato o solo nel frame più alto in un
tab:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Quando registri gli script dei contenuti in modo programmatico utilizzando chrome.scripting.registerContentScripts(...)
, il parametro allFrames
può essere utilizzato per specificare se lo script dei contenuti deve essere inserito in tutti i frame corrispondenti ai requisiti dell'URL specificato o solo nel frame più alto di una scheda. Può essere utilizzato solo con tabId e non può essere utilizzato se sono specificati frameIds o documentIds:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Inserire gli elementi nei frame correlati
Le estensioni potrebbero voler eseguire script in frame correlati a un frame corrispondente, ma che non corrispondono. In questo caso, uno scenario comune riguarda i frame con URL creati da un frame corrispondente, ma i cui URL non corrispondono ai pattern specificati dallo script.
Questo accade quando un'estensione vuole iniettare frame con URL che hanno schemi about:
, data:
, blob:
e filesystem:
. In questi casi, l'URL non corrisponderà al pattern dello script dei contenuti e, nel caso di about:
e data:
, non includerà nemmeno l'URL o l'origine principale nell'URL, come in about:blank
o data:text/html,<html>Hello, World!</html>
. Tuttavia, questi frame possono comunque essere associati al frame di creazione.
Per eseguire l'iniezione in questi frame, le estensioni possono specificare la proprietà "match_origin_as_fallback"
in una specifica dello script dei 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 potrebbe anche essere diverso 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 visualizzato il frame di destinazione. Sebbene in genere si tratti del frame principale o di quello di apertura, potrebbe non essere così (come nel caso di un frame che naviga in un iframe all'interno di un iframe).
Poiché viene confrontata l'origine del frame di iniziativa, il frame di iniziativa potrebbe trovarsi in qualsiasi percorso a partire da quell'origine. Per chiarire questa implicazione, Chrome richiede che gli script dei contenuti specificati con "match_origin_as_fallback"
impostato su true
specifichino anche un percorso di *
.
Quando sono specificati sia "match_origin_as_fallback"
che "match_about_blank"
,"match_origin_as_fallback"
ha la precedenza.
Comunicazione con la pagina di incorporamento
Sebbene gli ambienti di esecuzione degli script di contenuti e le pagine che li ospitano siano isolati tra loro, condividono l'accesso al DOM della pagina. Se la pagina vuole comunicare con lo script di contenuto o con l'estensione tramite lo script di contenuto, deve farlo tramite il DOM condiviso.
Un esempio può essere realizzato 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 controllato dallo script di contenuti, quindi inviato al processo di estensione. In questo modo, la pagina stabilisce una linea di comunicazione con il processo di estensione. L'operazione inversa è possibile tramite metodi simili.
File di estensione di accesso
Per accedere a un file di estensioni da uno script di contenuti, puoi chiamare
chrome.runtime.getURL()
per ottenere l'URL assoluto della risorsa 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 tramite il web nel file manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
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 dei contenuti riceve contenuti da un sito web distinto, ad esempio chiamando fetch()
, assicurati di filtrare i contenuti per proteggerli dagli attacchi di cross-site scripting prima di inserirli. Comunica solo tramite HTTPS per evitare attacchi "man-in-the-middle".
Assicurati di filtrare per pagine web dannose. Ad esempio, i seguenti pattern sono pericolosi e non sono consentiti nella versione 3 del file manifest:
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);
Preferisci invece 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);