Obsługa zdarzeń za pomocą skryptów service worker

Samouczek opisujący pojęcia związane z skryptami service worker rozszerzeń

Przegląd

Ten samouczek zawiera wprowadzenie do mechanizmów Service Worker rozszerzenia do Chrome. W ramach tego samouczka utworzysz rozszerzenie, które pozwoli użytkownikom szybko przechodzić do stron z informacjami o interfejsie API Chrome za pomocą omniboksu. Poznasz takie zagadnienia jak:

  • Zarejestruj skrypt service worker i zaimportuj moduły.
  • Debuguj skrypt service worker rozszerzenia.
  • Zarządzanie stanem i obsługą zdarzeń.
  • Wyzwalaj zdarzenia okresowe.
  • Komunikowanie się ze skryptami treści.

Zanim zaczniesz

W tym przewodniku zakładamy, że masz podstawowe doświadczenie w tworzeniu stron internetowych. Zapoznaj się z artykułami Rozszerzenia 101 i Hello World, aby poznać podstawowe informacje o tworzeniu rozszerzeń.

Tworzenie rozszerzenia

Zacznij od utworzenia nowego katalogu o nazwie quick-api-reference, w którym będą przechowywane pliki rozszerzeń, lub pobierz kod źródłowy z naszego repozytorium GitHub.

Krok 1. Zarejestruj skrypt service worker

Utwórz plik manifest w katalogu głównym projektu i dodaj ten kod:

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js",
  },
}

Rozszerzenia rejestrują skrypt service worker w pliku manifestu, który zawiera tylko 1 plik JavaScript. Nie musisz dzwonić pod numer navigator.serviceWorker.register(), jak w przypadku strony internetowej.

Utwórz folder images, a następnie pobierz do niego ikony.

Zapoznaj się z pierwszymi krokami samouczka Czas odczytu, aby dowiedzieć się więcej o metadanych i ikonach rozszerzenia w pliku manifestu.

Krok 2. Zaimportuj wiele modułów skryptu service worker

Nasz mechanizm Service Worker implementuje 2 funkcje. Aby ułatwić obsługę, zaprezentujemy każdą funkcję w osobnym module. Najpierw musimy zadeklarować skrypt service worker w pliku manifestu jako moduł ES, który umożliwia importowanie modułów w tym mechanizmie:

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

Utwórz plik service-worker.js i zaimportuj 2 moduły:

import './sw-omnibox.js';
import './sw-tips.js';

Utwórz te pliki i dodaj do każdego z nich dziennik konsoli.

sw-omnibox.js:

console.log("sw-omnibox.js")

sw-tips.js:

console.log("sw-tips.js")

Zobacz Importowanie skryptów, aby poznać inne sposoby importowania wielu plików w mechanizmie Service Worker.

Opcjonalnie: debugowanie skryptu service worker

Wyjaśnię Ci, jak znaleźć dzienniki skryptu service worker i dowiedzieć się, kiedy jego działanie zostało zakończone. Najpierw wykonaj instrukcje opisane w sekcji Wczytywanie rozszerzenia bez pakietu.

Po 30 sekundach pojawi się komunikat „Skrypt service worker (nieaktywny)” oznacza, że skrypt service worker został zakończony. Kliknij link „Skrypt service worker (nieaktywny)”, aby go sprawdzić. Pokazuje to poniższa animacja.

A widzisz, że kontrola pracownika service worker go obudził? Otwarcie skryptu service worker w narzędziach deweloperskich spowoduje, że pozostanie on aktywny. Aby zapewnić prawidłowe działanie rozszerzenia po zamknięciu skryptu service worker, zamknij Narzędzia deweloperskie.

Teraz uszkodzij rozszerzenie, aby dowiedzieć się, gdzie znaleźć błędy. Jednym ze sposobów jest usunięcie pliku „.js” z importu './sw-omnibox.js' w pliku service-worker.js. Chrome nie może zarejestrować skryptu service worker.

