Zastępowanie stron tła lub zdarzeń skryptem service worker
Skrypt service worker zastępuje stronę tła lub stronę zdarzenia rozszerzenia, aby kod tła nie był wykonywany w głównym wątku. Dzięki temu rozszerzenia działają tylko wtedy, gdy jest to potrzebne, co pozwala oszczędzać zasoby.
Strony w tle są od początku istnienia rozszerzeń ich kluczowym elementem. Mówiąc w prosty sposób, strony w tle zapewniają środowisko, które działa niezależnie od innych okien lub kart. Dzięki temu rozszerzenia mogą obserwować zdarzenia i reagować na nie.
Na tej stronie opisano zadania konwertowania stron działających w tle na instancje robocze rozszerzenia. Więcej informacji o skryptach service workera rozszerzenia znajdziesz w poniższym samouczku Obsługa zdarzeń za pomocą skryptów service workera oraz w sekcji Informacje o skryptach service workera rozszerzenia.
Różnice między skryptami w tle a rozszerzeniem typu service worker
W niektórych kontekstach można zobaczyć instancje robocze usługi rozszerzenia nazywane „skryptami w tle”. Chociaż skrypty service worker w rozszerzeniach działają w tle, nazywanie ich skryptami tła jest nieco mylące, ponieważ sugeruje to, że mają one te same możliwości. Różnice te opisaliśmy poniżej.
Zmiany na stronach tła
Skrypty service worker znacznie różnią się od stron działających w tle.
- Działa ona poza wątkiem głównym, co oznacza, że nie koliduje z treścią rozszerzenia.
- Mają one specjalne możliwości, takie jak przechwytywanie zdarzeń pobierania w źródle rozszerzenia, np. w wyskakującym okienku paska narzędzi.
- Mogą komunikować się i współdziałać z innymi kontekstami za pomocą interfejsu klienta.
Zmiany, które musisz wprowadzić
Aby uwzględnić różnice między sposobem działania skryptów działających w tle a mechanizmami Service Worker, musisz wprowadzić kilka zmian w kodzie. Po pierwsze, sposób, w jaki service worker jest określony w pliku manifestu, różni się od sposobu, w jaki są określone skrypty działające w tle. Dodatkowo:
- Ponieważ nie mają dostępu do DOM ani interfejsu
window
, musisz przenieść takie wywołania do innego interfejsu API lub do dokumentu poza ekranem. - Wyznaczniki zdarzeń nie powinny być rejestrowane w odpowiedzi na zwrócone obietnice ani w ramach wywołań zwrotnych zdarzeń.
- Ponieważ nie są one zgodne wstecznie z funkcją
XMLHttpRequest()
, musisz zastąpić wywołania tego interfejsu wywołaniamifetch()
. - Ponieważ są one zamykane, gdy nie są używane, musisz przechowywać stany aplikacji zamiast polegać na zmiennych globalnych. Zakończenie usługi może też spowodować zakończenie działania liczników czasu przed ich upływem. Musisz je zastąpić alarmami.
Na tej stronie szczegółowo opisujemy te zadania.
Zaktualizuj pole „background” w pliku manifestu.
W pliku manifestu V3 strony w tle są zastępowane przez skrypt service worker. Zmiany w pliku manifestu znajdziesz poniżej.
- W
manifest.json
zastąp"background.scripts"
tekstem"background.service_worker"
. Pamiętaj, że pole"service_worker"
przyjmuje ciąg znaków, a nie tablicę ciągów znaków. - Usuń
"background.persistent"
zmanifest.json
.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Pole "service_worker"
przyjmuje pojedynczy ciąg znaków. Pola "type"
będziesz potrzebować tylko wtedy, gdy używasz modułów ES (za pomocą słowa kluczowego import
). Jego wartość zawsze wynosi "module"
. Aby uzyskać więcej informacji, przeczytaj podstawowe informacje o skrypcie service worker rozszerzeń.
Przenoszenie wywołań DOM i okna do dokumentu poza ekranem
Niektóre rozszerzenia potrzebują dostępu do obiektów DOM i okna bez wizualnego otwierania nowego okna lub karty. Interfejs Offscreen API obsługuje te przypadki użycia, otwierając i zamykając niewyświetlane dokumenty zapakowane w rozszerzenie bez zakłócania wrażeń użytkownika. Poza przekazywaniem wiadomości dokumenty poza ekranem nie udostępniają interfejsów API innym kontekstom rozszerzeń, ale działają jak pełne strony internetowe, z którymi mogą wchodzić w interakcje rozszerzenia.
Aby użyć interfejsu Offscreen API, utwórz dokument offscreen z użyciem usługi workera.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
W dokumencie poza ekranem wykonaj wszystkie czynności, które wcześniej wykonywałeś/wykonywałaś w skrypcie uruchamianym w tle. Możesz na przykład skopiować tekst zaznaczony na stronie hosta.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Komunikuj się między dokumentami poza ekranem a pracownikami usługi rozszerzeń za pomocą przekazywania wiadomości.
Konwertowanie localStorage na inny typ
Interfejs platformy internetowej Storage
(dostępny w sekcji window.localStorage
) nie może być używany w skrypcie service worker. Aby rozwiązać ten problem, wykonaj jedną z tych czynności. Najpierw możesz zastąpić je wywołaniami innego mechanizmu przechowywania danych. Przestrzeń nazw chrome.storage.local
będzie odpowiednia w większości przypadków, ale dostępne są też inne opcje.
Możesz też przenieść wywołania do dokumentu poza ekranem. Aby na przykład przenieść dane wcześniej przechowywane w localStorage
do innego mechanizmu:
- Utwórz dokument poza ekranem z procedurą konwersji i modułem obsługi
runtime.onMessage
. - Dodaj rutynę konwersji do dokumentu poza ekranem.
- W skrypcie service worker rozszerzenia sprawdź swoje dane w
chrome.storage
. - Jeśli nie znajdziesz danych, create dokument poza ekranem i wywołaj funkcję
runtime.sendMessage()
, aby rozpocząć procedurę konwersji. - W module obsługi
runtime.onMessage
dodanym do dokumentu poza ekranem wywołaj rutynę konwersji.
Są też pewne niuanse dotyczące działania interfejsów API do przechowywania danych w internecie w rozszerzeniach. Więcej informacji znajdziesz w artykule Miejsce na dane i pliki cookie.
Sygnalizowanie zdarzeń w sposób synchroniczny
Rejestrowanie listenera asynchronicznie (np. w ramach obietnicy lub funkcji zwracającej wywołanie zwrotne) nie jest gwarantowane w pliku manifestu w wersji 3. Weź pod uwagę ten kod.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
Działa to w przypadku trwałej strony w tle, ponieważ jest ona stale uruchomiona i nigdy nie jest ponownie inicjowana. W pliku manifestu w wersji 3 usługa robocza zostanie ponownie zainicjowana po przesłaniu zdarzenia. Oznacza to, że po uruchomieniu zdarzenia detektory nie zostaną zarejestrowane (ponieważ zostały dodane asynchronicznie), a zdarzenie zostanie pominięte.
Zamiast tego przenieś rejestrację odbiornika zdarzeń na najwyższy poziom skryptu. Dzięki temu Chrome będzie w stanie natychmiast znaleźć i wywołać moduł obsługi kliknięcia w działaniu, nawet jeśli rozszerzenie nie zakończyło wykonywania logiki uruchamiania.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Zastąp XMLHttpRequest() globalną funkcją fetch().
XMLHttpRequest()
nie może być wywoływany z pośrednika usług, rozszerzenia ani w inny sposób. Zastąp wywołania skryptu XMLHttpRequest()
wywołaniem globalnego 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);
Zachowanie stanów
Usługa robocza jest tymczasowa, co oznacza, że prawdopodobnie będzie wielokrotnie uruchamiana, uruchamiana i zamykana podczas sesji przeglądarki użytkownika. Oznacza to też, że dane nie są od razu dostępne w zmiennych globalnych, ponieważ poprzedni kontekst został zdemontowany. Aby tego uniknąć, użyj interfejsów API usługi Storage jako źródła informacji. Przykład pokazuje, jak to zrobić.
W tym przykładzie do przechowywania nazwy użyto zmiennej globalnej. W ramach jednej sesji przeglądarki w usługach workera ta zmienna może być resetowana wiele razy.
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 }); });
W przypadku platformy Manifest V3 zastąp zmienną globalną wywołaniem Storage API.
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 }); });
Konwertowanie minutników na alarmy
Zwykle operacje opóźnione lub okresowe są wykonywane za pomocą metod setTimeout()
lub setInterval()
. Te interfejsy API mogą jednak nie działać w skryptach service workera, ponieważ ich liczniki są anulowane, gdy skrypt zostanie zamknięty.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Zamiast niego należy używać interfejsu Alarms API. Podobnie jak w przypadku innych słuchaczy, słuchacze alarmów powinni być zarejestrowani na najwyższym poziomie skryptu.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
Utrzymywanie skryptu service worker w stanie aktywnym
Skrypty Service Worker są z założenia uruchamiane przez zdarzenia i zawieszają się, gdy nie są używane. Dzięki temu Chrome może zoptymalizować wydajność i zużycie pamięci przez Twoje rozszerzenie. Więcej informacji znajdziesz w dokumentacji cyklu życia mechanizmów Service Worker. W wyjątkowych przypadkach może być konieczne podjęcie dodatkowych środków, aby zapewnić dłuższe działanie usługi.
Utrzymywanie pracownika usługi do czasu zakończenia długotrwałej operacji
Podczas długo trwających operacji skryptu service worker, które nie wywołują interfejsów API rozszerzeń, może się wyłączyć w trakcie operacji. Przykłady:
fetch()
może zająć więcej niż 5 minut (np. duże pobieranie przy słabym połączeniu).- złożone obliczenia asynchroniczne, które trwają ponad 30 sekund;
Aby w takich przypadkach wydłużyć czas działania skryptu service worker, możesz okresowo wywoływać prosty interfejs API rozszerzenia w celu zresetowania licznika czasu oczekiwania. Uwaga: opcja ta jest zarezerwowana tylko w wyjątkowych przypadkach i w większości przypadków istnieje zwykle lepszy, idiomatyczny sposób na uzyskanie tego samego wyniku.
Poniższy przykład przedstawia funkcję pomocniczą waitUntil()
, która utrzymuje aktywny skrypt service worker, dopóki dana obietnica nie zostanie zrealizowana:
async function waitUntil(promise) = {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Ciągłe utrzymywanie aktywności Service Worker
W rzadkich przypadkach przedłużenie okresu przechowywania jest konieczne. Zidentyfikowaliśmy przypadki użycia w przedsiębiorstwach i edukacji jako najważniejsze i zezwalamy na nie, ale nie wspieramy ich w ogóle. W takich wyjątkowych okolicznościach można zachować aktywność serwisu workera, okresowo wywołując interfejs API rozszerzenia. Pamiętaj, że ta rekomendacja dotyczy tylko rozszerzeń działających na zarządzanych urządzeniach w ramach zastosowań korporacyjnych lub edukacyjnych. Jest to niedozwolone w pozostałych przypadkach, a zespół ds. rozszerzeń do Chrome zastrzega sobie prawo do podjęcia działań przeciwko tym rozszerzeniom w przyszłości.
Aby utrzymać aktywny skrypt service worker, użyj tego fragmentu kodu:
/**
* 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'];
}