Uno script di contenuto è una parte dell'estensione che viene eseguita nel contesto di una particolare pagina Web (al contrario degli script di sfondo che fanno parte dell'estensione o degli script che fanno parte del sito stesso, come quelli caricati utilizzando l'elemento <script>).

Gli script in Background possono accedere a tutte le WebExtension JavaScript APIs, ma non possono accedere direttamente al contenuto delle pagine Web. Quindi, se la tua estensione ha bisogno di farlo, hai bisogno degli script di contenuto .

Proprio come gli script caricati da normali pagine web, gli script di contenuto possono leggere e modificare il contenuto delle loro pagine usando le API DOM standard.

Gli script di contenuto possono accedere solo a un piccolo sottoinsieme delle API di WebExtension, ma possono comunicare con script in background utilizzando un sistema di messaggistica e quindi accedere indirettamente alle API di WebExtension.

Gli script di contenuto possono accedere solo a un piccolo sottoinsieme delle WebExtension APIs, ma possono comunicare con gli script in background utilizzando un sistema di messaggistica e quindi accedere indirettamente alle WebExtension APIs.

Nota che gli script di contenuto sono bloccati nei seguenti domini:

  • accounts-static.cdn.mozilla.net
  • accounts.firefox.com
  • addons.cdn.mozilla.net
  • addons.mozilla.org
  • api.accounts.firefox.com
  • content.cdn.mozilla.net
  • content.cdn.mozilla.net
  • discovery.addons.mozilla.org
  • input.mozilla.org
  • install.mozilla.org
  • oauth.accounts.firefox.com
  • profile.accounts.firefox.com
  • support.mozilla.org
  • sync.services.mozilla.com
  • testpilot.firefox.com

Se provi a iniettare uno script di contenuto in una pagina in uno di questi domini, l'operazione fallirà e la pagina registrerà un errore CSP.

Poiché queste restrizioni includono addons.mozilla.org, gli utenti possono essere tentati di utilizzare l'estensione immediatamente dopo l'installazione, solo per scoprire che non funziona! È possibile aggiungere un avviso appropriato o una pagina di inserimento per spostare gli utenti da addons.mozilla.org.

Valori aggiunti all'ambito globale di uno script di contenuto con var foo o window.foo = "bar" potrebbe scomparire a causa di un bug 1408996.

Caricamento di script di contenuto

Puoi caricare uno script di contenuto in una pagina web in tre modi:

Al momento dell'installazione, nelle pagine che corrispondono ai pattern URL: utilizzando la chiave content_scripts in manifest.json, è possibile chiedere al browser di caricare uno script di contenuto ogni volta che il browser carica una pagina il cui URL corrisponde a un determinato pattern.

  1. Al momento dell'installazione, nelle pagine che corrispondono al pattern URL: utilizzando il tag content_scripts nel vostro file manifest.json, è possibile chiedere al browser di caricare uno script di contenuto ogni volta che il browser carica una pagina il cui URL corrisponde ad un determinato pattern
  2. In fase di runtime, nelle pagine che corrispondono a un determinato pattern URL: utilizzando contentScripts API, potete chiedere al browser di caricare uno script di contenuto ogni volta che il browser carica una pagina il cui URL corrisponde ad un determinato pattern. Questo è un metodo possibile (1), tranne che è possibile aggiungere e rimuovere script di contenuto in fase di esecuzione.
  3. In fase di runtime, in specifiche schede: utilizzando le tabs.executeScript() API, è possibile caricare uno script di contenuto in una scheda specifica ogni volta che si desidera: ad esempio, in risposta all'utente che fa clic su una azione del browser.

Esiste un solo ambito globale per frame e per estensione. Ciò significa che le variabili di uno script di contenuto possono essere direttamente accessibili da un altro script di contenuto, indipendentemente dal modo in cui è stato caricato.

Utilizzando i metodi (1) e (2), è possibile caricare gli script solo in pagine i cui URL possono essere rappresentati utilizzando un pattern di confronto.

