Zastępowanie stron tła lub stron wydarzeń za pomocą skryptu service worker
Skrypt service worker zastępuje stronę tła lub stronę zdarzeń rozszerzenia, aby kod działający w tle nie był wykonywany w głównym wątku. Dzięki temu rozszerzenia działają tylko wtedy, gdy są potrzebne, co pozwala oszczędzać zasoby.
Strony w tle są podstawowym komponentem rozszerzeń od czasu ich wprowadzenia. Mówiąc wprost, strony w tle zapewniają środowisko niezależne od innych okien i kart. Umożliwia to rozszerzeniom obserwowanie zdarzeń i reagowanie na nie.
Na tej stronie opisujemy zadania związane z przekształcaniem stron w tle w procesy rozszerzenia. Więcej informacji o skryptach service worker w rozszerzeniach znajdziesz w samouczku Obsługa zdarzeń za pomocą skryptów service worker i w sekcji Informacje o skryptach service worker w rozszerzeniach.
Różnice między skryptami działającymi w tle a procesami roboczymi usługi rozszerzenia
W niektórych kontekstach skrypty service worker rozszerzeń są nazywane „skryptami działającymi w tle”. Chociaż skrypty service worker rozszerzeń działają w tle, nazywanie ich skryptami działającymi w tle jest nieco mylące, ponieważ sugeruje identyczne możliwości. Różnice opisaliśmy poniżej.
Zmiany na stronach w tle
Service worker różni się od stron działających w tle pod wieloma względami.
- Działają one poza wątkiem głównym, co oznacza, że nie zakłócają treści rozszerzenia.
- Mają one specjalne możliwości, takie jak przechwytywanie zdarzeń pobierania w domenie rozszerzenia, np. zdarzeń z wyskakującego okienka paska narzędzi.
- Mogą komunikować się z innymi kontekstami i wchodzić z nimi w interakcje za pomocą interfejsu klientów.
Zmiany, które musisz wprowadzić
Aby uwzględnić różnice w działaniu skryptów działających w tle i procesów roboczych usługi, musisz wprowadzić kilka zmian w kodzie. Na początek warto zauważyć, że sposób określania skryptu service worker w pliku manifestu różni się od sposobu określania skryptów działających w tle. Dodatkowo:
- Nie mają one dostępu do DOM ani interfejsu
window, więc musisz przenieść takie wywołania do innego interfejsu API lub do dokumentu poza ekranem. - Detektory zdarzeń nie powinny być rejestrowane w odpowiedzi na zwrócone obietnice ani w wywołaniach zwrotnych zdarzeń.
- Nie są one wstecznie zgodne z
XMLHttpRequest(), więc musisz zastąpić wywołania tego interfejsu wywołaniamifetch(). - Ponieważ kończą one działanie, gdy nie są używane, musisz zachowywać stany aplikacji, zamiast polegać na zmiennych globalnych. Zakończenie działania service workerów może też spowodować przerwanie działania timerów przed ich ukończeniem. Musisz zastąpić je alarmami.
Na tej stronie znajdziesz szczegółowy opis tych zadań.
Zaktualizuj pole „background” w pliku manifestu.
W platformie Manifest V3 strony w tle są zastępowane przez skrypt service worker. Poniżej znajdziesz listę zmian w pliku manifestu.
- Zastąp
"background.scripts"tekstem"background.service_worker"wmanifest.json. 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 (ze słowem kluczowym import). Jego wartość zawsze wynosi "module". Więcej informacji znajdziesz w artykule Podstawy dotyczące skryptu service worker rozszerzenia.
Przenoszenie wywołań DOM i okien do dokumentu poza ekranem
Niektóre rozszerzenia potrzebują dostępu do obiektów DOM i obiektów okna bez wizualnego otwierania nowego okna lub karty. Interfejs Offscreen API obsługuje te przypadki użycia, otwierając i zamykając nie wyświetlane dokumenty spakowane z rozszerzeniem bez pogarszania wrażeń użytkowników. Poza przekazywaniem wiadomości dokumenty poza ekranem nie współdzielą interfejsów API z innymi kontekstami rozszerzeń, ale działają jako pełne strony internetowe, z którymi rozszerzenia mogą wchodzić w interakcje.
Aby użyć interfejsu Offscreen API, utwórz dokument poza ekranem z poziomu skryptu service worker.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
W dokumencie poza ekranem wykonaj dowolną czynność, którą wcześniej uruchamiałeś(-aś) w skrypcie działającym 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');
komunikowanie się między dokumentami poza ekranem a procesami roboczymi usług rozszerzeń za pomocą przekazywania wiadomości;
Konwertowanie localStorage na inny typ
Interfejs Storage platformy internetowej (dostępny pod adresem 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ć ją wywołaniami innego mechanizmu pamięci. 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 przechowywane wcześniej w localStorage do innego mechanizmu:
- Utwórz dokument poza ekranem z procedurą konwersji i procedurą obsługi
runtime.onMessage. - Dodaj do dokumentu poza ekranem procedurę konwersji.
- W sekcji sprawdzania skryptu service worker rozszerzenia poszukaj
chrome.storagedotyczącego Twoich danych. - Jeśli danych nie można znaleźć, utwórz dokument poza ekranem i wywołaj funkcję
runtime.sendMessage(), aby rozpocząć procedurę konwersji. - W obsłudze
runtime.onMessagedodanej do dokumentu poza ekranem wywołaj procedurę konwersji.
Istnieją też pewne niuanse dotyczące działania interfejsów API pamięci internetowej w rozszerzeniach. Więcej informacji znajdziesz w sekcji Miejsce na dane i pliki cookie.
Synchroniczne rejestrowanie detektorów zdarzeń
Rejestrowanie detektora asynchronicznie (np. w obiekcie typu Promise lub wywołaniu zwrotnym) nie musi działać w przypadku platformy Manifest V3. Spójrz na 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 przypadku Manifestu V3 po wysłaniu zdarzenia skrypt service worker zostanie ponownie zainicjowany. Oznacza to, że gdy zdarzenie zostanie wywołane, detektory nie będą zarejestrowane (ponieważ są dodawane asynchronicznie), a zdarzenie zostanie pominięte.
Zamiast tego przenieś rejestrację detektora zdarzeń na najwyższy poziom skryptu. Dzięki temu Chrome będzie mógł od razu znaleźć i wywołać moduł obsługi kliknięć działania, nawet jeśli rozszerzenie nie zakończyło jeszcze wykonywania logiki uruchamiania.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Zastąpienie XMLHttpRequest() globalną funkcją fetch()
Nie można wywołać funkcji XMLHttpRequest() ze skryptu service worker, rozszerzenia ani w inny sposób. Zastąp wywołania z skryptu działającego w tle do XMLHttpRequest() wywołaniami do 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);
Utrwalanie stanów
Service workerzy są ulotni, co oznacza, że prawdopodobnie będą wielokrotnie uruchamiani, wykonywani i zamykani podczas sesji przeglądarki użytkownika. Oznacza to również, że dane nie są od razu dostępne w zmiennych globalnych, ponieważ poprzedni kontekst został usunięty. Aby to obejść, używaj interfejsów API pamięci jako źródła informacji. Przykład pokaże, jak to zrobić.
W przykładzie poniżej użyto zmiennej globalnej do przechowywania nazwy. W przypadku service workera ta zmienna może być resetowana wielokrotnie w trakcie sesji przeglądarki użytkownika.
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 Manifestu V3 zastąp zmienną globalną wywołaniem interfejsu 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
Często stosuje się opóźnione lub okresowe operacje przy użyciu metod setTimeout() lub setInterval(). Te interfejsy API mogą jednak nie działać w skryptach service worker, ponieważ liczniki są anulowane za każdym razem, gdy skrypt service worker jest zamykany.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Zamiast tego używaj interfejsu Alarms API. Podobnie jak w przypadku innych odbiorców, odbiorcy 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 działania skryptu service worker
Skrypty Service Worker są z definicji oparte na zdarzeniach i zostaną zakończone w przypadku braku aktywności. Dzięki temu Chrome może optymalizować wydajność i zużycie pamięci rozszerzenia. Więcej informacji znajdziesz w dokumentacji dotyczącej cyklu życia skryptu service worker. W wyjątkowych przypadkach może być konieczne podjęcie dodatkowych działań, aby zapewnić dłuższą aktywność skryptu service worker.
Utrzymywanie działania skryptu service worker do czasu zakończenia długo trwającej operacji
Podczas długo trwających operacji skryptu service worker, które nie wywołują interfejsów API rozszerzeń, skrypt service worker może zostać wyłączony w trakcie operacji. Przykłady:
fetch()Żądanie, którego realizacja może potrwać dłużej niż 5 minut (np. duże pobieranie przy słabym połączeniu).- Złożone obliczenia asynchroniczne trwające dłużej niż 30 sekund.
Aby w takich przypadkach wydłużyć czas życia skryptu service worker, możesz okresowo wywoływać prosty interfejs API rozszerzenia, aby zresetować licznik czasu oczekiwania. Pamiętaj, że jest to zarezerwowane tylko dla wyjątkowych przypadków, a w większości sytuacji istnieje lepszy, typowy dla platformy sposób na osiągnięcie tego samego rezultatu.
Poniższy przykład przedstawia waitUntil() funkcję pomocniczą, która utrzymuje działanie skryptu service worker do momentu rozwiązania danego obiektu typu Promise:
async function waitUntil(promise) {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Utrzymywanie ciągłej aktywności skryptu service worker
W rzadkich przypadkach konieczne jest przedłużenie okresu ważności na czas nieokreślony. Za najważniejsze przypadki użycia uznaliśmy zastosowania w firmach i instytucjach edukacyjnych. W tych przypadkach zezwalamy na to, ale ogólnie nie obsługujemy tej funkcji. W takich wyjątkowych okolicznościach utrzymanie działania skryptu service worker można osiągnąć, okresowo wywołując prosty interfejs API rozszerzenia. Pamiętaj, że ta rekomendacja dotyczy tylko rozszerzeń działających na urządzeniach zarządzanych w przypadku zastosowań w firmach lub instytucjach edukacyjnych. W innych przypadkach jest to niedozwolone, a zespół ds. rozszerzeń do Chrome zastrzega sobie prawo do podjęcia w przyszłości działań przeciwko takim rozszerzeniom.
Aby utrzymać aktywność skryptu 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'];
}