Wróć na stronę chrome://extensions i odśwież rozszerzenie. Wyświetlą się 2 błędy:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Więcej sposobów debugowania skryptu service worker rozszerzenia znajdziesz w artykule Debugowanie rozszerzeń.

Krok 4. Zainicjuj stan

Chrome wyłącza mechanizmy Service Worker, jeśli nie są potrzebne. Używamy interfejsu chrome.storage API, aby utrzymywać stan w sesjach skryptu service worker. Aby uzyskać dostęp do pamięci, musimy poprosić o uprawnienia w pliku manifestu:

manifest.json:

{
  ...
  "permissions": ["storage"],
}

Najpierw zapisz domyślne sugestie w pamięci. Stan zainicjowania rozszerzenia można zainicjować, gdy rozszerzenie jest zainstalowane po raz pierwszy, nasłuchując zdarzenia runtime.onInstalled():

sw-omnibox.js:

...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    chrome.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

Skrypty service worker nie mają bezpośredniego dostępu do obiektu window, więc nie mogą używać window.localStorage do przechowywania wartości. Mechanizmy Service Worker to środowiska wykonawcze o ograniczonym czasie trwania, które są wielokrotnie zamykane w trakcie sesji przeglądarki użytkownika, przez co są niezgodne ze zmiennymi globalnymi. Zamiast tego użyj programu chrome.storage.local, który przechowuje dane na komputerze lokalnym.

Zapoznaj się z artykułem Utrwalanie danych zamiast używania zmiennych globalnych, aby poznać inne opcje przechowywania danych dla skryptów service worker rozszerzenia.

Krok 5. Zarejestruj wydarzenia

Wszystkie detektory zdarzeń muszą być zarejestrowane statycznie w globalnym zakresie skryptu service worker. Inaczej mówiąc, detektory zdarzeń nie powinny być zagnieżdżone w funkcjach asynchronicznych. Dzięki temu Chrome może mieć pewność, że po ponownym uruchomieniu skryptu service worker zostaną przywrócone wszystkie moduły obsługi zdarzeń.

W tym przykładzie użyliśmy interfejsu API chrome.omnibox, ale najpierw musimy zadeklarować w pliku manifestu regułę słowa kluczowego omniboksa:

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

