Migreren naar een servicemedewerker

Achtergrond- of evenementpagina's vervangen door een servicemedewerker

Een servicemedewerker vervangt de achtergrond- of gebeurtenispagina van de extensie om ervoor te zorgen dat de achtergrondcode buiten de rode draad blijft. Hierdoor kunnen extensies alleen worden uitgevoerd wanneer dat nodig is, waardoor bronnen worden bespaard.

Achtergrondpagina's vormen sinds de introductie ervan een fundamenteel onderdeel van extensies. Simpel gezegd: achtergrondpagina's bieden een omgeving die onafhankelijk is van enig ander venster of tabblad. Hierdoor kunnen extensies observeren en reageren op gebeurtenissen.

Op deze pagina worden taken beschreven voor het converteren van achtergrondpagina's naar extensieservicemedewerkers. Voor meer informatie over extensieservicemedewerkers in het algemeen raadpleegt u de tutorial Gebeurtenissen afhandelen met servicemedewerkers en de sectie Over extensieservicemedewerkers .

Verschillen tussen achtergrondscripts en extensieservicemedewerkers

In sommige contexten zie je extensieservicemedewerkers die 'achtergrondscripts' worden genoemd. Hoewel extensieservicemedewerkers op de achtergrond actief zijn, is het enigszins misleidend om ze achtergrondscripts te noemen, omdat dit identieke mogelijkheden impliceert. De verschillen worden hieronder beschreven.

Wijzigingen ten opzichte van achtergrondpagina's

Servicemedewerkers heeft een aantal verschillen met achtergrondpagina's.

  • Ze functioneren buiten de hoofdlijn, wat betekent dat ze de inhoud van de extensie niet verstoren.
  • Ze hebben speciale mogelijkheden, zoals het onderscheppen van ophaalgebeurtenissen op de oorsprong van de extensie, zoals die van een pop-up op de werkbalk.
  • Ze kunnen communiceren en interacteren met andere contexten via de Clients-interface .

Wijzigingen die u moet aanbrengen

U moet een paar codeaanpassingen maken om rekening te houden met de verschillen tussen de manier waarop achtergrondscripts en servicemedewerkers werken. Om te beginnen verschilt de manier waarop een servicemedewerker in het manifestbestand wordt gespecificeerd van de manier waarop achtergrondscripts worden gespecificeerd. Aanvullend:

  • Omdat ze geen toegang hebben tot de DOM of de window , moet u dergelijke aanroepen naar een andere API of naar een document buiten het scherm verplaatsen.
  • Gebeurtenislisteners mogen niet worden geregistreerd als reactie op geretourneerde beloften of terugbellen van interne gebeurtenissen.
  • Omdat ze niet achterwaarts compatibel zijn met XMLHttpRequest() moet je de oproepen naar deze interface vervangen door oproepen naar fetch() .
  • Omdat ze worden beëindigd wanneer ze niet worden gebruikt, moet u de toepassingsstatus behouden in plaats van te vertrouwen op globale variabelen. Het beëindigen van servicemedewerkers kan ook timers beëindigen voordat ze zijn voltooid. Je moet ze vervangen door alarmen.

Op deze pagina worden deze taken gedetailleerd beschreven.

Werk het veld 'achtergrond' in het manifest bij

In Manifest V3 worden achtergrondpagina's vervangen door een servicemedewerker . De manifeste wijzigingen worden hieronder vermeld.

  • Vervang "background.scripts" door "background.service_worker" in manifest.json . Houd er rekening mee dat het veld "service_worker" een string bevat, en geen array van strings.
  • Verwijder "background.persistent" uit manifest.json .
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

Het veld "service_worker" bevat één enkele tekenreeks. Het veld "type" heeft u alleen nodig als u ES-modules gebruikt (met behulp van het trefwoord import ). De waarde ervan zal altijd "module" zijn. Zie Basisbeginselen van de servicemedewerker voor uitbreiding voor meer informatie