Usando il metodo (3), puoi anche caricare script in pagine contenute nella tua estensione, ma non puoi caricare script in pagine del browser privilegiate (come "about: debugging" o "about: addons").

Ambiente degli script di contenuto

accesso al DOM

Gli script di contenuto possono accedere e modificare il DOM della pagina, proprio come possono fare i normali script di pagina. Possono anche vedere eventuali modifiche apportate al DOM tramite script di pagina.

Tuttavia, gli script di contenuto ottengono una "visualizzazione originale del DOM". Questo significa:

  • Gli script di contenuto non possono vedere le variabili JavaScript definite dagli script di pagina.
  • Se uno script di pagina ridefinisce una proprietà DOM incorporata, lo script di contenuto vedrà la versione originale della proprietà, non la versione ridefinita.

In Firefox, questo comportamento è chiamato Xray vision.

Consideriamo una pagina web come questa:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  </head>

  <body>
    <script src="page-scripts/page-script.js"></script>
  </body>
</html>

Lo script "page-script.js" esegue:

// page-script.js

// add a new element to the DOM
var p = document.createElement("p");
p.textContent = "This paragraph was added by a page script.";
p.setAttribute("id", "page-script-para");
document.body.appendChild(p);

// define a new property on the window
window.foo = "This global variable was added by a page script";

// redefine the built-in window.confirm() function
window.confirm = function() {
  alert("The page script has also redefined 'confirm'");
}

Ora un'estensione inietta uno script di contenuto nella pagina:

// content-script.js

// can access and modify the DOM
var pageScriptPara = document.getElementById("page-script-para");
pageScriptPara.style.backgroundColor = "blue";

// can't see page-script-added properties
console.log(window.foo);  // undefined

// sees the original form of redefined properties
window.confirm("Are you sure?"); // calls the original window.confirm()

Lo stesso vale al contrario: gli script di pagina non possono vedere le proprietà JavaScript aggiunte dagli script di contenuto.

Ciò significa che gli script di contenuto possono contare su proprietà DOM che si comportano in modo prevedibile, senza preoccuparsi delle sue variabili che si scontrano con le variabili dello script di pagina.

Una conseguenza pratica di questo comportamento è che uno script di contenuto non avrà accesso a nessuna libreria JavaScript caricata dalla pagina. Ad esempio, se la pagina include jQuery, lo script di contenuto non sarà in grado di vederlo.

Se uno script di contenuto desidera utilizzare una libreria JavaScript, la stessa libreria deve essere iniettata come script di contenuto insieme allo script di contenuto che vuole utilizzarlo:

"content_scripts": [
  {
    "matches": ["*://*.mozilla.org/*"],
    "js": ["jquery.js", "content-script.js"]
  }
]

Nota: Firefox fornisce alcune API che consentono agli script di contenuto di accedere agli oggetti JavaScript creati dagli script di pagina e di esporre i propri oggetti JavaScript agli script di pagina.

Vedere Condivisione di oggetti con script di pagina per maggiori dettagli.

WebExtension APIs

Oltre alle API DOM standard, gli script di contenuto possono utilizzare le seguenti API WebExtension:

Da extension:

Da runtime:

Da i18n:

Da menus:

Tutto da storage.

XHR e Fetch

Gli script di contenuto posso fare delle normali richieste usando window.XMLHttpRequest e window.fetch() APIs.

Gli script di contenuto ottengono gli stessi privilegi per i cross-domain del resto dell'estensione: quindi se l'estensione ha richiesto l'accesso tra cross-domain per un dominio utilizzando il tag permissions nel file manifest.json, i suoi script di contenuto ottengono l'accesso anche a quel dominio.

Ciò si ottiene esponendo le istanze XHR e di recupero più privilegiate nello script di contenuto, che ha l'effetto collaterale di non impostare le intestazioni di Origin e Referer come una richiesta dalla pagina stessa, spesso è preferibile evitare che la richiesta sveli la sua natura trasversale. Dalla versione 58 in poi le estensioni che devono eseguire richieste che si comportano come se fossero inviate dal contenuto stesso possono utilizzare content.XMLHttpRequest e content.fetch() instead. Per le estensioni cross-browser, la loro presenza deve essere rilevata dalla funzione.

