Migracja do skryptu service worker

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 elementem rozszerzeń od czasu ich wprowadzenia. Mówiąc najprościej, strony w tle zapewniają środowisko niezależne od innych okien lub 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 usługi rozszerzenia. Więcej informacji o skryptach service worker rozszerzeń znajdziesz w samouczku Obsługa zdarzeń za pomocą skryptów service worker i w sekcji Skrypty service worker rozszerzeń.

Różnice między skryptami działającymi w tle a procesami roboczymi usługi rozszerzenia

W niektórych kontekstach procesy robocze 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 te opisujemy 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 głównym wątkiem, co oznacza, że nie zakłócają treści rozszerzenia.
  • Mają one specjalne możliwości, takie jak przechwytywanie zdarzeń pobierania w domenie rozszerzenia, np. tych 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 service workerów, 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:

  • Ponieważ nie mają dostępu do DOM ani interfejsu window, 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 zgodne wstecznie z interfejsem XMLHttpRequest(), więc musisz zastąpić wywołania tego interfejsu wywołaniami interfejsu fetch().
  • 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ółowe informacje o tych zadaniach.

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" w pliku manifest.json. Pamiętaj, że pole "service_worker" przyjmuje ciąg znaków, a nie tablicę ciągów znaków.
  • Usuń urządzenie "background.persistent" z konta manifest.json.
(platforma) Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
(platforma) Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

Pole "service_worker" przyjmuje jeden 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 okien bez wizualnego otwierania nowego okna lub karty. Interfejs Offscreen API obsługuje te przypadki użycia, otwierając i zamykając niewyś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.

browser.offscreen.createDocument({
  url: browser.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');

Komunikacja między dokumentami poza ekranem a procesami roboczymi usługi rozszerzenia 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: Po pierwsze, możesz zastąpić ją wywołaniami innego mechanizmu pamięci. Przestrzeń nazw browser.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:

  1. Utwórz dokument poza ekranem z procedurą konwersji i procedurą obsługi runtime.onMessage.
  2. Dodaj do dokumentu poza ekranem procedurę konwersji.
  3. W skrypcie service worker rozszerzenia sprawdź browser.storage swoje dane.
  4. Jeśli danych nie można znaleźć, utwórz dokument poza ekranem i wywołaj funkcję runtime.sendMessage(), aby rozpocząć procedurę konwersji.
  5. W obsłudze runtime.onMessage dodanej 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 artykule Miejsce na dane i pliki cookie.

Synchroniczne rejestrowanie detektorów

Rejestrowanie odbiornika asynchronicznie (np. w obietnicy lub wywołaniu zwrotnym) nie musi działać w przypadku platformy Manifest V3. Spójrz na ten kod.

browser.storage.local.get(["badgeText"], ({ badgeText }) => {
  browser.browserAction.setBadgeText({ text: badgeText });
  browser.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 platformy Manifest V3 po wysłaniu zdarzenia skrypt service worker zostanie ponownie zainicjowany. Oznacza to, że gdy zdarzenie zostanie uruchomione, 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ł natychmiast znaleźć i wywołać moduł obsługi kliknięć działania, nawet jeśli rozszerzenie nie zakończyło jeszcze wykonywania logiki uruchamiania.

browser.action.onClicked.addListener(handleActionClick);

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

Zastąpienie XMLHttpRequest() globalną funkcją fetch()

Funkcji XMLHttpRequest() nie można wywołać 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().

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);
fetch()
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 tego uniknąć, używaj interfejsów API pamięci masowej 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 skrypcie service worker ta zmienna może być resetowana wielokrotnie w trakcie sesji przeglądarki użytkownika.

Skrypt tła platformy Manifest V2
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 });
});

W przypadku Manifestu V3 zastąp zmienną globalną wywołaniem interfejsu Storage API.

Skrypt service worker Manifest V3
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 });
});

Konwertowanie minutników na alarmy

Często stosuje się opóźnione lub okresowe operacje za pomocą 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.

Skrypt tła platformy Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  browser.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.

Skrypt service worker platformy Manifest V3
async function startAlarm(name, duration) {
  await browser.alarms.create(name, { delayInMinutes: 3 });
}

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

Utrzymywanie działania skryptu service worker

Service workerzy są z definicji sterowani zdarzeniami i zostaną zamknięci w przypadku braku aktywności. Dzięki temu Chrome może optymalizować wydajność i zużycie pamięci przez rozszerzenie. Więcej informacji znajdziesz w dokumentacji 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 przetworzenie 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 działania komponentu 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, bardziej typowy dla platformy sposób na osiągnięcie tego samego rezultatu.

Poniższy przykład pokazuje 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(browser.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 takie działanie, ale ogólnie go nie obsługujemy. W takich wyjątkowych okolicznościach utrzymanie skryptu service worker przy życiu 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 działań przeciwko takim rozszerzeniom w przyszłości.

Aby utrzymać działanie 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 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'];
}