Sostituzione di pagine di sfondo o di eventi con un service worker
Un service worker sostituisce la pagina degli eventi o di background dell'estensione per garantire che il codice di background rimanga fuori dal thread principale. In questo modo, le estensioni vengono eseguite solo quando necessario, risparmiando risorse.
Le pagine di sfondo sono un componente fondamentale delle estensioni sin dalla loro introduzione. In parole semplici, le pagine in background forniscono un ambiente indipendente da qualsiasi altra finestra o scheda. Ciò consente alle estensioni di osservare e agire in risposta agli eventi.
Questa pagina descrive le attività per convertire le pagine di sfondo in service worker 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 di sfondo e service worker dell'estensione
In alcuni contesti vedrai i service worker delle estensioni chiamati "script di sfondo". Sebbene i service worker delle estensioni vengano eseguiti in background, chiamarli script di background è un po' fuorviante perché implica funzionalità identiche. Le differenze sono descritte di seguito.
Modifiche dalle pagine in background
I service worker presentano una serie di differenze rispetto alle pagine di sfondo.
- Funzionano al di fuori del thread principale, il che significa che non interferiscono con i contenuti delle estensioni.
- Hanno funzionalità speciali, come l'intercettazione degli eventi di recupero sull'origine dell'estensione, ad esempio quelli di un popup della barra degli strumenti.
- Possono comunicare e interagire con altri contesti tramite l'interfaccia Client.
Modifiche che dovrai apportare
Dovrai apportare alcune modifiche al codice per tenere conto delle differenze tra il funzionamento degli script di sfondo e dei service worker. Per iniziare, il modo in cui viene specificato un service worker nel file manifest è diverso da quello in cui vengono specificati gli script di sfondo. Inoltre:
- Poiché non possono accedere al DOM o all'interfaccia
window, devi spostare queste chiamate in un'altra API o in un documento fuori schermo. - I listener di eventi non devono essere registrati in risposta a 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 rendere persistenti gli stati dell'applicazione anziché fare affidamento sulle variabili globali. L'interruzione dei service worker può anche terminare i timer prima del completamento. Dovrai sostituirli con le sveglie.
Questa pagina descrive in dettaglio queste attività.
Aggiorna il campo "background" nel manifest
In Manifest V3, le pagine di sfondo vengono sostituite da un service worker. Le modifiche al 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. Avrai bisogno del campo "type" solo se utilizzi i moduli ES (utilizzando la parola chiave import). Il suo valore sarà sempre "module". Per saperne di più, consulta Nozioni di base sui service worker delle estensioni.
Spostare le chiamate DOM e finestra in un documento fuori schermo
Alcune estensioni devono accedere agli oggetti DOM e finestra senza aprire visivamente una nuova finestra o scheda. L'API Offscreen supporta questi casi d'uso aprendo e chiudendo i documenti non visualizzati inclusi nel pacchetto dell'estensione, senza interrompere l'esperienza utente. Ad eccezione del passaggio di messaggi, i documenti fuori schermo non condividono le API con altri contesti di estensione, ma funzionano come pagine web complete con cui le estensioni possono interagire.
Per utilizzare l'API Offscreen, crea un documento offscreen dal service worker.
browser.offscreen.createDocument({
url: browser.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Nel documento fuori schermo esegui qualsiasi azione che avresti eseguito in precedenza 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 fuori schermo e i service worker dell'estensione utilizzando il passaggio di messaggi.
Converti 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 delle seguenti operazioni. Innanzitutto, puoi sostituirlo con chiamate a un altro meccanismo di archiviazione. Lo spazio dei nomi browser.storage.local è adatto alla maggior parte dei casi d'uso, ma sono disponibili altre opzioni.
Puoi anche spostare le sue chiamate in un documento fuori dallo schermo. Ad esempio, per eseguire la migrazione dei dati precedentemente archiviati in localStorage a un altro meccanismo:
- Crea un documento fuori schermo con una routine di conversione e un gestore
runtime.onMessage. - Aggiungi una routine di conversione al documento fuori schermo.
- Nel controllo del service worker dell'estensione
browser.storage, cerca i tuoi dati. - Se i tuoi dati non vengono trovati, crea un documento fuori schermo e chiama il numero
runtime.sendMessage()per avviare la routine di conversione. - Nel gestore
runtime.onMessageche hai aggiunto al documento fuori schermo, chiama la routine di conversione.
Esistono anche alcune sfumature nel funzionamento delle API di archiviazione web nelle estensioni. Scopri di più in Spazio di archiviazione e cookie.
Registrare i listener in modo sincrono
La registrazione di un listener in modo asincrono (ad esempio all'interno di una promessa o di un callback) non è garantita in Manifest V3. Considera il seguente codice.
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.browserAction.setBadgeText({ text: badgeText });
browser.browserAction.onClicked.addListener(handleActionClick);
});
Funziona con una pagina di sfondo persistente perché la pagina è in esecuzione continua e non viene mai reinizializzata. In Manifest V3, il service worker verrà reinizializzato quando l'evento viene inviato. Ciò significa che quando l'evento viene attivato, i listener non vengono registrati (in quanto vengono aggiunti in modo asincrono) e l'evento viene perso.
Sposta invece la registrazione del listener di eventi al 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 terminato l'esecuzione della logica di avvio.
browser.action.onClicked.addListener(handleActionClick);
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.action.setBadgeText({ text: badgeText });
});
Sostituisci XMLHttpRequest() con fetch() globale
XMLHttpRequest() non può essere chiamato da un service worker, un'estensione o in altro modo. Sostituisci le chiamate dallo script di sfondo a XMLHttpRequest() con le chiamate a fetch() globale.
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);
Stati persistenti
I service worker 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 eliminato. Per risolvere questo problema, utilizza le API Storage come fonte attendibile. Un esempio mostrerà come fare.
L'esempio seguente utilizza una variabile globale per archiviare un nome. In un service worker, questa variabile potrebbe essere reimpostata più volte nel corso della sessione del browser di un utente.
let savedName = undefined; browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); browser.browserAction.onClicked.addListener((tab) => { browser.tabs.sendMessage(tab.id, { name: savedName }); });
Per Manifest V3, sostituisci la variabile globale con una chiamata all'API Storage.
browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { browser.storage.local.set({ name }); } }); browser.action.onClicked.addListener(async (tab) => { const { name } = await browser.storage.local.get(["name"]); browser.tabs.sendMessage(tab.id, { name }); });
Convertire i timer in sveglie
È comune utilizzare operazioni ritardate o periodiche utilizzando i metodi setTimeout() o setInterval(). Tuttavia, queste API possono non funzionare nei service worker perché i timer vengono annullati ogni volta che il service worker viene terminato.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { browser.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Utilizza invece l'API Alarms. Come per gli altri listener, i listener di allarme devono essere registrati nel livello superiore dello script.
async function startAlarm(name, duration) { await browser.alarms.create(name, { delayInMinutes: 3 }); } browser.alarms.onAlarm.addListener(() => { browser.action.setIcon({ path: getRandomIconPath(), }); });
Mantieni attivo il service worker
I service worker sono per definizione basati sugli eventi e terminano in caso di inattività. In questo modo, Chrome può ottimizzare le prestazioni e il consumo di memoria della tua estensione. Scopri di più nella nostra documentazione sul ciclo di vita dei service worker. I casi eccezionali potrebbero richiedere misure aggiuntive per garantire che un service worker rimanga attivo più a lungo.
Mantenere attivo un service worker finché non viene completata un'operazione a lunga esecuzione
Durante le operazioni a lunga esecuzione dei service worker che non chiamano le API delle estensioni, il service worker potrebbe arrestarsi a metà dell'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 scarsa). - 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 banale per reimpostare il contatore del timeout. Tieni presente che questa opzione è riservata solo a casi eccezionali e che nella maggior parte delle situazioni esiste un modo migliore e più idiomatico per ottenere lo stesso risultato.
L'esempio seguente mostra una funzione helper waitUntil() che mantiene attivo il service worker finché una determinata promessa non viene risolta:
async function waitUntil(promise) {
const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Mantenere in esecuzione continua un service worker
In rari casi, è necessario estendere la durata a tempo indeterminato. Abbiamo identificato l'ambito aziendale e quello dell'istruzione come i casi d'uso più importanti e li consentiamo in modo specifico, ma non supportiamo questa funzionalità in generale. In queste circostanze eccezionali, è possibile mantenere attivo un service worker 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 didattici. Non è consentito in altri casi e il team delle estensioni di Chrome si riserva il diritto di intraprendere azioni contro queste estensioni in futuro.
Utilizza il seguente snippet di codice per mantenere attivo il 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 browser.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 browser.storage.local.get('last-heartbeat'))['last-heartbeat'];
}