Migracja do skryptu service worker

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

Skrypt service worker zastępuje stronę zdarzenia lub stronę tła rozszerzenia, aby zapewnić, że kod tła nie będzie działać 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 działać w odpowiedzi na nie.

Na tej stronie opisano zadania związane z konwersją stron w tle na rozszerzenia typu service worker. 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 zobaczysz pracowników usług rozszerzenia o nazwie „skrypty 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

Serwisy mają wiele różnic w stosunku do stron 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ć

Musisz wprowadzić kilka zmian w kodzie, aby uwzględnić różnice między działaniem skryptów w tle a usługami w tle. 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 z wersją XMLHttpRequest(), musisz zastąpić wywołania tego interfejsu wywołaniami fetch().
  • 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:

  • 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" 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 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". Więcej informacji znajdziesz w artykule Podstawy rozszerzeń z usługami w tle.

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 wygody 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');

Komunikacja między dokumentami poza ekranem a pracownikami obsługi rozszerzenia 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. Po pierwsze, możesz zastąpić je wywołaniami innego mechanizmu przechowywania. 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:

  1. Utwórz dokument poza ekranem z rutyną konwersji i obsługą runtime.onMessage.
  2. Dodaj do dokumentu poza ekranem procedurę konwersji.
  3. W rozszerzeniu workera usługi sprawdź, czy w elementach chrome.storage znajdują się Twoje dane.
  4. Jeśli nie znajdziesz danych, create dokument poza ekranem i wywołaj funkcję runtime.sendMessage(), aby rozpocząć procedurę konwersji.
  5. W obiekcie runtime.onMessage dodanym do dokumentu poza ekranem wywołaj rutynę konwersji.

Istnieją też pewne różnice w działaniu interfejsów API pamięci internetowej 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. Rozważ poniższy kod.

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

Ta metoda działa 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 workera zostanie ponownie zainicjowana po przesł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ę odbiornika zdarzeń na najwyższy poziom skryptu. Dzięki temu Chrome będzie mogło natychmiast znaleźć i wywołać metodę obsługi kliknięcia akcji, 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ąp XMLHttpRequest() globalną funkcją fetch().

XMLHttpRequest() nie może być wywoływany z pośrednika usługi, rozszerzenia ani w inny sposób. Zastąp wywołania z tła skryptu XMLHttpRequest() 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);

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. Poniżej znajdziesz przykład, 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.

Skrypt tła 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 V3 zastąp zmienną globalną wywołaniem interfejsu Storage API.

Skrypt service worker w wersji 3 manifestu
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.

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

Zamiast tego użyj interfejsu Alarms API. Podobnie jak w przypadku innych słuchaczy, słuchacze alarmów powinni być zarejestrowani na najwyższym poziomie skryptu.

Skrypt service worker w wersji 3 manifestu
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 dotyczącej cyklu życia usług workera. 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ługotrwałych operacji usługi workera, które nie wywołują interfejsów API rozszerzeń, usługa workera może zostać zamknięta 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 trwające dłużej niż 30 sekund;

Aby przedłużyć czas działania usługi w takich przypadkach, możesz okresowo wywoływać interfejs API rozszerzenia, aby zresetować licznik limitu czasu. Pamiętaj, że ta metoda jest przeznaczona tylko do wyjątkowych sytuacji. W większości przypadków istnieje lepszy, typowy dla danej platformy sposób osiągnięcia tego samego rezultatu.

Poniższy przykład pokazuje funkcję pomocniczą waitUntil(), która utrzymuje usługę w stanie aktywności do momentu, gdy dana obietnica zostanie spełniona:

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

waitUntil(someExpensiveCalculation());

Utrzymywanie w stanie aktywnym elementu service workera

W rzadkich przypadkach konieczne może być przedłużenie okresu ważności w nieskończoność. 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ć aktywnego pracownika usługi, 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. W innych przypadkach jest to niedozwolone, a zespół ds. rozszerzeń Chrome zastrzega sobie prawo do podjęcia w przyszłości działań wobec takich rozszerzeń.

Aby utrzymać pracownika usługi w stanie aktywnym, 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'];
}