Migrazione a un service worker

Sostituire le pagine di sfondo o degli eventi con un service worker

Un service worker sostituisce la pagina di sfondo o degli eventi dell'estensione per garantire che il codice di sfondo 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 povere, le pagine di sfondo forniscono un ambiente indipendente da qualsiasi altra finestra o scheda. Ciò consente alle estensioni di osservare gli eventi e agire in risposta a essi.

Questa pagina descrive le attività per la conversione delle pagine di sfondo in service worker delle estensioni. Per ulteriori informazioni 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 gli script di sfondo e i service worker delle estensioni

In alcuni contesti, i service worker delle estensioni vengono chiamati "script di sfondo". Sebbene i service worker delle estensioni vengano eseguiti in background, chiamarli script di sfondo è in qualche modo fuorviante perché implica funzionalità identiche. Le differenze sono descritte di seguito.

Modifiche rispetto alle pagine di sfondo

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 provenienti da un popup della barra degli strumenti.
  • Possono comunicare e interagire con altri contesti tramite l'interfaccia Clients.

Modifiche da 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 degli script di sfondo. Inoltre:

  • Poiché non possono accedere al DOM o all'interfaccia window, dovrai spostare queste chiamate in un'API diversa 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 a fetch().
  • Poiché terminano quando non sono in uso, dovrai rendere persistenti gli stati dell'applicazione anziché fare affidamento sulle variabili globali. La terminazione dei service worker può anche interrompere i timer prima che siano completati. Dovrai sostituirli con gli allarmi.

Questa pagina descrive queste attività in dettaglio.

Aggiornare il campo "background" nel file manifest

In Manifest V3, le pagine di sfondo vengono sostituite da un service worker. Di seguito sono elencate le modifiche al file manifest.

  • Sostituisci "background.scripts" con "background.service_worker" in manifest.json. Tieni presente che il campo "service_worker" accetta una stringa, non un array di stringhe.
  • Rimuovi "background.persistent" da manifest.json.
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifest V3
{
  ...
  "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 Nozioni di base sui service worker delle estensioni.

Spostare le chiamate DOM e window in un documento fuori schermo

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 inclusi nell'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 fuori schermo dal service worker.

chrome.offscreen.createDocument({
  url: chrome.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 di sfondo. Ad esempio, puoi copiare il testo selezionato nella pagina host.

let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');

Comunica tra i documenti fuori schermo e i service worker delle estensioni utilizzando il passaggio 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 questo problema, puoi fare una delle due cose seguenti. Innanzitutto, puoi sostituirla con chiamate a un altro meccanismo di archiviazione. Lo spazio dei nomi chrome.storage.local soddisferà la maggior parte dei casi d'uso, ma sono disponibili altre opzioni.

Puoi anche spostare le chiamate in un documento fuori schermo. Ad esempio, per eseguire la migrazione dei dati archiviati in precedenza in localStorage a un altro meccanismo:

  1. Crea un documento fuori schermo con una routine di conversione e un runtime.onMessage gestore.
  2. Aggiungi una routine di conversione al documento fuori schermo.
  3. Nel service worker dell'estensione, controlla chrome.storage per i tuoi dati.
  4. Se i dati non vengono trovati, crea un documento fuori schermo e chiama runtime.sendMessage() per avviare la routine di conversione.
  5. Nel gestore runtime.onMessage che hai aggiunto al documento fuori schermo, chiama la routine di conversione.

Esistono anche alcune sfumature relative al funzionamento delle API di archiviazione web nelle estensioni. Scopri di più in Archiviazione e cookie.

Registrare i listener in modo sincrono

La registrazione asincrona di un listener (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 di sfondo persistente perché la pagina è in esecuzione costante 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 verranno registrati (poiché vengono aggiunti in modo asincrono) e l'evento verrà perso.

Sposta invece la registrazione del listener di eventi al livello superiore dello script. In questo modo, Chrome sarà in grado di trovare e richiamare immediatamente il gestore dei clic dell'azione, anche se l'estensione non ha terminato l'esecuzione della logica di avvio.

chrome.action.onClicked.addListener(handleActionClick);

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.action.setBadgeText({ text: badgeText });
});

Sostituire XMLHttpRequest() con fetch() globale

XMLHttpRequest() non può essere chiamato da un service worker, un'estensione o altro. Sostituisci le chiamate da script di sfondo a XMLHttpRequest() con chiamate a globale fetch().

XMLHttpRequest()
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);
fetch()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

Rendere persistenti gli stati

I service worker sono effimeri, il che significa che è probabile che vengano 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 perché il contesto precedente è stato eliminato. Per ovviare a questo problema, utilizza le API di archiviazione come fonte di riferimento. Un esempio mostrerà come farlo.

L'esempio seguente utilizza una variabile globale per memorizzare un nome. In un service worker, questa variabile potrebbe essere reimpostata più volte nel corso della sessione del browser di un utente.

Script di sfondo di Manifest V2
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.

Service worker di Manifest V3
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 allarmi

È 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.

Script di sfondo di Manifest V2
// 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.

Service worker di Manifest V3
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

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 dell'estensione. Scopri di più nella documentazione sul ciclo di vita dei service worker. In casi eccezionali, potrebbero essere necessarie misure aggiuntive per garantire che un service worker rimanga attivo per un periodo di tempo più lungo.

Mantenere attivo un service worker fino al completamento di 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 essere arrestato a metà dell'operazione. Ecco alcuni esempi:

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 operazione è riservata solo a casi eccezionali e nella maggior parte delle situazioni esiste in genere un modo migliore e idiomatico della piattaforma per ottenere lo stesso risultato.

L'esempio seguente mostra una funzione helper waitUntil() che mantiene attivo il 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 service worker in modo continuo

In rari casi, è necessario estendere la durata all'infinito. Abbiamo identificato le aziende e gli istituti scolastici come i casi d'uso più importanti e lo consentiamo in modo specifico, ma non lo supportiamo in generale. In queste circostanze eccezionali, è possibile mantenere attivo un service worker chiamando periodicamente un'API di estensione banale. È importante notare che questa raccomandazione 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 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 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'];
}