Sostituzione di pagine di sfondo o di eventi con un service worker
Un service worker sostituisce la pagina di background o degli eventi dell'estensione per garantire che il codice in background rimanga fuori dal thread principale. In questo modo, le estensioni vengono eseguite solo quando necessario, risparmiando risorse.
Le pagine in background sono un componente fondamentale delle estensioni fin dalla loro introduzione. In parole povere, le pagine di sfondo forniscono un ambiente indipendente da qualsiasi altra finestra o scheda. In questo modo le estensioni possono osservare gli eventi e intervenire in risposta.
Questa pagina descrive le attività per la conversione delle pagine in background in worker di servizio dell'estensione. Per saperne di più sui service worker delle estensioni in generale, consulta il tutorial Gestire gli eventi con i service worker e la sezione Informazioni sui service worker delle estensioni.
Differenze tra script in background e service worker di estensione
In alcuni contesti vedrai i worker di servizio delle estensioni chiamati "script in background". Sebbene i service worker delle estensioni vengano eseguiti in background, chiamarli script in background è in qualche modo fuorviante perché implica funzionalità identiche. Le differenze sono descritte di seguito.
Modifiche dalle pagine di sfondo
I worker di servizio presentano una serie di differenze rispetto alle pagine in background.
- Funzionano al di fuori del thread principale, il che significa che non interferiscono con i contenuti dell'estensione.
- Hanno funzionalità speciali come l'intercettazione degli eventi di recupero sull'origine dell'estensione, come quelli da un popup della barra degli strumenti.
- Possono comunicare e interagire con altri contesti tramite l'interfaccia Client.
Modifiche da apportare
Dovrai apportare alcune modifiche al codice per tenere conto delle differenze tra il funzionamento degli script in background e dei service worker. Per iniziare, il modo in cui viene specificato un service worker nel file manifest è diverso da come vengono specificati gli script in background. Inoltre:
- Poiché non possono accedere al DOM o all'interfaccia
window
, dovrai spostare queste chiamate in un'API diversa o in un documento offscreen. - Gli ascoltatori di eventi non devono essere registrati in risposta alle promesse restituite o all'interno dei callback degli eventi.
- Poiché non sono compatibili con le versioni precedenti di
XMLHttpRequest()
, dovrai sostituire le chiamate a questa interfaccia con chiamate afetch()
. - Poiché terminano quando non sono in uso, devi mantenere gli stati dell'applicazione in modo permanente anziché affidarti a variabili globali. La terminazione dei worker di servizio può anche terminare i timer prima del completamento. Dovrai sostituirli con sveglie.
Questa pagina descrive queste attività in dettaglio.
Aggiorna il campo "background" nel file manifest
In Manifest V3, le pagine in background vengono sostituite da un service worker. Le modifiche al file manifest sono elencate di seguito.
- Sostituisci
"background.scripts"
con"background.service_worker"
inmanifest.json
. Tieni presente che il campo"service_worker"
accetta una stringa, non un array di stringhe. - Rimuovi
"background.persistent"
damanifest.json
.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Il campo "service_worker"
accetta una singola stringa. Il campo "type"
è necessario solo se utilizzi i moduli ES (utilizzando la parola chiave import
). Il suo valore sarà sempre "module"
. Per ulteriori informazioni, consulta la sezione Nozioni di base sui worker di servizio delle estensioni.
Spostare le chiamate DOM e window in un documento offscreen
Alcune estensioni devono accedere agli oggetti DOM e window senza aprire visivamente una nuova finestra o scheda. L'API Offscreen supporta questi casi d'uso aprendo e chiudendo i documenti non visualizzati pacchettizzati con l'estensione, senza interrompere l'esperienza utente. Ad eccezione della trasmissione dei messaggi, i documenti fuori schermo non condividono API con altri contesti di estensioni, ma funzionano come pagine web complete con cui le estensioni possono interagire.
Per utilizzare l'API Offscreen, crea un documento fuori schermo dal service worker.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Nel documento offscreen, esegui qualsiasi azione che in precedenza avresti eseguito in uno script in background. Ad esempio, puoi copiare il testo selezionato nella pagina host.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Comunicare tra i documenti offscreen e i worker del servizio di estensione utilizzando la trasmissione di messaggi.
Convertire localStorage in un altro tipo
L'interfaccia Storage
della piattaforma web (accessibile da window.localStorage
) non può essere utilizzata in un service worker. Per risolvere il problema, esegui una di queste due operazioni. Innanzitutto, puoi sostituirlo con chiamate a un altro meccanismo di archiviazione. Lo spazio dei nomi chrome.storage.local
è adatto alla maggior parte dei casi d'uso, ma sono disponibili altre opzioni.
Puoi anche spostare le relative chiamate in un documento fuori schermo. Ad esempio, per eseguire la migrazione dei dati precedentemente archiviati in localStorage
a un altro meccanismo:
- Crea un documento offscreen con una routine di conversione e un gestore
runtime.onMessage
. - Aggiungi una routine di conversione al documento fuori schermo.
- Nel worker del servizio dell'estensione, controlla
chrome.storage
per i tuoi dati. - Se i dati non vengono trovati, create un documento offscreen e chiama
runtime.sendMessage()
per avviare la routine di conversione. - Nell'handler
runtime.onMessage
che hai aggiunto al documento offscreen, chiama la routine di conversione.
Ci sono anche alcune sfumature nel funzionamento delle API di archiviazione web nelle estensioni. Scopri di più in Archiviazione e cookie.
Registra i listener in modo sincrono
La registrazione di un ascoltatore in modo asincrono (ad esempio all'interno di una promessa o di un callback) non è garantita in Manifest V3. Considera il seguente codice.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
Questo funziona con una pagina in background persistente perché la pagina è in esecuzione costante e non viene mai reinizializzata. In Manifest v3, il service worker verrà reinizializzato al momento dell'invio dell'evento. Ciò significa che quando si verifica l'evento, i listener non verranno registrati (poiché vengono aggiunti in modo asincrono) e l'evento verrà perso.
Sposta invece la registrazione del listener di eventi nel livello superiore dello script. In questo modo, Chrome potrà trovare e richiamare immediatamente il gestore dei clic dell'azione, anche se l'estensione non ha completato l'esecuzione della logica di avvio.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Sostituisci XMLHttpRequest() con fetch() globale
Impossibile chiamare XMLHttpRequest()
da un service worker, un'estensione o altro. Sostituisci le chiamate dallo script di background a XMLHttpRequest()
con chiamate a globale fetch()
.
const xhr = new XMLHttpRequest(); console.log('UNSENT', xhr.readyState); xhr.open('GET', '/api', true); console.log('OPENED', xhr.readyState); xhr.onload = () => { console.log('DONE', xhr.readyState); }; xhr.send(null);
const response = await fetch('https://www.example.com/greeting.json'') console.log(response.statusText);
Mantieni gli stati
I worker di servizio sono effimeri, il che significa che probabilmente verranno avviati, eseguiti e terminati ripetutamente durante la sessione del browser di un utente. Significa anche che i dati non sono immediatamente disponibili nelle variabili globali poiché il contesto precedente è stato rimosso. Per ovviare a questo problema, utilizza le API di archiviazione come fonte attendibile. Di seguito è riportato un esempio.
L'esempio seguente utilizza una variabile globale per memorizzare un nome. In un service worker, questa variabile potrebbe essere reimpostata più volte nel corso di una sessione del browser dell'utente.
let savedName = undefined; chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); chrome.browserAction.onClicked.addListener((tab) => { chrome.tabs.sendMessage(tab.id, { name: savedName }); });
Per Manifest V3, sostituisci la variabile globale con una chiamata all'API Storage.
chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { chrome.storage.local.set({ name }); } }); chrome.action.onClicked.addListener(async (tab) => { const { name } = await chrome.storage.local.get(["name"]); chrome.tabs.sendMessage(tab.id, { name }); });
Convertire i timer in sveglie
È comune utilizzare operazioni ritardate o periodiche con i metodi setTimeout()
o setInterval()
. Tuttavia, queste API possono non riuscire nei service worker perché i timer vengono annullati ogni volta che il service worker viene interrotto.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Utilizza invece l'API Alarms. Come per gli altri listener, i listener di allarmi devono essere registrati nel livello superiore dello script.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
Mantenere attivo il service worker
Per definizione, i service worker sono basati su eventi e vengono interrotti in caso di inattività. In questo modo, Chrome può ottimizzare le prestazioni e il consumo di memoria dell'estensione. Scopri di più nella nostra documentazione sul ciclo di vita dei worker di servizio. In casi eccezionali potrebbero essere necessarie misure aggiuntive per garantire che un service worker resti in vita per un periodo di tempo più lungo.
Mantenere attivo un worker di servizio fino al termine di un'operazione di lunga durata
Durante le operazioni dei worker di servizio a lunga esecuzione che non chiamano le API di estensioni, il worker di servizio potrebbe arrestarsi durante l'operazione. Ecco alcuni esempi:
- Una richiesta
fetch()
che potrebbe richiedere più di cinque minuti (ad es. un download di grandi dimensioni su una connessione potenzialmente scadente). - Un calcolo asincrono complesso che richiede più di 30 secondi.
Per estendere la durata del service worker in questi casi, puoi chiamare periodicamente un'API di estensione semplice per reimpostare il contatore del timeout. Tieni presente che questa opzione è riservata solo a casi eccezionali e nella maggior parte dei casi esiste un modo migliore e idiomatico della piattaforma per ottenere lo stesso risultato.
L'esempio seguente mostra una funzione di assistenza waitUntil()
che mantiene attivo il tuo service worker fino alla risoluzione di una determinata promessa:
async function waitUntil(promise) = {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Mantenere attivo un worker di servizio in modo continuo
In rari casi, è necessario estendere la durata a tempo indeterminato. Abbiamo identificato le imprese e l'istruzione come i principali casi d'uso e in particolare consentiamo loro di farlo, ma non supportiamo questa possibilità in generale. In queste circostanze eccezionali, è possibile mantenere attivo un worker di servizio chiamando periodicamente un'API di estensione banale. È importante notare che questo consiglio si applica solo alle estensioni in esecuzione su dispositivi gestiti per casi d'uso aziendali o scolastici. Non è consentito in altri casi e il team addetto alle estensioni di Chrome si riserva il diritto di prendere provvedimenti contro queste estensioni in futuro.
Utilizza il seguente snippet di codice per mantenere attivo il tuo service worker:
/**
* Tracks when a service worker was last alive and extends the service worker
* lifetime by writing the current time to extension storage every 20 seconds.
* You should still prepare for unexpected termination - for example, if the
* extension process crashes or your extension is manually stopped at
* chrome://serviceworker-internals.
*/
let heartbeatInterval;
async function runHeartbeat() {
await chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}
/**
* Starts the heartbeat interval which keeps the service worker alive. Call
* this sparingly when you are doing work which requires persistence, and call
* stopHeartbeat once that work is complete.
*/
async function startHeartbeat() {
// Run the heartbeat once at service worker startup.
runHeartbeat().then(() => {
// Then again every 20 seconds.
heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
});
}
async function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
/**
* Returns the last heartbeat stored in extension storage, or undefined if
* the heartbeat has never run before.
*/
async function getLastHeartbeat() {
return (await chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}