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

Samouczek przedstawiający pojęcia związane z usługami instancji roboczych rozszerzeń

Omówienie

Ten samouczek zawiera wprowadzenie do usług w rozszerzeniu Chrome. W ramach utworzysz rozszerzenie, które pozwoli użytkownikom szybko przejść do dokumentacji interfejsu API Chrome za pomocą omniboksu. Zapoznasz się z tymi zagadnieniami:

  • Zarejestruj skrypt service worker i importuj moduły.
  • Debuguj skrypt usługi rozszerzenia.
  • zarządzać stanem i obsługiwać zdarzenia;
  • Wywołuj zdarzenia okresowe.
  • komunikować się ze skryptami treści;

Zanim rozpoczniesz

W tym przewodniku zakładamy, że masz podstawowe doświadczenie w programowaniu stron internetowych. Zalecamy sprawdzenie Rozszerzenia 101 i Hello World zawierające wprowadzenie do w tworzeniu rozszerzeń.

Tworzenie rozszerzenia

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

Krok 1. Zarejestruj usługę w tle

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ą swój skrypt service worker w pliku manifestu, który wymaga tylko jednego pliku JavaScript. Nie musisz wywoływać funkcji navigator.serviceWorker.register(), jak na stronie internetowej.

Utwórz folder images, a potem pobierz do niego ikony.

Aby dowiedzieć się więcej o metadanych i ikonach rozszerzenia w pliku manifestu, zapoznaj się z pierwszymi krokami samouczka dotyczącego czasu czytania.

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

Skrypt service worker implementuje 2 funkcje. Aby ułatwić utrzymanie, każdą funkcję zaimplementujemy w osobnym module. Najpierw musimy zadeklarować skrypt service worker jako moduł ES w pliku manifestu, co umożliwia importowanie modułów do niego:

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 do każdego z nich dodaj log konsoli.

sw-omnibox.js:

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

sw-tips.js:

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

Aby dowiedzieć się, jak importować wiele plików w ramach usługi, przeczytaj artykuł Importowanie skryptów.

Opcjonalnie: debugowanie usługi workera

Wyjaśnię, jak znaleźć logi skryptu service worker i dowiedzieć się, kiedy został wyłączony. Najpierw postępuj zgodnie z instrukcjami wczytywania rozpakowanego rozszerzenia.

Po 30 sekundach pojawi się komunikat „Skrypt service worker (nieaktywny)”. co oznacza, że skrypt service worker został zakończony. Kliknij skrypt service worker (nieaktywny) aby go sprawdzić. Widać to na animacji poniżej.

Czy zauważyłeś, że pracownik usługi wybudził urządzenie? Otwarcie serwisowego workera w narzędziach deweloperskich spowoduje, że pozostanie on aktywny. Aby upewnić się, że rozszerzenie będzie działać prawidłowo po zakończeniu działania skryptu service worker, zamknij Narzędzia deweloperskie.

Teraz rozłóż rozszerzenie, aby dowiedzieć się, gdzie szukać błędów. Jednym ze sposobów jest usunięcie rozszerzenia „.js” z importu './sw-omnibox.js' w pliku service-worker.js. Chrome nie będzie mógł zarejestrować skryptu service worker.

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

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Więcej sposobów debugowania skryptu usługi rozszerzenia znajdziesz w artykule Debugowanie rozszerzeń.

Krok 4. Zainicjuj stan

Jeśli nie są potrzebne, Chrome je wyłączy. Używamy interfejsu API chrome.storage, aby zachowywać stan w różnych 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. Możemy zainicjować stan podczas pierwszej instalacji rozszerzenia, 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 i nie mogą używać window.localStorage do przechowywania wartości. Skrypty service worker to także środowiska wykonawcze o krótkim czasie działania. są wielokrotnie zamykane przez całą sesję przeglądarki użytkownika, co sprawia, że są niezgodne z zmiennych globalnych. Zamiast tego użyj chrome.storage.local, który przechowuje dane na komputerze lokalnym.

Aby dowiedzieć się więcej o innych opcjach przechowywania danych dla pracowników usługi w rozszerzeniu, przeczytaj artykuł Zachowuj dane zamiast używać zmiennych globalnych.

Krok 5. Zarejestruj swoje zdarzenia