Teraz zarejestruj detektory zdarzeń omniboksu na najwyższym poziomie skryptu. Gdy użytkownik wpisze na pasku adresu słowo kluczowe w omniboksie (api), a następnie klawisz Tab lub spację, Chrome wyświetli listę sugestii w oparciu o zapisane słowa kluczowe. Za wypełnienie tych sugestii odpowiada zdarzenie onInputChanged(), które pobiera dane wejściowe bieżącego użytkownika i obiekt suggestResult.

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await chrome.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open chrome.${api} API` };
  });
  suggest(suggestions);
});

Gdy użytkownik wybierze sugestię, onInputEntered() otworzy odpowiednią stronę z informacjami o interfejsie API Chrome.

sw-omnibox.js:

...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
  chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

Funkcja updateHistory() zapisuje tekst z omniboksu w storage.local. Dzięki temu najnowsze wyszukiwane hasło może zostać użyte później jako sugestia w omniboksie.

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return chrome.storage.local.set({ apiSuggestions });
}

Krok 6. Skonfiguruj wydarzenie cykliczne

Metody setTimeout() i setInterval() są zwykle używane do wykonywania opóźnionych lub okresowych zadań. Te interfejsy API mogą jednak zawieść, ponieważ algorytm szeregowania anuluje liczniki po zakończeniu działania skryptu service worker. Zamiast nich rozszerzenia mogą korzystać z interfejsu API chrome.alarms.

Najpierw poproś o uprawnienie "alarms" w pliku manifestu. Dodatkowo, aby pobrać wskazówki dotyczące rozszerzeń z lokalizacji hostowanej zdalnie, musisz poprosić o uprawnienia hosta:

manifest.json:

{
  ...
  "permissions": ["storage", "alarms"],
  "permissions": ["storage"],
  "host_permissions": ["https://extension-tips.glitch.me/*"],
}

Rozszerzenie pobierze wszystkie wskazówki, wybierze jedną z nich losowo i zapisze ją w pamięci. Utworzymy alarm, który będzie uruchamiany raz dziennie w celu zaktualizowania wskazówki. Alarmy nie są zapisywane po zamknięciu Chrome. Musimy sprawdzić, czy alarm istnieje, i w razie potrzeby go utworzyć.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://extension-tips.glitch.me/tips.json');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return chrome.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await chrome.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    chrome.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);

Krok 7. Komunikacja z innymi kontekstami

Rozszerzenia używają skryptów treści do odczytywania i modyfikowania treści strony. Gdy użytkownik wejdzie na stronę referencyjną interfejsu Chrome API, skrypt treści rozszerzenia zaktualizuje stronę o wskazówkę dnia. Wysyła wiadomość z prośbą o napiwek dnia od pracownika service worker.

Zacznij od zadeklarowania skryptu treści w pliku manifestu i dodania wzorca dopasowania zgodnego z dokumentacją interfejsu API Chrome.

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

Utwórz nowy plik z treścią. Poniższy kod powoduje wysłanie do skryptu service worker wiadomości z prośbą o napiwek. Następnie dodaje przycisk otwierający wyskakujące okienko z wskazówką dotyczącą rozszerzenia. Ten kod wykorzystuje nową platformę internetową Popover API.

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

Ostatnim krokiem jest dodanie do naszego skryptu service worker modułu obsługi wiadomości, który wysyła odpowiedź z codzienną wskazówką na skrypt treści.

sw-tips.js:

...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    chrome.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

Sprawdź, czy to działa

Sprawdź, czy struktura pliku projektu wygląda tak:

Zawartość folderu rozszerzenia: images folder, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js i content.js

Wczytywanie rozszerzenia lokalnie

Aby wczytać w trybie programisty rozszerzenie bez pakietu, wykonaj czynności opisane w artykule Hello world.

Otwórz stronę referencyjną

  1. Na pasku adresu przeglądarki wpisz słowo kluczowe „api”.
  2. Naciśnij klawisz Tab lub spację.
  3. Wpisz pełną nazwę interfejsu API.
    • LUB wybierz pozycję z listy wcześniejszych wyszukiwań
  4. Otworzy się nowa strona ze stroną informacyjną interfejsu Chrome API.

Powinien on wyglądać podobnie do tego:

Szybkie informacje o interfejsie API otwierające dokumentację interfejsu API środowiska wykonawczego
Rozszerzenie Quick API otwierające interfejs Runtime API.

Otwórz wskazówkę dnia

Kliknij przycisk Wskazówka na pasku nawigacyjnym, aby otworzyć wskazówkę dotyczącą rozszerzenia.

Otwórz wskazówkę dnia w
Rozszerzenie Quick API z najważniejszymi informacjami.

🎯 Potencjalne ulepszenia

Na podstawie informacji zdobytych dzisiaj spróbuj wykonać następujące czynności:

  • Poznaj inny sposób implementacji sugestii omniboksa.
  • Utwórz własny niestandardowy panel do wyświetlania wskazówki dotyczącej rozszerzenia.
  • Otwórz dodatkową stronę z informacjami o interfejsie API rozszerzeń internetowych w MDN.

Buduj dalej!

Gratulujemy ukończenia samouczka 🎉. Rozwijaj swoje umiejętności, korzystając z innych samouczków dla początkujących:

Rozszerzenie Czego się nauczysz
Czas czytania Aby automatycznie wstawić element z określonego zestawu stron.
Menedżer kart Aby utworzyć wyskakujące okienko, które zarządza kartami przeglądarki.
Tryb pełnej koncentracji Aby uruchomić kod na bieżącej stronie po kliknięciu działania rozszerzenia.

Przeglądaj dalej

Aby kontynuować ścieżkę szkoleniową dotyczącą rozszerzenia Service Worker, zalecamy zapoznanie się z tymi artykułami: