Migrazione a un service worker

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 di sfondo 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 e intervenire in risposta agli eventi.

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 worker di servizio delle estensioni

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 nell'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 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 cominciare, il modo in cui un service worker viene specificato 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 a fetch().
  • Poiché vengono interrotti quando non sono in uso, dovrai mantenere gli stati dell'applicazione anziché fare affidamento su 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" in manifest.json. Tieni presente che il campo "service_worker" accetta una stringa, non un array di stringhe.
  • Rimuovi "background.persistent" dal 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 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 del passaggio di messaggi, i documenti offscreen 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 offscreen dal servizio 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, puoi procedere in uno dei seguenti modi. 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 offscreen. Ad esempio, per eseguire la migrazione dei dati precedentemente archiviati in localStorage a un altro meccanismo:

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

Esistono anche alcune sfumature sul funzionamento delle API di archiviazione web nelle estensioni. Scopri di più in Spazio di 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 dell'ascoltatore 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 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

XMLHttpRequest() non può essere chiamato da un service worker, da un'estensione o in altro modo. Sostituisci le chiamate dal tuo script di background a XMLHttpRequest() con chiamate a global 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);

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 worker di servizio, questa variabile potrebbe essere reimpostata più volte nel corso della sessione del browser di un utente.

Script di background 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 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 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 terminato.

Script di sfondo 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 ascoltatori, gli ascoltatori di alarme devono essere registrati nel livello superiore dello script.

Service worker 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

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. I casi eccezionali potrebbero richiedere misure aggiuntive per garantire che un worker di servizio rimanga attivo 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 servizio 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 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 aziende e l'istruzione come i casi d'uso più importanti e lo consentiamo specificamente in questi settori, ma non 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 delle estensioni di Chrome si riserva il diritto di intervenire in futuro su queste estensioni.

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'];
}