Script di contenuti

Gli script di contenuti sono file che vengono eseguiti nel contesto di pagine web. Utilizzando il Document Object Model (DOM) standard, sono in grado di leggere i dettagli delle pagine web visitate dal browser, apportarvi modifiche e passare informazioni all'estensione padre.

Informazioni sulle funzionalità degli script dei contenuti

Gli script di contenuti possono accedere ai file delle estensioni dopo averli dichiarati come risorse accessibili sul web. Possono accedere direttamente alle seguenti API delle estensioni:

Gli script di contenuti non possono accedere direttamente ad altre API. Tuttavia, possono accedervi indirettamente scambiando messaggi con altre parti dell'estensione.

Lavorare in mondi isolati

Gli script di contenuti risiedono in un mondo isolato, consentendo a uno script di contenuti di apportare modifiche al proprio ambiente JavaScript senza entrare in conflitto con gli script dei contenuti della pagina o di altre estensioni.

Un'estensione può essere eseguita in una pagina web con un codice simile all'esempio riportato di seguito.

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 inserire il seguente script di contenuti utilizzando una delle tecniche descritte nella sezione Inserisci 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.

Inserisci script

Gli script di contenuti possono essere dichiarati in modo statico, dichiarato dinamicamente o inseriti in modo programmatico.

Inserisci con dichiarazioni statiche

Utilizza le dichiarazioni degli script di contenuti statici in manifest.json per gli script che devono essere eseguiti automaticamente su un insieme di pagine ben noto.

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 eseguiti automaticamente 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 in quali pagine verrà inserito questo script di contenuti. Consulta Pattern di corrispondenza per maggiori dettagli sulla sintassi di queste stringhe e Pattern di corrispondenza e glob per informazioni su come escludere gli URL.
css array di stringhe Facoltativo. L'elenco di file CSS da inserire nelle pagine corrispondenti. Questi vengono inseriti nell'ordine in cui appaiono in questo array, prima che venga creato o visualizzato qualsiasi DOM per la pagina.
js array di stringhe Facoltativo. L'elenco di file JavaScript da inserire 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 root 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 boolean 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 boolean 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. Questi includono frame con schemi diversi, come about:, data:, blob: e filesystem:. Vedi anche Inserimento di frame correlati.
world ExecutionWorld Facoltativo. Il mondo JavaScript in cui eseguire uno script. Il valore predefinito è ISOLATED. Vedi anche Lavorare in mondi isolati.

Inserisci dichiarazioni dinamiche

Gli script di contenuti dinamici sono utili quando i pattern di corrispondenza per gli script di contenuti non sono noti o quando questi script non devono essere sempre inseriti su host noti.

Introdotte in Chrome 96, le dichiarazioni dinamiche sono simili alle dichiarazioni statiche, ma l'oggetto script dei contenuti viene registrato in Chrome utilizzando metodi nello spazio dei nomi chrome.scripting anziché in manifest.json. L'API Scripting consente inoltre agli sviluppatori di estensioni di:

Come le dichiarazioni statiche, le dichiarazioni dinamiche possono includere file JavaScript, 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"));

Inserisci in modo programmatico

Utilizza l'inserimento programmatico per gli script di contenuti che devono essere eseguiti in risposta a eventi o in occasioni specifiche.

Per inserire uno script di contenuti in modo programmatico, l'estensione deve avere le 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 oppure 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"]
  });
});

Oppure, 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 inserita è una copia della funzione a cui viene fatto riferimento nella chiamata chrome.scripting.executeScript(), non della funzione originale stessa. Di conseguenza, il corpo della funzione deve essere autonomo; i riferimenti a variabili al di fuori della funzione faranno sì che lo script di contenuti restituisca un elemento ReferenceError.

Quando inserisci i dati come 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. Sono escluse le pagine in cui questo script di contenuti verrebbe altrimenti inserito. Per informazioni dettagliate sulla sintassi di queste stringhe, consulta Pattern di corrispondenza.
include_globs array di stringhe Facoltativo. Applicato dopo il giorno matches per includere solo gli URL che corrispondono anche a questo glob. Ha lo scopo di emulare la parola chiave Greasemonkey @include.
exclude_globs array di stringhe Facoltativo. Applicato dopo il giorno matches per escludere gli URL che corrispondono a questo glob. Destinato a emulare la parola chiave Greasemonkey @exclude.

Lo script dei contenuti verrà inserito in una pagina se si verificano entrambe le seguenti condizioni:

  • L'URL corrisponde a qualsiasi pattern matches e a qualsiasi pattern include_globs.
  • Inoltre, l'URL non corrisponde a un pattern exclude_matches o exclude_globs. Poiché la proprietà matches è obbligatoria, è possibile usare exclude_matches, include_globs e exclude_globs solo per limitare le pagine interessate.

