Migracja do skryptu service worker

Zastępowanie stron tła lub zdarzeń skryptem service worker

Skrypt service worker zastępuje stronę zdarzenia lub stronę działającą w tle 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 jest to konieczne, co pozwala oszczędzać zasoby.

Strony tła są podstawowym elementem rozszerzeń od czasu ich wprowadzenia. Mówiąc prościej, strony działające w tle zapewniają środowisko, które działa niezależnie od innych okien i kart. Dzięki temu rozszerzenia mogą obserwować zdarzenia i reagować na nie.

Na tej stronie opisano zadania związane z konwertowaniem stron działających w tle na skrypty service worker 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 skryptami service worker rozszerzeń

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, że mają takie same możliwości. Różnice opisujemy poniżej.

Zmiany na stronach działających w tle

Skrypty service worker różnią się od stron działających w tle pod kilkoma względami.

  • Działają poza głównym wątkiem, co oznacza, że nie zakłócają działania treści rozszerzenia.
  • Mają specjalne możliwości, takie jak przechwytywanie zdarzeń pobierania w źródle rozszerzenia, np. z wyskakującego okienka paska narzędzi.
  • Mogą komunikować się i wchodzić w interakcje z innymi kontekstami za pomocą interfejsu Clients.

Zmiany, które musisz wprowadzić

Musisz wprowadzić kilka zmian w kodzie, aby uwzględnić różnice między działaniem skryptów działających w tle a skryptów service worker. Po pierwsze, 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ą dostępu do modelu DOM ani interfejsu window, więc musisz przenieść takie wywołania do innego interfejsu API lub do dokumentu poza ekranem.
  • Detektorów zdarzeń nie należy rejestrować w odpowiedzi na zwrócone obietnice ani w wywołaniach zwrotnych zdarzeń.
  • Ponieważ nie są one zgodne wstecznie z interfejsem XMLHttpRequest(), musisz zastąpić wywołania tego interfejsu wywołaniami interfejsu fetch().
  • Ponieważ są one kończone, gdy nie są używane, musisz zachowywać stany aplikacji, zamiast polegać na zmiennych globalnych. Zakończenie skryptów service worker może też spowodować zakończenie liczników czasu przed ich upływem. Musisz je zastąpić alarmami.

Na tej stronie znajdziesz szczegółowe informacje o tych zadaniach.

Aktualizowanie pola „background” w pliku manifestu

W platformie Manifest V3 strony działające w tle są zastępowane przez skrypt service worker. Zmiany w pliku manifestu są wymienione poniżej.

  • Zastąp "background.scripts" wartością "background.service_worker" w manifest.json. Pamiętaj, że pole "service_worker" przyjmuje ciąg znaków, a nie tablicę ciągów znaków.
  • Usuń "background.persistent" z 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 pojedynczy ciąg znaków. Pole "type" jest potrzebne tylko wtedy, gdy używasz modułów ES (za pomocą słowa kluczowego import). Jego wartość zawsze będzie wynosić "module". Więcej informacji znajdziesz w artykule Podstawowe informacje o skryptach 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 spakowane z rozszerzeniem, bez zakłócania wygody użytkownika. Z wyjątkiem przekazywania wiadomości dokumenty poza ekranem nie udostępniają interfejsów API innym kontekstom rozszerzeń, ale działają jako pełne strony internetowe, z którymi rozszerzenia mogą wchodzić w interakcje.

Aby używać 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 dowolne działanie, które wcześniej zostało uruchomione 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');

Komunikuj się między dokumentami poza ekranem a skryptami service worker rozszerzeń za pomocą przekazywania wiadomości.

Konwertowanie localStorage na inny typ

Interfejsu Storage platformy internetowej (dostępnego z window.localStorage) nie można używać w skrypcie service worker. Aby rozwiązać ten problem, wykonaj jedną z tych czynności: Po pierwsze, możesz zastąpić je wywołaniami innego mechanizmu pamięci masowej. Przestrzeń nazw browser.storage.local sprawdzi się 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 modułem obsługi runtime.onMessage.
  2. Dodaj procedurę konwersji do dokumentu poza ekranem.
  3. W skrypcie service worker rozszerzenia sprawdź, czy dane są dostępne w browser.storage.
  4. Jeśli nie znajdziesz danych, utwórz dokument poza ekranem i wywołaj runtime.sendMessage(), aby rozpocząć procedurę konwersji.
  5. W uchwycie runtime.onMessage dodanym do dokumentu poza ekranem wywołaj procedurę konwersji.