Verplaats DOM- en vensteroproepen naar een document buiten het scherm

Sommige extensies hebben toegang nodig tot de DOM- en vensterobjecten zonder visueel een nieuw venster of tabblad te openen. De Offscreen API ondersteunt deze gebruiksscenario's door niet-weergegeven documenten met extensie te openen en te sluiten, zonder de gebruikerservaring te verstoren. Behalve het doorgeven van berichten delen offscreen-documenten geen API's met andere extensiecontexten, maar functioneren ze als volledige webpagina's waarmee extensies kunnen communiceren.

Om de Offscreen API te gebruiken, maakt u een offscreen document van de servicemedewerker.

chrome.offscreen.createDocument({
  url: chrome.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

Voer in het offscreen-document elke actie uit die u voorheen in een achtergrondscript zou hebben uitgevoerd. U kunt bijvoorbeeld de tekst kopiëren die op de hostpagina is geselecteerd.

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

Communiceer tussen documenten buiten het scherm en medewerkers van de uitbreidingsservice via het doorgeven van berichten .

Converteer localStorage naar een ander type

De Storage van het webplatform (toegankelijk via window.localStorage ) kan niet worden gebruikt in een servicemedewerker. Om dit aan te pakken, kunt u een van de volgende twee dingen doen. Ten eerste kunt u het vervangen door oproepen naar een ander opslagmechanisme. De naamruimte chrome.storage.local is geschikt voor de meeste gebruiksscenario's, maar er zijn ook andere opties beschikbaar.

U kunt de oproepen ook verplaatsen naar een document buiten het scherm . Om bijvoorbeeld gegevens die eerder in localStorage zijn opgeslagen naar een ander mechanisme te migreren:

  1. Maak een document buiten het scherm met een conversieroutine en een runtime.onMessage handler.
  2. Voeg een conversieroutine toe aan het offscreen-document.
  3. Controleer in de extensieservicemedewerker chrome.storage op uw gegevens.
  4. Als uw gegevens niet worden gevonden, maakt u een document buiten het scherm en roept u runtime.sendMessage() aan om de conversieroutine te starten.
  5. Roep de conversieroutine aan in de runtime.onMessage handler die u aan het offscreen-document hebt toegevoegd.

Er zijn ook enkele nuances in de manier waarop webopslag-API's in extensies werken. Lees meer in Opslag en cookies .

Registreer luisteraars synchroon

Het is niet gegarandeerd dat het asynchroon registreren van een luisteraar (bijvoorbeeld binnen een belofte of terugbelactie) werkt in Manifest V3. Beschouw de volgende code.

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

Dit werkt met een persistente achtergrondpagina omdat de pagina voortdurend actief is en nooit opnieuw wordt geïnitialiseerd. In Manifest V3 wordt de servicemedewerker opnieuw geïnitialiseerd wanneer de gebeurtenis wordt verzonden. Dit betekent dat wanneer de gebeurtenis plaatsvindt, de luisteraars niet worden geregistreerd (aangezien ze asynchroon worden toegevoegd) en dat de gebeurtenis wordt gemist.

Verplaats in plaats daarvan de registratie van de gebeurtenislistener naar het hoogste niveau van uw script. Dit zorgt ervoor dat Chrome de klikhandler van uw actie onmiddellijk kan vinden en aanroepen, zelfs als uw extensie nog niet klaar is met het uitvoeren van de opstartlogica.

chrome.action.onClicked.addListener(handleActionClick);

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

Vervang XMLHttpRequest() door global fetch()

XMLHttpRequest() kan niet worden aangeroepen vanuit een servicemedewerker, extensie of anderszins. Vervang aanroepen van uw achtergrondscript naar XMLHttpRequest() door aanroepen naar 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);
ophalen()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

Aanhoudende staten

Servicemedewerkers zijn kortstondig, wat betekent dat ze waarschijnlijk herhaaldelijk zullen starten, uitvoeren en eindigen tijdens de browsersessie van een gebruiker. Het betekent ook dat gegevens niet onmiddellijk beschikbaar zijn in globale variabelen sinds de vorige context werd afgebroken. Om dit te omzeilen, gebruikt u opslag-API's als bron van waarheid. Een voorbeeld zal laten zien hoe u dit kunt doen.

In het volgende voorbeeld wordt een globale variabele gebruikt om een ​​naam op te slaan. Bij een servicemedewerker kan deze variabele tijdens de browsersessie van een gebruiker meerdere keren opnieuw worden ingesteld.

Manifest V2-achtergrondscript
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 });
});

