Script di contenuti

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:

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

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

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:

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