Script di contenuti

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:

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 array di stringhe 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:

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 matches e a qualsiasi pattern include_globs.
  • L'URL non corrisponde anche a un pattern exclude_matches o exclude_globs. Poiché la proprietà matches è obbligatoria, exclude_matches, include_globs, e exclude_globs possono 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/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 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" ],
}]);

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:

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);

In alternativa, 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);