Communicazione con gli script di background

Sebbene gli script di contenuto non possano utilizzare direttamente la maggior parte delle API di WebExtension, possono comunicare con gli script in background dell'estensione utilizzando le API di messaggistica e pertanto possono accedere indirettamente a tutte le API a cui possono accedere gli script in background.

Esistono due schemi di base per la comunicazione tra gli script in background e gli script di contenuto: è possibile inviare messaggi una tantum, con una risposta opzionale, oppure è possibile impostare una connessione più longeva tra i due lati e utilizzare tale connessione per scambiare messaggi .

Messaggi one-off

Per inviare messaggi one-off, con una risposta opzionale, puoi utilizzare le seguenti API:

  In content script In background script
Invia un messaggi browser.runtime.sendMessage() browser.tabs.sendMessage()
Ricevi un messaggio browser.runtime.onMessage browser.runtime.onMessage

Ad esempio, ecco uno script di contenuto che si pone in ascolto degli eventi click in una pagina web.

Se il clic era su un collegamento, invia la pagina di sfondo con l'URL di destinazione:

// content-script.js

window.addEventListener("click", notifyExtension);

function notifyExtension(e) {
  if (e.target.tagName != "A") {
    return;
  }
  browser.runtime.sendMessage({"url": e.target.href});
}

Lo script in background ascolta questi messaggi e visualizza una notifica utilizzando le notifications API:

// background-script.js

browser.runtime.onMessage.addListener(notify);

function notify(message) {
  browser.notifications.create({
    "type": "basic",
    "iconUrl": browser.extension.getURL("link.png"),
    "title": "You clicked a link!",
    "message": message.url
  });
}

(Questo codice di esempio è una riduzione dell'esempio notify-link-clicks-i18n che è possibile trovare su GitHub.)

Messaggi connection-based

L'invio di messaggi one-off può risultare complicato se si scambiano molti messaggi tra uno script in background e uno script di contenuto. Quindi uno schema alternativo è stabilire una connessione più longeva tra i due contesti e usare questa connessione per scambiare messaggi.

Ogni lato ha un oggetto runtime.Port, che può utilizzare per scambiare messaggi.

Per creare la connessione:

Questo metodo ritorna un oggetto di tipo runtime.Port.

Dopo che tutti e due i lati hanno una porta di comunicazione possono:

  • inviare messaggi usando runtime.Port.postMessage(), e
  • ricevere messaggi usando runtime.Port.onMessage()

Ad esempio, appena viene caricato, questo script di contenuto:

  • Si collega ad uno script di background
  • Memorizza la Port in una variabile myPort
  • Ascolta i messaggi su myPort, e li visualizza nella console di log
  • Usa myPort per inviare messaggi allo script di background quando l'utente fa clic sul documento
// content-script.js

var myPort = browser.runtime.connect({name:"port-from-cs"});
myPort.postMessage({greeting: "hello from content script"});

myPort.onMessage.addListener(function(m) {
  console.log("In content script, received message from background script: ");
  console.log(m.greeting);
});

document.body.addEventListener("click", function() {
  myPort.postMessage({greeting: "they clicked the page!"});
});

Lo script di sfondo corrispondente:

  • attende i tentativi di connessione dallo script di contenuto
  • quando riceve un tentativo di connessione:
    • memorizza la porta in una variabile denominata portFromCS
    • invia allo script del contenuto un messaggio utilizzando la porta
    •   inizia ad ascoltare i messaggi ricevuti sulla porta e li invia al log della console
  • invia messaggi allo script di contenuto, utilizzando portFromCS, quando l'utente fa clic sull'azione del browser dell'estensione
// background-script.js

var portFromCS;

function connected(p) {
  portFromCS = p;
  portFromCS.postMessage({greeting: "hi there content script!"});
  portFromCS.onMessage.addListener(function(m) {
    console.log("In background script, received message from content script");
    console.log(m.greeting);
  });
}

browser.runtime.onConnect.addListener(connected);

browser.browserAction.onClicked.addListener(function() {
  portFromCS.postMessage({greeting: "they clicked the button!"});
});

Multipli script di contenuto

Se si dispone di più script di contenuto che comunicano contemporaneamente, è possibile memorizzare ciascuna connessione in un array.

// background-script.js

var ports = []

function connected(p) {
  ports[p.sender.tab.id]    = p
  //...
}

browser.runtime.onConnect.addListener(connected)

browser.browserAction.onClicked.addListener(function() {
  ports.forEach(p => {
        p.postMessage({greeting: "they clicked the button!"})
    })
});

Communicazione con la pagina web

Sebbene per impostazione predefinita gli script di contenuto non abbiano accesso agli oggetti creati dagli script di pagina, possono comunicare con gli script di pagina utilizzando la API DOM window.postMessage e window.addEventListener.

Per esempio:

// page-script.js

var messenger = document.getElementById("from-page-script");

messenger.addEventListener("click", messageContentScript);

function messageContentScript() {
  window.postMessage({
    direction: "from-page-script",
    message: "Message from the page"
  }, "*");
// content-script.js

window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data &&
      event.data.direction == "from-page-script") {
    alert("Content script received message: \"" + event.data.message + "\"");
  }
});