Wszystkie odbiorniki zdarzeń muszą być zarejestrowane statycznie w zakresie globalnym usługi. Inaczej mówiąc, detektory zdarzeń nie powinny być zagnieżdżone w funkcjach asynchronicznych. Dzięki temu Chrome może zapewnić przywrócenie wszystkich przetwarzaczy zdarzeń w przypadku ponownego uruchomienia usługi.

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

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 z omniboksu (api), a następnie klawisz Tab lub spację, Chrome wyświetli listę sugestii na podstawie zapisanych słów kluczowych. Za wypełnianie tych sugestii odpowiada zdarzenie onInputChanged(), które pobiera dane wejściowe bieżącego użytkownika oraz 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() pobiera dane z omniboxa i zapisuje je w storage.local. Dzięki temu ostatnio użyte hasło może zostać później użyte 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() lub setInterval() są zwykle używane do wykonywania działań opóźnionych lub okresowych zadania. Te interfejsy API mogą jednak nie działać, ponieważ po zakończeniu działania wątku usługi harmonogram anuluje zegary. Zamiast tego rozszerzenia mogą używać interfejsu API chrome.alarms.

Zacznij od zgłoszenia żądania uprawnienia "alarms" w pliku manifestu. Aby pobrać wskazówki dotyczące rozszerzeń z lokalizacji hostowanej zdalnie, musisz poprosić o uprawnienia hosta:

manifest.json:

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

Rozszerzenie pobierze wszystkie wskazówki, wybierze jedną 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 więc sprawdzić, czy alarm istnieje, i utworzyć go, jeśli go nie ma.

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. Komunikuj się z innymi kontekstami

Rozszerzenia używają skryptów treści do odczytywania i modyfikowania zawartości strony. Gdy użytkownik otworzy stronę referencyjną interfejsu API Chrome, skrypt treści rozszerzenia zaktualizuje stronę o poradę dnia. Wysyła wiadomość z prośbą o wskazówkę dnia do pracownika obsługi klienta.

Najpierw zadeklaruj skrypt treści w pliku manifestu i dodaj wzór dopasowania zgodny z dokumentacją referencyjną interfejsu API Chrome.

manifest.json:

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

Utwórz nowy plik treści. Poniższy kod wysyła wiadomość do service workera z prośbą o podpowiedź. Następnie dodaje przycisk powodujący otwarcie wyskakującego okienka ze 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 mechanizmu obsługi wiadomości modułu obsługi wiadomości, który wysyła odpowiedź do skryptu treści z codzienną wskazówką.

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;
  }
});

Sprawdzanie działania

Sprawdź, czy struktura plików w projekcie wygląda tak:

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

Ładowanie rozszerzenia lokalnie

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

Otwieranie strony referencyjnej

  1. Na pasku adresu przeglądarki wpisz słowo kluczowe „api”.
  2. Naciśnij Tab. lub „spacja”.
  3. Wpisz pełną nazwę interfejsu API.
    • LUB wybrać z listy poprzednich wyszukiwań.
  4. Otworzy się nowa strona z informacjami o interfejsie API Chrome.

Powinien on wyglądać podobnie do tego:

Krótkie omówienie interfejsu API z otwieraniem dokumentacji interfejsu API środowiska wykonawczego
Rozszerzenie szybkiego interfejsu API otwierające środowisko wykonawcze.

Otwórz wskazówkę dnia

Aby otworzyć wskazówkę dotyczącą rozszerzenia, kliknij przycisk Wskazówka w panelu nawigacyjnym.

Otwórz codzienny tip
Szybkie rozszerzenie interfejsu API, które otwiera wskazówkę dnia.

🎯 Potencjalne ulepszenia

Na podstawie tego, czego się dziś nauczysz, spróbuj wykonać jedną z tych czynności:

  • Poznaj inny sposób implementacji sugestii omniboksu.
  • Utwórz własny, niestandardowy modalny panel do wyświetlania wskazówek dotyczących rozszerzenia.
  • Otwórz dodatkową stronę z informacjami o interfejsie API rozszerzeń sieciowych w MDN.

Rozwijaj dalej!

Gratulujemy ukończenia tego samouczka. 🎉 Aby dalej rozwijać swoje umiejętności, ukończ inne samouczki dla początkujących:

Rozszerzenie Czego się nauczysz
Czas czytania Aby automatycznie wstawić element na określonym zestawie stron.
Menedżer kart Aby utworzyć wyskakujące okienko do zarządzania kartami przeglądarki.
Tryb pełnej koncentracji Aby uruchomić kod na bieżącej stronie po kliknięciu działania rozszerzenia.

Przeglądaj dalej

Jeśli chcesz kontynuować ścieżkę szkoleniową dotyczącą skryptu service worker rozszerzenia, zapoznaj się z tymi artykułami: