Migrazione a un service worker

Sostituzione di pagine di sfondo o di eventi con un service worker

Un service worker sostituisce lo sfondo o la pagina degli eventi dell'estensione per garantire che il codice in background rimanga fuori dal thread principale. In questo modo le estensioni possono essere eseguite solo quando necessario, risparmiando risorse.

Le pagine in background sono state una componente fondamentale delle estensioni fin dalla loro introduzione. In parole povere, le pagine in background offrono un ambiente indipendente da qualsiasi altra finestra o scheda. In questo modo le estensioni possono osservare gli eventi e intervenire in risposta.

In questa pagina vengono descritte le attività per la conversione delle pagine in background 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 script in background e service worker delle estensioni

In alcuni contesti, i service worker delle estensioni saranno chiamati "script in background". Sebbene i Service worker delle estensioni vengano eseguiti in background, chiamarli script in background è alquanto fuorviante in quanto suggerendo funzionalità identiche. Le differenze sono descritte di seguito.

Modifiche rispetto alle pagine in background

I Service worker presentano diverse differenze rispetto alle pagine in background.

  • Funzionano al di fuori del thread principale, ovvero 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. Innanzitutto, il modo in cui un service worker viene specificato nel file manifest è diverso dal modo in cui vengono specificati gli script in background. Inoltre:

  • Poiché non possono accedere al DOM o all'interfaccia window, dovrai trasferire queste chiamate a un'API diversa o a un documento fuori schermo.
  • I listener di eventi non devono essere registrati in risposta a promesse restituite o a callback di eventi interni.
  • Poiché non sono compatibili con le versioni precedenti di XMLHttpRequest(), dovrai sostituire le chiamate a questa interfaccia con quelle a fetch().
  • Poiché terminano quando non sono in uso, dovrai mantenere gli stati dell'applicazione anziché fare affidamento su variabili globali. I Service worker di terminazione possono anche terminare i timer prima del completamento. Dovrai sostituirli con sveglie.

In questa pagina vengono descritte dettagliatamente queste attività.

Aggiorna il campo "in background" nel file manifest

In Manifest V3, le pagine in background sono sostituite da un service worker. Le modifiche al file manifest sono elencate di seguito.

  • Sostituisci "background.scripts" con "background.service_worker" nella colonna manifest.json. Tieni presente che il campo "service_worker" accetta una stringa e 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 moduli ES (con la parola chiave import). Il suo valore sarà sempre "module". Per ulteriori informazioni, consulta Nozioni di base sul service worker delle estensioni

Spostare DOM e chiamate finestra in un documento fuori schermo

Alcune estensioni richiedono l'accesso agli oggetti DOM e delle finestre senza dover aprire visivamente una nuova finestra o scheda. L'API Offscreen supporta questi casi d'uso aprendo e chiudendo i documenti non visualizzati pacchettizzati con estensione, senza compromettere l'esperienza utente. Ad eccezione della trasmissione dei 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 un'azione che avresti precedentemente 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');

Comunica tra documenti fuori schermo e service worker delle estensioni utilizzando la trasmissione 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 di queste due operazioni. Innanzitutto, puoi sostituirlo con chiamate a un altro meccanismo di archiviazione. Lo spazio dei nomi chrome.storage.local è in grado di gestire 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 precedentemente archiviati in localStorage in un altro meccanismo:

  1. Crea un documento fuori schermo con una routine di conversione e un gestore runtime.onMessage.
  2. Aggiungi una routine di conversione al documento fuori schermo.
  3. Nel service worker dell'estensione, seleziona chrome.storage per vedere 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.

Ci sono anche alcune sfumature nel funzionamento delle API di archiviazione web nelle estensioni. Scopri di più in Spazio di archiviazione e cookie.

Registra i listener in modo sincrono

Non è garantito che un listener venga registrato in modo asincrono (ad esempio all'interno di una promessa o di un callback) 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 permanente perché la pagina è in esecuzione costante e non viene mai reinizializzata. In Manifest V3, il service worker verrà reinizializzato all'invio dell'evento. Ciò significa che, quando l'evento viene attivato, i listener non verranno registrati (poiché vengono aggiunti in modo asincrono) e l'evento non sarà rilevato.

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 });
});

Sostituisci XMLHttpRequest() con il fetch() globale

Non è possibile chiamare XMLHttpRequest() da un service worker, da un'estensione o in altro modo. Sostituisci le chiamate dallo script in background a XMLHttpRequest() con le chiamate globali 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 stati

I Service worker sono temporanei, il che significa che probabilmente verranno avviati, eseguiti e arrestati ripetutamente durante la sessione del browser di un utente. Significa inoltre che i dati non sono immediatamente disponibili nelle variabili globali poiché il contesto precedente è stato eliminato. Per aggirare il problema, usa le API di archiviazione come fonte attendibile. Un esempio mostra come eseguire questa operazione.

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.

Script in 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 });
});

Converti i timer in sveglie

È comune l'uso di operazioni ritardate o periodiche con i metodi setTimeout() o setInterval(). Queste API possono tuttavia avere esito negativo nei service worker, perché i timer vengono annullati ogni volta che il service worker viene terminato.

Script in background Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

Utilizza invece l'API Avvisi. Come per altri listener, i listener di sveglie devono essere registrati al 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(),
  });
});

Mantieni attivo il service worker

I Service worker sono per definizione basati sugli eventi e termineranno per inattività. In questo modo Chrome può ottimizzare le prestazioni e il consumo di memoria della tua estensione. Scopri di più nella nostra documentazione relativa al ciclo di vita dei service worker. Alcuni casi eccezionali potrebbero richiedere misure aggiuntive per garantire che un service worker rimanga attivo per un periodo di tempo più lungo.

Mantieni attivo un service worker fino al completamento di un'operazione a lunga esecuzione

Durante le operazioni dei service worker a lunga esecuzione che non chiamano le API delle estensioni, quest'ultimo potrebbe arrestarsi durante il funzionamento. Tra gli esempi possibili:

  • Una richiesta fetch() che potrebbe richiedere più di cinque minuti (ad esempio un download di grandi dimensioni su una connessione potenzialmente debole).
  • 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 di timeout. Tieni presente che questa procedura è 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 helper waitUntil() che mantiene attivo il service worker fino a quando una determinata promessa non viene risolta:

async function waitUntil(promise) = {
  const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
  try {
    await promise;
  } finally {
    clearInterval(keepAlive);
  }
}

waitUntil(someExpensiveCalculation());

Mantieni attivo un service worker

In rari casi, è necessario estendere la durata a tempo indeterminato. Abbiamo identificato l'impresa e l'istruzione come i principali casi d'uso e questo è consentito nello specifico, ma non supportiamo questa soluzione in generale. In queste circostanze eccezionali, è possibile mantenere attivo un service worker chiamando periodicamente un'API di estensione banale. È importante tenere presente che questo consiglio si applica solo alle estensioni eseguite sui dispositivi gestiti per casi d'uso aziendali o scolastici. Non è consentito in altri casi e il team responsabile delle estensioni di Chrome si riserva il diritto di prendere provvedimenti in merito a 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'];
}