Migracja do skryptu service worker

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

Skrypt service worker zastępuje tło lub stronę zdarzenia rozszerzenia, by upewnić się, że kod tła nie jest widoczny w wątku głównym. Dzięki temu rozszerzenia mogą być uruchamiane tylko wtedy, gdy jest to potrzebne, co oszczędza zasoby.

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

Na tej stronie opisujemy zadania konwertowania stron w tle na mechanizmy Service Worker rozszerzeń. Więcej informacji o skryptach service worker rozszerzeń znajdziesz w samouczku Obsługa zdarzeń z skryptami service worker oraz w sekcji Informacje o skryptach service worker rozszerzeń.

Różnice między skryptami działającymi w tle a skryptami service worker rozszerzeń

W niektórych kontekstach instancje robocze rozszerzeń nazywane „skryptami działającymi w tle” są widoczne. Mimo że mechanizmy service worker rozszerzeń działają w tle, wywoływanie ich może być mylące, ponieważ sugeruje, że mają identyczne możliwości. Różnice opisano poniżej.

Zmiany na stronach w tle

Skrypty service worker różnią się między stronami w tle.

  • Funkcjonują one poza głównym wątkiem, co oznacza, że nie zakłócają zawartości rozszerzenia.
  • Mają specjalne funkcje, takie jak przechwytywanie zdarzeń pobierania z początku rozszerzenia, np. z wyskakującego okienka paska narzędzi.
  • Mogą się komunikować i wchodzić w interakcje z innymi kontekstami za pomocą interfejsu klientów.

Wymagane zmiany

Musisz wprowadzić kilka zmian w kodzie, aby uwzględnić różnice między sposobem działania skryptów działających w tle i skryptów service worker. Na początku sposób określenia skryptu service worker w pliku manifestu różni się od sposobu określenia 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 niewyświetlającego się na ekranie.
  • Detektory zdarzeń nie powinny być rejestrowane w odpowiedzi na zwrócone obietnice lub w wywołaniach zwrotnych zdarzeń.
  • Ponieważ nie są one zgodne wstecznie z XMLHttpRequest(), musisz zastąpić połączenia do tego interfejsu wywołaniami fetch().
  • Ponieważ kończą się, gdy nie są używane, nie możesz polegać na zmiennych globalnych, więc musisz zachować stany aplikacji. Zatrzymanie mechanizmów Service Worker może także zakończyć liczniki czasu przed zakończeniem. Musisz zastąpić je alarmami.

Na tej stronie szczegółowo opisujemy te zadania.

Zaktualizuj pole „tło” w pliku manifestu

W pliku Manifest V3 strony w tle są zastępowane przez skrypt service worker. Zmiany w pliku manifestu znajdziesz poniżej.

  • Zamień "background.scripts" na "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 (używających słowa kluczowego import). Jego wartością będzie zawsze "module". Więcej informacji znajdziesz w artykule z podstawowymi informacjami o mechanizmie Service Worker dla rozszerzeń

Przenieś wywołania DOM i okien do dokumentu poza ekranem

Niektóre rozszerzenia potrzebują dostępu do obiektów DOM i okna bez otwierania nowego okna lub karty. Interfejs Offscreen API umożliwia otwieranie i zamykanie niewyświetlonych dokumentów w pakiecie z rozszerzeniem bez zakłócania wrażeń użytkownika. Oprócz przekazywania 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żywać 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 dowolne działanie, które wcześniej byłyby wykonywane w skrypcie działającym w tle. Możesz na przykład skopiować tekst zaznaczony na stronie hostującej.

let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');

Komunikuj się między dokumentami spoza ekranu a skryptami usług rozszerzeń za pomocą przekazywania wiadomości.

Konwertowanie parametru localStorage na inny typ

Interfejsu Storage platformy internetowej (dostępnego z poziomu window.localStorage) nie można używać w skryptach service worker. Aby rozwiązać ten problem, wykonaj jedną z tych czynności. Po pierwsze, można zastąpić je wywołaniami innego mechanizmu pamięci masowej. Przestrzeń nazw chrome.storage.local sprawdzi się w większości przypadków, ale dostępne są inne opcje.

Możesz też przenieść wywołania do dokumentu spoza ekranu. Aby na przykład przenieść do innego mechanizmu dane przechowywane wcześniej w usłudze localStorage:

  1. Utwórz dokument poza ekranem z rutyną konwersji i modułem obsługi runtime.onMessage.
  2. Dodaj rutynę konwersji do dokumentu poza ekranem.
  3. W skrypcie service worker rozszerzenia sprawdź swoje dane w obszarze chrome.storage.
  4. Jeśli nie uda się znaleźć Twoich danych, utwórz dokument poza ekranem i wywołaj runtime.sendMessage(), aby rozpocząć rutynę konwersji.
  5. W module obsługi runtime.onMessage dodanego do dokumentu poza ekranem wywołaj rutynę konwersji.