Istnieją też pewne niuanse związane z działaniem interfejsów API pamięci masowej w rozszerzeniach. Więcej informacji znajdziesz w artykule Pamięć i pliki cookie.

Rejestrowanie detektorów synchronicznie

Asynchroniczna rejestracja detektora (np. w ramach obietnicy lub wywołania zwrotnego) może nie 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 działającej w tle, ponieważ jest ona stale uruchomiona i nigdy nie jest ponownie inicjowana. W przypadku platformy Manifest V3 skrypt service worker zostanie ponownie zainicjowany po wysłaniu zdarzenia. Oznacza to, że gdy zdarzenie zostanie wywołane, detektory nie zostaną 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ąp XMLHttpRequest() globalną funkcją fetch()

Nie można wywołać funkcji XMLHttpRequest() z skryptu service worker, rozszerzenia ani w żaden inny sposób. Zastąp wywołania XMLHttpRequest() ze skryptu działającego w tle wywołaniami 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

Skrypty service worker są efemeryczne, co oznacza, że prawdopodobnie będą wielokrotnie uruchamiane, wykonywane i kończone 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 Storage API jako źródła informacji. Poniższy przykład pokazuje, 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 podczas sesji przeglądarki użytkownika.

Skrypt działający w tle 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 platformy 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 używa się opóźnionych lub okresowych operacji za pomocą metod setTimeout() lub setInterval(). Te interfejsy API mogą jednak nie działać w skryptach service worker, ponieważ liczniki czasu są anulowane po zakończeniu działania skryptu service worker.

Skrypt działający w tle platformy Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  browser.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

Zamiast tego użyj interfejsu Alarms API. Podobnie jak inne detektory, detektory alarmów powinny być zarejestrowane 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 aktywności 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 przez rozszerzenie. Więcej informacji znajdziesz w dokumentacji dotyczącej cyklu życia skryptu service worker. W wyjątkowych przypadkach mogą być wymagane dodatkowe środki, aby skrypt service worker działał dłużej.

Utrzymywanie działania skryptu service worker do momentu zakończenia długo trwającej operacji

Podczas długotrwałych operacji skryptu service worker, które nie wywołują interfejsów API rozszerzeń, skrypt może zostać wyłączony w trakcie operacji. Przykłady:

  • Żądanie fetch(), które może trwać dłużej niż 5 minut (np. pobieranie dużego pliku przy potencjalnie słabym połączeniu).
  • Złożone obliczenia asynchroniczne, które trwają dłużej niż 30 sekund.

Aby w takich przypadkach wydłużyć czas działania skryptu service worker, możesz okresowo wywoływać prosty interfejs API rozszerzenia, aby resetować licznik czasu oczekiwania. Pamiętaj, że jest to zarezerwowane tylko dla wyjątkowych przypadków, a w większości sytuacji zwykle istnieje lepszy, idiomatyczny sposób na osiągnięcie tego samego rezultatu.

Poniższy przykład pokazuje funkcję pomocniczą waitUntil(), która utrzymuje działanie skryptu service worker do momentu, aż dana obietnica zostanie spełniona:

async function waitUntil(promise) {
  const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
  try {
    await promise;
  } finally {
    clearInterval(keepAlive);
  }
}

waitUntil(someExpensiveCalculation());

Utrzymywanie działania skryptu service worker w trybie ciągłym

W rzadkich przypadkach konieczne jest przedłużenie okresu ważności na czas nieokreślony. Zidentyfikowaliśmy firmy i instytucje edukacyjne jako największe przypadki użycia i zezwalamy na to w tych obszarach, ale ogólnie nie obsługujemy tej funkcji. W tych wyjątkowych okolicznościach można utrzymać działanie skryptu service worker, 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ń biznesowych lub 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 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'];
}