La seguente estensione inserisce lo script dei 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à del globo 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 "caratteri jolly". L'asterisco (*) corrisponde a qualsiasi stringa di qualsiasi lunghezza, inclusa la stringa vuota, mentre il punto interrogativo (?) corrisponde a qualsiasi carattere singolo.

Ad esempio, il valore del glob 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"]
    }
  ],
  ...
}

È possibile includerne uno, tutti o alcuni per raggiungere 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 consente di controllare quando i file JavaScript vengono inseriti nella pagina web. Il valore preferito e predefinito è "document_idle". Consulta il tipo RunAt per conoscere 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 Preferito. Se possibile, utilizza "document_idle".

Il browser sceglie un orario in cui inserire gli script tra il giorno "document_end" e subito dopo l'attivazione dell'evento window.onload. Il momento esatto dell'inserimento dipende dalla complessità del documento e dal tempo necessario per il caricamento ed è ottimizzato per la velocità di caricamento delle pagine.

Gli script di contenuti in esecuzione alla pagina "document_idle" non richiedono l'ascolto dell'evento window.onload, sono garantiti che vengano eseguiti al termine del DOM. Se uno script deve essere eseguito dopo il giorno window.onload, l'estensione può verificare se onload è già stato attivato utilizzando la proprietà document.readyState.
document_start stringa Gli script vengono inseriti dopo tutti i file da css, ma prima che venga creato un altro DOM o prima 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.

Specifica le cornici

Il campo "all_frames" consente all'estensione di specificare se i file JavaScript e CSS devono essere inseriti in tutti i frame che corrispondono ai requisiti per gli URL specificati o solo nel frame più alto di una scheda.

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 Descrizione
all_frames boolean Facoltativo. Il valore predefinito è false, il che significa che viene soddisfatto solo il frame principale.

Se viene specificato true, verranno inseriti tutti i frame, anche se non è il frame più alto della scheda. Ogni frame viene controllato in modo indipendente per verificare i requisiti dell'URL. Non verrà inserito nei frame secondari se i requisiti dell'URL non sono soddisfatti.

Le estensioni potrebbero voler eseguire script in frame correlati a un frame corrispondente, ma che non corrispondono. Uno scenario comune in questo caso è per i frame con URL creati da un frame corrispondente, ma i cui URL non corrispondono ai pattern specificati dello script.

Questo si verifica quando 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 includere 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 inserire questi frame, le estensioni possono specificare la proprietà "match_origin_as_fallback" in una specifica di script di contenuti nel file 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 diversa dall'origine del frame di destinazione (ad esempio, data: URL hanno un'origine nulla).

L'iniziatore del frame è il frame che ha creato o navigato nel frame target. Sebbene si tratti di solito dell'elemento principale diretto o dell'elemento di apertura, 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, quest'ultimo potrebbe essere attivo in qualsiasi percorso da quell'origine. Per chiarire questa implicazione, Chrome richiede che tutti gli script di contenuti specificati con l'elemento "match_origin_as_fallback" impostato su true per specificare anche un percorso di *.

Quando sono specificati entrambi i criteri ("match_origin_as_fallback" e "match_about_blank"), "match_origin_as_fallback" ha la priorità.

Comunicazione con la pagina di incorporamento

Anche se gli ambienti di esecuzione degli script di contenuti e le pagine che li ospitano sono isolati l'uno dall'altro, condividono l'accesso al DOM della pagina. Se la pagina vuole comunicare con lo script dei contenuti o con l'estensione tramite lo script dei contenuti, deve farlo tramite il DOM condiviso.

Puoi ottenere un esempio 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 senza estensione, example.html, pubblica i messaggi a se stessa. Questo messaggio viene intercettato e controllato dallo script dei contenuti e quindi pubblicato nel processo di estensione. In questo modo, la pagina stabilisce una linea di comunicazione per il processo di estensione. Il contrario è possibile con mezzi simili.

Accesso ai file delle estensioni

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 usare caratteri o immagini in un file CSS, puoi usare @@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 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

Mentre i mondi isolati offrono 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 separato, ad esempio chiamando fetch(), assicurati di filtrare i contenuti in base agli attacchi di cross-site scripting prima di iniettarli. Comunicare 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 consentiti in Manifest V3:

Cosa non fare

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Cosa non fare

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

Preferisci API più sicure che non eseguono script:

Cosa fare

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Cosa fare

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);