Voor Manifest V3 vervangt u de globale variabele door een aanroep naar Storage API .

Duidelijke V3-servicemedewerker
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 });
});

Converteer timers naar alarmen

Het is gebruikelijk om vertraagde of periodieke bewerkingen te gebruiken met behulp van de methoden setTimeout() of setInterval() . Deze API's kunnen echter mislukken bij servicemedewerkers, omdat de timers worden geannuleerd wanneer de servicemedewerker wordt beëindigd.

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

Gebruik in plaats daarvan de Alarmen-API . Net als andere luisteraars moeten alarmluisteraars op het hoogste niveau van uw script worden geregistreerd.

Duidelijke V3-servicemedewerker
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

chrome.alarms.onAlarm.addListener(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
});

Houd de servicemedewerker in leven

Servicemedewerkers zijn per definitie gebeurtenisgestuurd en zullen eindigen bij inactiviteit. Op deze manier kan Chrome de prestaties en het geheugengebruik van uw extensie optimaliseren. Lees meer in onze levenscyclusdocumentatie voor servicemedewerkers . Uitzonderlijke gevallen kunnen aanvullende maatregelen vereisen om ervoor te zorgen dat een servicemedewerker langer in leven blijft.

Houd een servicemedewerker in leven totdat een langlopende operatie is voltooid

Tijdens langlopende service worker-bewerkingen waarbij geen uitbreidings-API's worden aangeroepen, kan de service worker halverwege de bewerking worden afgesloten. Voorbeelden zijn onder meer:

  • Een fetch() -verzoek dat mogelijk langer dan vijf minuten duurt (bijvoorbeeld een grote download via een mogelijk slechte verbinding).
  • Een complexe asynchrone berekening die meer dan 30 seconden duurt.

Om in deze gevallen de levensduur van de servicemedewerker te verlengen, kunt u periodiek een triviale uitbreidings-API aanroepen om de time-outteller opnieuw in te stellen. Houd er rekening mee dat dit alleen voor uitzonderlijke gevallen is gereserveerd en dat er in de meeste situaties meestal een betere, platformidiomatische manier is om hetzelfde resultaat te bereiken.

In het volgende voorbeeld ziet u een helperfunctie waitUntil() die uw servicemedewerker in leven houdt totdat een bepaalde belofte wordt opgelost:

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

waitUntil(someExpensiveCalculation());

Houd een servicemedewerker continu in leven

In zeldzame gevallen is het nodig om de levensduur voor onbepaalde tijd te verlengen. We hebben het bedrijfsleven en het onderwijs geïdentificeerd als de grootste gebruiksscenario's, en we staan ​​dit daar specifiek toe, maar we ondersteunen dit in het algemeen niet. In deze uitzonderlijke omstandigheden kan het in leven houden van een servicemedewerker worden bereikt door periodiek een triviale uitbreidings-API aan te roepen. Het is belangrijk op te merken dat deze aanbeveling alleen van toepassing is op extensies die worden uitgevoerd op beheerde apparaten voor gebruik in het bedrijfsleven of in het onderwijs. In andere gevallen is het niet toegestaan ​​en het Chrome-extensieteam behoudt zich het recht voor om in de toekomst actie te ondernemen tegen deze extensies.

Gebruik het volgende codefragment om uw servicemedewerker in leven te houden:

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