Per un esempio completo e funzionante, visitate la pagina demo su GitHub e seguite le istruzioni.

Nota che ogni volta che si interagisce con contenuti web non fidati in questo modo, è necessario fare molta attenzione. Le estensioni sono codici privilegiati che possono avere potenti funzionalità e pagine Web ostili possono facilmente ingannarli per accedere a tali funzionalità.

Per fare un esempio banale, supponiamo che il codice dello script del contenuto che riceve il messaggio faccia qualcosa del genere:
 

window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data.direction &&
      event.data.direction == "from-page-script") {
    eval(event.data.message);
  }
});

Ora lo script di pagina può eseguire qualsiasi codice con tutti i privilegi dello script di contenuto.

Usare eval() in uno script di contenuto

In Chrome, eval() esegue sempre il codice nel contesto dello script del contenuto, non nel contesto della pagina.

In Firefox:

  • Se usate eval(), esso esegue il codice nel contesto dello script di conteneto.
  • Se usate window.eval(), esso esegue il code nel contesto della pagina.

Per esempio, considerate uno script di contenuto simile a questo:

// content-script.js

window.eval('window.x = 1;');
eval('window.y = 2');

console.log(`In content script, window.x: ${window.x}`);
console.log(`In content script, window.y: ${window.y}`);

window.postMessage({
  message: "check"
}, "*");

Questo codice crea semplicemente le variabili x e y usando window.eval() and eval(), quindi invia i loro valori al log della console, ed infine invia un messaggio alla pagina.

Alla ricezione del messaggio, lo script della pagina invia le variabili al log della console:

 

window.addEventListener("message", function(event) {
  if (event.source === window && event.data && event.data.message === "check") {
    console.log(`In page script, window.x: ${window.x}`);
    console.log(`In page script, window.y: ${window.y}`);
  }
});

In Chrome, si ottine questo risultato:

In content script, window.x: 1
In content script, window.y: 2
In page script, window.x: undefined
In page script, window.y: undefined

In Firefox, invece, il risultato sarà il seguente:

In content script, window.x: undefined
In content script, window.y: 2
In page script, window.x: 1
In page script, window.y: undefined

Lo stesso vale per setTimeout(), setInterval(), e Function().

Quando si esegue il codice nel contesto della pagina, è necessario prestare molta attenzione. L'ambiente della pagina è controllato da pagine Web potenzialmente dannose, che possono ridefinire gli oggetti con cui interagire per comportarsi in modi imprevisti:

// page.js redefines console.log

var original = console.log;

console.log = function() {
  original(true);
}
 
// content-script.js calls the redefined version

window.eval('console.log(false)');

Tag del documento e collaboratori

Tag: 
Hanno collaborato alla realizzazione di questa pagina: MarcoAGreco
Ultima modifica di: MarcoAGreco,