Achtergrond- of gebeurtenispagina's vervangen door een servicemedewerker
Een servicemedewerker vervangt de achtergrond- of gebeurtenispagina van de extensie om ervoor te zorgen dat de achtergrondcode buiten de hoofdthread blijft. Hierdoor worden extensies alleen uitgevoerd als dat nodig is, waardoor bronnen worden bespaard.
Achtergrondpagina's zijn sinds de introductie van extensies een fundamenteel onderdeel van extensies. Eenvoudig gezegd bieden achtergrondpagina's een omgeving die onafhankelijk is van andere vensters of tabbladen. Hiermee kunnen extensies gebeurtenissen observeren en erop reageren.
Op deze pagina worden taken beschreven om achtergrondpagina's te converteren naar extensie-serviceworkers. Ga voor meer informatie over service workers voor extensies naar de tutorial Gebeurtenissen verwerken met service workers en het gedeelte Over service workers voor extensies.
Verschillen tussen achtergrondscripts en serviceworkers voor extensies
In sommige contexten worden extensie-serviceworkers 'achtergrondscripts' genoemd. Hoewel extensie-serviceworkers op de achtergrond worden uitgevoerd, is het enigszins misleidend om ze achtergrondscripts te noemen, omdat dit impliceert dat ze dezelfde mogelijkheden hebben. De verschillen worden hieronder beschreven.
Wijzigingen van achtergrondpagina's
Service workers verschillen op een aantal punten van achtergrondpagina's.
- Ze werken buiten de hoofdthread, wat betekent dat ze de extensiecontent niet verstoren.
- Ze hebben speciale mogelijkheden, zoals het onderscheppen van ophaalgebeurtenissen op de oorsprong van de extensie, zoals die van een pop-up van de werkbalk.
- Ze kunnen communiceren en interactie hebben met andere contexten via de Clients-interface.
Wijzigingen die u moet aanbrengen
Je moet een paar codeaanpassingen aanbrengen om rekening te houden met de verschillen tussen de manier waarop achtergrondscripts en servicemedewerkers werken. Om te beginnen is de manier waarop een servicemedewerker wordt gespecificeerd in het manifestbestand anders dan de manier waarop achtergrondscripts worden gespecificeerd. Bovendien geldt het volgende:
- Omdat ze geen toegang hebben tot de DOM of de
window-interface, moet je dergelijke aanroepen verplaatsen naar een andere API of naar een document buiten het scherm. - Event-listeners mogen niet worden geregistreerd als reactie op geretourneerde beloften of in gebeurtenis-callbacks.
- Omdat ze niet compatibel zijn met eerdere versies van
XMLHttpRequest(), moet u aanroepen naar deze interface vervangen door aanroepen naarfetch(). - Omdat ze worden beëindigd als ze niet worden gebruikt, moet je de app-statussen behouden in plaats van te vertrouwen op globale variabelen. Als je serviceworkers beëindigt, kunnen timers ook worden beëindigd voordat ze zijn voltooid. Je moet ze vervangen door wekkers.
Op deze pagina worden deze taken gedetailleerd beschreven.
Het veld background in het manifest updaten
In Manifest V3 worden achtergrondpagina's vervangen door een service worker. De manifestwijzigingen staan hieronder vermeld.
- Vervang
"background.scripts"door"background.service_worker"in demanifest.json. Het veld"service_worker"vereist een tekenreeks, geen matrix van tekenreeksen. - Verwijder
"background.persistent"uit demanifest.json.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Het veld "service_worker" neemt één tekenreeks. U heeft het veld "type" alleen nodig als u ES-modules gebruikt (met het zoekwoord import). De waarde is altijd "module". Ga naar Basisprincipes van serviceworkers voor extensies voor meer informatie.
DOM- en vensteraanroepen verplaatsen 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 use cases door niet-getoonde documenten die bij de extensie zijn verpakt te openen en te sluiten, zonder de gebruikerservaring te verstoren. Behalve voor het doorgeven van berichten delen offscreen documenten geen API's met andere extensiecontexten, maar functioneren ze als volledige webpagina's waarmee extensies kunnen communiceren.
Als je de Offscreen API wilt gebruiken, maak je een offscreen document via de servicemedewerker.
browser.offscreen.createDocument({
url: browser.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Voer in het document buiten het scherm een actie uit die je eerder in een achtergrondscript zou hebben uitgevoerd. Je kunt bijvoorbeeld 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 serviceworkers van extensies via berichtuitwisseling.
localStorage converteren naar een ander type
De Storage-interface van het webplatform (toegankelijk via window.localStorage) kan niet worden gebruikt in een servicemedewerker. U kunt dit op 2 manieren oplossen. Je kunt de aanroepen vervangen door aanroepen naar een ander opslagmechanisme. De naamruimte browser.storage.local is geschikt voor de meeste use cases, maar er zijn ook andere opties beschikbaar.
Je kunt de aanroepen ook verplaatsen naar een document buiten het scherm. Zo migreer je gegevens die eerder zijn opgeslagen in localStorage naar een ander mechanisme:
- Maak een document buiten het scherm met een conversieroutine en een
runtime.onMessage-handler. - Voeg een conversieroutine toe aan het document buiten het scherm.
- Controleer in de serviceworker van de extensie
browser.storagevoor je gegevens. - Als je gegevens niet worden gevonden, maak je een document buiten het scherm en roep je
runtime.sendMessage()aan om de conversieroutine te starten. - Roep de conversieroutine aan in de
runtime.onMessage-handler die je aan het offscreen document hebt toegevoegd.
Er zijn ook enkele nuances in de manier waarop API's voor webopslag werken in extensies. Meer informatie vind je in Opslag en cookies.
Listeners synchroon registreren
Het asynchroon registreren van een listener (bijvoorbeeld binnen een promise of callback) werkt niet gegarandeerd in Manifest V3. Neem de volgende code door.
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.browserAction.setBadgeText({ text: badgeText });
browser.browserAction.onClicked.addListener(handleActionClick);
});
Dit werkt met een permanente achtergrondpagina omdat de pagina voortdurend wordt uitgevoerd en nooit opnieuw wordt geïnitialiseerd. In Manifest V3 wordt de servicemedewerker opnieuw geïnitialiseerd als de gebeurtenis wordt verstuurd. Dit betekent dat wanneer de gebeurtenis wordt geactiveerd, de listeners niet worden geregistreerd (omdat ze asynchroon worden toegevoegd) en de gebeurtenis wordt gemist.
Verplaats de registratie van de gebeurtenislistener naar het hoogste niveau van je script. Zo kan Chrome de klikhandler van je actie onmiddellijk vinden en aanroepen, zelfs als de opstartlogica van je extensie nog niet is uitgevoerd.
browser.action.onClicked.addListener(handleActionClick);
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.action.setBadgeText({ text: badgeText });
});
XMLHttpRequest() vervangen door global fetch()
XMLHttpRequest() kan niet worden aangeroepen vanuit een servicemedewerker, extensie of anderszins. Vervang aanroepen vanuit je achtergrondscript naar XMLHttpRequest() door aanroepen naar global fetch().
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);
Staten aanhouden
Service workers zijn kortstondig, wat betekent dat ze waarschijnlijk herhaaldelijk worden gestart, uitgevoerd en beëindigd tijdens de browsersessie van een gebruiker. Dit betekent ook dat gegevens niet onmiddellijk beschikbaar zijn in globale variabelen, omdat de vorige context is verwijderd. Gebruik Storage API's als bron van waarheid om dit te omzeilen. In een voorbeeld wordt uitgelegd hoe je dit doet.
In het volgende voorbeeld wordt een globale variabele gebruikt om een naam op te slaan. In een servicemedewerker kan deze variabele meerdere keren worden gereset tijdens de browsersessie van een gebruiker.
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 }); });
Vervang voor Manifest V3 de globale variabele door een aanroep naar de Storage API.
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 }); });
Timers omzetten in wekkers
Het is gebruikelijk om vertraagde of periodieke bewerkingen te gebruiken met de methoden setTimeout() of setInterval(). Deze API's kunnen mislukken in servicemedewerkers, omdat de timers worden geannuleerd wanneer de servicemedewerker wordt beëindigd.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { browser.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Gebruik in plaats daarvan de Alarms API. Net als andere listeners moeten wekkers worden geregistreerd op het hoogste niveau van je script.
async function startAlarm(name, duration) { await browser.alarms.create(name, { delayInMinutes: 3 }); } browser.alarms.onAlarm.addListener(() => { browser.action.setIcon({ path: getRandomIconPath(), }); });
De servicemedewerker actief houden
Service workers zijn per definitie gebeurtenisgestuurd en worden beëindigd bij inactiviteit. Zo kan Chrome de prestaties en het geheugenverbruik van je extensie optimaliseren. Meer informatie vind je in onze documentatie over de levenscyclus van serviceworkers. In uitzonderlijke gevallen kunnen aanvullende maatregelen nodig zijn om te zorgen dat een servicemedewerker langer actief blijft.
Een servicemedewerker actief houden totdat een langdurige bewerking is afgerond
Tijdens langdurige service worker-bewerkingen die geen extensie-API's aanroepen, kan de service worker halverwege de bewerking worden afgesloten. Voorbeelden hiervan zijn:
- Een
fetch()-verzoek dat mogelijk langer dan 5 minuten duurt (bijvoorbeeld een grote download via een mogelijk slechte verbinding). - Een complexe asynchrone berekening die meer dan 30 seconden duurt.
Als je de levensduur van de serviceworker in deze gevallen wilt verlengen, kun je periodiek een eenvoudige extensie-API aanroepen om de time-outteller te resetten. Dit is alleen bedoeld voor uitzonderlijke gevallen. In de meeste situaties is er meestal een betere, platformspecifieke manier om hetzelfde resultaat te bereiken.
In het volgende voorbeeld zie je een helperfunctie van waitUntil() die je servicemedewerker actief houdt totdat een bepaalde promise is opgelost:
async function waitUntil(promise) {
const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Een serviceworker continu actief houden
In zeldzame gevallen is het nodig de levensduur onbeperkt te verlengen. We hebben vastgesteld dat bedrijven en het onderwijs de grootste use cases zijn en we staan dit daar specifiek toe, maar we ondersteunen dit niet in het algemeen. In deze uitzonderlijke omstandigheden kun je een service worker actief houden door periodiek een triviale extensie-API aan te roepen. Deze aanbeveling geldt alleen voor extensies die worden uitgevoerd op beheerde apparaten voor zakelijke of educatieve toepassingen. In andere gevallen is dit niet toegestaan en het team voor Chrome-extensies behoudt zich het recht voor om in de toekomst actie te ondernemen tegen die extensies.
Gebruik het volgende codefragment om je servicemedewerker actief 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 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'];
}