Istnieją też pewne niuanse dotyczące działania interfejsów API do przechowywania danych w sieci w rozszerzeniach. Więcej informacji znajdziesz w sekcji Pamięć i pliki cookie.

Synchronicznie rejestruj detektory

Asynchronicznie rejestrowanie odbiornika (na przykład w obietce lub wywołaniu zwrotnym) nie ma gwarancji, że w platformie Manifest V3 będzie działać. Przeanalizuj ten kod.

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.browserAction.setBadgeText({ text: badgeText });
  chrome.browserAction.onClicked.addListener(handleActionClick);
});

Działa to w przypadku stałej strony w tle, ponieważ działa ona cały czas i nigdy nie zostaje zainicjowana ponownie. W pliku manifestu w wersji 3 skrypt service worker zostanie ponownie zainicjowany po wysłaniu zdarzenia. Oznacza to, że po wywołaniu zdarzenia detektory nie będą rejestrowane (ponieważ są dodawane asynchronicznie), a zdarzenie zostanie pominięte.

Zamiast tego przenieś rejestrację odbiornika do najwyższego poziomu skryptu. Dzięki temu Chrome będzie w stanie natychmiast znaleźć i wywołać moduł obsługi kliknięć akcji, nawet jeśli rozszerzenie nie zacznie jeszcze wykonywać logiki uruchamiania.

chrome.action.onClicked.addListener(handleActionClick);

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

Zastąp metodę XMLHttpRequest() globalną metodą fetch()

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

Utrwal stany

Mechanizmy Service Worker są tymczasowe, co oznacza, że najprawdopodobniej są uruchamiane, uruchamiane i wyłączane podczas sesji przeglądarki użytkownika. Oznacza to również, że dane nie są od razu dostępne w zmiennych globalnych z powodu wyłączenia poprzedniego kontekstu. Aby obejść ten problem, jako źródła informacji użyj interfejsów API pamięci masowej. Przykład pokazuje, jak to zrobić.

W poniższym przykładzie do przechowywania nazwy użyto zmiennej globalnej. W skrypcie service worker ta zmienna może być resetowana wielokrotnie w trakcie sesji przeglądarki użytkownika.

Skrypt w tle platformy Manifest V2
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 pliku manifestu w wersji 3 zastąp zmienną globalną wywołaniem interfejsu Storage API.

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

Zmień minutniki na alarmy

Częstą praktyką jest wykonywanie operacji opóźnionych lub okresowych za pomocą metod setTimeout() i setInterval(). Te interfejsy API mogą jednak kończyć się niepowodzeniem, ponieważ liczniki czasu są anulowane po każdym zamknięciu skryptu service worker.

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

W takiej sytuacji użyj interfejsu Alarms API. Tak jak w przypadku innych detektorów, detektory alarmów powinny być zarejestrowane na najwyższym poziomie skryptu.

Skrypt service worker platformy Manifest V3
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 z definicji działają na podstawie zdarzeń i kończą się w przypadku braku aktywności. Dzięki temu Chrome może zoptymalizować wydajność i wykorzystanie pamięci przez rozszerzenie. Więcej informacji znajdziesz w dokumentacji cyklu życia skryptu service worker. W wyjątkowych przypadkach mogą być wymagane dodatkowe działania, aby wydłużyć czas działania skryptu service worker.

Utrzymuj aktywność 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 się wyłączyć w trakcie operacji. Przykłady:

  • Żądanie fetch() może trwać dłużej niż 5 minut (np. duże pobieranie 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 w celu zresetowania licznika czasu oczekiwania. Pamiętaj, że te opcje są zarezerwowane tylko dla wyjątkowych przypadków i w większości przypadków istnieje zwykle lepszy, idiomatyczny sposób na osiągnięcie tego samego wyniku.

Ten przykład przedstawia funkcję pomocniczą waitUntil(), która podtrzymuje działanie skryptu service worker, dopóki nie zostanie rozwinięta dana obietnica:

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

waitUntil(someExpensiveCalculation());

Zapewnianie nieprzerwanego działania skryptu service worker

W rzadkich przypadkach konieczne jest przedłużenie okresu trwania bezterminowo. Zidentyfikowaliśmy największe przypadki użycia związane z przedsiębiorstwem i edukacją, i szczególnie to dopuszczamy, ale ogólnie nie jest to obsługiwane. W tych wyjątkowych okolicznościach można zapewnić nieprzerwane 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 zarządzanych urządzeniach w firmach lub instytucjach edukacyjnych. W innych przypadkach jest to niedozwolone. Zespół ds. rozszerzeń do Chrome zastrzega sobie prawo do podjęcia działań w związku z tymi rozszerzeniami.

Aby zapewnić 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 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'];
}