Remplacer les pages d'arrière-plan ou d'événement par un service worker
Un service worker remplace la page d'arrière-plan ou d'événement de l'extension pour s'assurer que le code d'arrière-plan reste en dehors du thread principal. Cela permet aux extensions de ne s'exécuter que lorsque cela est nécessaire, ce qui économise des ressources.
Les pages d'arrière-plan sont un composant fondamental des extensions depuis leur introduction. En termes simples, les pages d'arrière-plan fournissent un environnement indépendant de toute autre fenêtre ou onglet. Cela permet aux extensions d'observer les événements et d'y répondre.
Cette page décrit les tâches permettant de convertir des pages d'arrière-plan en service workers d'extension. Pour en savoir plus sur les service workers d'extension en général, consultez le tutoriel Gérer les événements avec des service workers et la section À propos des service workers d'extension.
Différences entre les scripts d'arrière-plan et les service workers d'extension
Dans certains contextes, les service workers d'extension sont appelés "scripts d'arrière-plan". Bien que les service workers d'extension s'exécutent en arrière-plan, les appeler scripts d'arrière-plan est quelque peu trompeur, car cela implique des fonctionnalités identiques. Les différences sont décrites ci-dessous.
Modifications apportées aux pages d'arrière-plan
Les service workers présentent un certain nombre de différences par rapport aux pages d'arrière-plan.
- Ils fonctionnent en dehors du thread principal, ce qui signifie qu'ils n'interfèrent pas avec le contenu de l'extension.
- Ils disposent de fonctionnalités spéciales, telles que l'interception des événements de récupération sur l'origine de l'extension, comme ceux d'une fenêtre pop-up de barre d'outils.
- Ils peuvent communiquer et interagir avec d'autres contextes via l'interface Clients.
Modifications à apporter
Vous devrez apporter quelques ajustements au code pour tenir compte des différences entre le fonctionnement des scripts d'arrière-plan et des service workers. Pour commencer, la façon dont un service worker est spécifié dans le fichier manifeste est différente de celle des scripts d'arrière-plan. De plus :
- Comme ils ne peuvent pas accéder au DOM ni à l'interface
window, vous devez déplacer ces appels vers une autre API ou dans un document hors écran. - Les écouteurs d'événements ne doivent pas être enregistrés en réponse à des promesses renvoyées ni dans des rappels d'événements.
- Comme ils ne sont pas rétrocompatibles avec
XMLHttpRequest(), vous devez remplacer les appels à cette interface par des appels àfetch(). - Comme ils se terminent lorsqu'ils ne sont pas utilisés, vous devez conserver les états de l'application plutôt que de vous appuyer sur des variables globales. La fin des service workers peut également mettre fin aux minuteurs avant qu'ils ne soient terminés. Vous devez les remplacer par des alarmes.
Cette page décrit ces tâches en détail.
Mettre à jour le champ "background" dans le fichier manifeste
Dans Manifest V3, les pages d'arrière-plan sont remplacées par un service worker. Les modifications apportées au manifeste sont listées ci-dessous.
- Remplacez
"background.scripts"par"background.service_worker"dans lemanifest.json. Notez que le champ"service_worker"prend une chaîne, et non un tableau de chaînes. - Supprimez
"background.persistent"dumanifest.json.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Le champ "service_worker" prend une seule chaîne. Vous n'aurez besoin du champ "type" que si vous utilisez des modules ES (à l'aide du mot clé import). Sa valeur sera toujours "module". Pour en savoir plus, consultez Principes de base des service workers d'extension.
Déplacer les appels DOM et de fenêtre vers un document hors écran
Certaines extensions doivent accéder aux objets DOM et de fenêtre sans ouvrir visuellement une nouvelle fenêtre ni un nouvel onglet. L'API Offscreen est compatible avec ces cas d'utilisation en ouvrant et en fermant des documents non affichés regroupés avec l'extension, sans perturber l'expérience utilisateur. À l'exception du transfert de messages, les documents hors écran ne partagent pas d'API avec d'autres contextes d'extension, mais fonctionnent comme des pages Web complètes avec lesquelles les extensions peuvent interagir.
Pour utiliser l'API Offscreen, créez un document hors écran à partir du service worker.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Dans le document hors écran, effectuez toutes les actions que vous auriez exécutées auparavant dans un script d'arrière-plan. Par exemple, vous pouvez copier le texte sélectionné sur la page hôte.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Communiquez entre les documents hors écran et les service workers d'extension à l'aide du transfert de messages.
Convertir localStorage en un autre type
L'interface Storage de la plate-forme Web (accessible depuis window.localStorage) ne peut pas être utilisée dans un service worker. Pour résoudre ce problème, effectuez l'une des deux opérations suivantes. Tout d'abord, vous pouvez la remplacer par des appels à un autre mécanisme de stockage. L'espace de noms chrome.storage.local répondra à la plupart des cas d'utilisation, mais d'autres options sont disponibles.
Vous pouvez également déplacer ses appels vers un document hors écran. Par exemple, pour migrer des données précédemment stockées dans localStorage vers un autre mécanisme :
- Créez un document hors écran avec une routine de conversion et un
runtime.onMessagegestionnaire. - Ajoutez une routine de conversion au document hors écran.
- Dans le service worker d'extension, recherchez vos données dans
chrome.storage. - Si vos données ne sont pas trouvées, créez un document hors écran et appelez
runtime.sendMessage()pour démarrer la routine de conversion. - Dans le gestionnaire
runtime.onMessageque vous avez ajouté au document hors écran, appelez la routine de conversion.
Le fonctionnement des API de stockage Web dans les extensions présente également quelques nuances. Pour en savoir plus, consultez Stockage et cookies.
Enregistrer les écouteurs de manière synchrone
L'enregistrement asynchrone d'un écouteur (par exemple, dans une promesse ou un rappel) n'est pas garanti dans Manifest V3. Considérez le code suivant.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
Cela fonctionne avec une page d'arrière-plan persistante, car la page s'exécute en permanence et n'est jamais réinitialisée. Dans Manifest V3, le service worker sera réinitialisé lorsque l'événement sera distribué. Cela signifie que lorsque l'événement se déclenche, les écouteurs ne seront pas enregistrés (car ils sont ajoutés de manière asynchrone) et l'événement sera manqué.
À la place, déplacez l'enregistrement de l'écouteur d'événements au niveau supérieur de votre script. Cela permet à Chrome de trouver et d'appeler immédiatement le gestionnaire de clics de votre action, même si votre extension n'a pas terminé d'exécuter sa logique de démarrage.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Remplacer XMLHttpRequest() par global fetch()
XMLHttpRequest() ne peut pas être appelé à partir d'un service worker, d'une extension ou autre. Remplacez les appels de votre script d'arrière-plan à XMLHttpRequest() par des appels à 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);
Conserver les états
Les service workers sont éphémères, ce qui signifie qu'ils sont susceptibles de démarrer, de s'exécuter et de se terminer à plusieurs reprises au cours d'une session de navigateur d'un utilisateur. Cela signifie également que les données ne sont pas immédiatement disponibles dans les variables globales, car le contexte précédent a été supprimé. Pour contourner ce problème, utilisez les API de stockage comme source de référence. Un exemple vous montrera comment procéder.
L'exemple suivant utilise une variable globale pour stocker un nom. Dans un service worker, cette variable peut être réinitialisée plusieurs fois au cours d'une session de navigateur d'un utilisateur.
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 }); });
Pour Manifest V3, remplacez la variable globale par un appel à l'API Storage.
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 }); });
Convertir les minuteurs en alarmes
Il est courant d'utiliser des opérations différées ou périodiques à l'aide des méthodes setTimeout() ou setInterval(). Toutefois, ces API peuvent échouer dans les service workers, car les minuteurs sont annulés chaque fois que le service worker est arrêté.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Utilisez plutôt l'API Alarms. Comme pour les autres écouteurs, les écouteurs d'alarme doivent être enregistrés au niveau supérieur de votre script.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
Maintenir le service worker actif
Les service workers sont par définition axés sur les événements et se terminent en cas d'inactivité. Chrome peut ainsi optimiser les performances et la consommation de mémoire de votre extension. Pour en savoir plus, consultez la documentation sur le cycle de vie des service workers. Dans des cas exceptionnels, des mesures supplémentaires peuvent être nécessaires pour s'assurer qu'un service worker reste actif plus longtemps.
Maintenir un service worker actif jusqu'à la fin d'une opération de longue durée
Lors des opérations de longue durée des service workers qui n'appellent pas d'API d'extension, le service worker peut s'arrêter en cours d'opération. Voici quelques exemples :
- Une
fetch()requête qui peut prendre plus de cinq minutes (par exemple, un téléchargement volumineux sur une connexion potentiellement mauvaise). - Un calcul asynchrone complexe qui prend plus de 30 secondes.
Pour prolonger la durée de vie du service worker dans ces cas, vous pouvez appeler périodiquement une API d'extension triviale pour réinitialiser le compteur de délai avant expiration. Veuillez noter que cela n'est réservé qu'aux cas exceptionnels et que, dans la plupart des situations, il existe généralement un moyen plus efficace et plus idiomatique de la plate-forme pour obtenir le même résultat.
L'exemple suivant montre une fonction d'assistance waitUntil() qui maintient votre service worker actif jusqu'à ce qu'une promesse donnée soit résolue :
async function waitUntil(promise) {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Maintenir un service worker actif en continu
Dans de rares cas, il est nécessaire de prolonger la durée de vie indéfiniment. Nous avons identifié les entreprises et l'éducation comme les principaux cas d'utilisation, et nous l'autorisons spécifiquement dans ces cas, mais nous ne le prenons pas en charge en général. Dans ces circonstances exceptionnelles, vous pouvez maintenir un service worker actif en appelant périodiquement une API d'extension triviale. Il est important de noter que cette recommandation ne s'applique qu'aux extensions exécutées sur des appareils gérés pour les cas d'utilisation en entreprise ou dans l'éducation. Elle n'est pas autorisée dans d'autres cas, et l'équipe chargée des extensions Chrome se réserve le droit de prendre des mesures à l'encontre de ces extensions à l'avenir.
Utilisez l'extrait de code suivant pour maintenir votre service worker actif :
/**
* 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'];
}