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 tego samouczka utworzysz rozszerzenie, które umożliwi użytkownikom szybkie przechodzenie do stron referencyjnych interfejsu API Chrome za pomocą paska wyszukiwania. Zapoznasz się z tymi zagadnieniami:

  • Zarejestruj skrypt service worker i importuj moduły.
  • Debugowanie rozszerzenia service workera.
  • zarządzać stanem i obsługiwać zdarzenia;
  • Wywoływanie okresowych zdarzeń.
  • komunikować się ze skryptami treści;

Zanim rozpoczniesz

W tym przewodniku zakładamy, że masz podstawowe doświadczenie w programowaniu stron internetowych. Zalecamy zapoznanie się z wersjami Rozszerzenia 101 i Hello World, w których znajdziesz wprowadzenie do tworzenia rozszerzeń.

Kompilowanie rozszerzenia

Najpierw utwórz nowy katalog o nazwie quick-api-reference, w którym będą przechowywane pliki rozszerzenia, lub pobierz kod źródłowy z naszego repozytorium GitHub Samples.

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ą swojego pracownika usługi w pliku manifestu, który zawiera tylko 1 plik 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 metadanychikonach rozszerzenia w pliku manifestu, zapoznaj się z pierwszymi krokami samouczka dotyczącego czytania.

Krok 2. Zaimportuj wiele modułów usług robota

Nasz serwis worker implementuje 2 funkcje. Aby ułatwić ich obsługę, wdrożymy każdą z nich w osobnym module. Najpierw musimy zadeklarować w pliku manifestu usługę workera jako moduł ES, co pozwoli nam zaimportować moduły w usłudze workera:

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-omniboks.js:

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

sw-tips.js:

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

Aby dowiedzieć się więcej o importowaniu wielu plików w ramach usługi, przeczytaj artykuł Importowanie skryptów.

Opcjonalnie: debugowanie usługi workera

Wyjaśnię, jak znaleźć dzienniki usługi w ramach workera i jak sprawdzić, kiedy został on zakończony. Najpierw wykonaj instrukcje wczytywania rozszerzenia bez pakietu.

Po 30 sekundach pojawi się komunikat „Skrypt service worker (nieaktywny)”, co oznacza, że został wyłączony. Kliknij link „Usługa (nieaktywna)”, aby go sprawdzić. Pokazuje to poniższa animacja.

Czy zauważyłeś/zauważyłaś, że skontrolowanie skryptu service worker spowodowało jego uruchomienie? Otwarcie service workera w narzędziach deweloperskich spowoduje, że pozostanie on aktywny. Aby mieć pewność, że Twoje rozszerzenie działa prawidłowo po zakończeniu pracy usługi, pamiętaj o zamknięciu DevTools.

Teraz rozłóż rozszerzenie, aby dowiedzieć się, gdzie szukać błędów. Jednym ze sposobów jest usunięcie pliku „.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. Pojawią 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 usługi rozszerzenia znajdziesz w artykule Debugowanie rozszerzeń.

Krok 4. Zainicjuj stan

Jeśli nie są potrzebne, Chrome je wyłączy. Do zachowania stanu w sesjach obsługiwanych przez usługę używamy interfejsu API chrome.storage. Aby uzyskać dostęp do pamięci, musisz poprosić o odpowiednie uprawnienia w pliku manifestu:

manifest.json:

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

Najpierw zapisz domyślne sugestie w pamięci. Stan można zainicjować podczas pierwszego instalowania 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']
    });
  }
});

Serwisy nie mają bezpośredniego dostępu do obiektu window, więc nie mogą używać window.localStorage do przechowywania wartości. Ponadto serwisy są krótkotrwałymi środowiskami wykonawczymi, które są wielokrotnie zamykane w trakcie sesji przeglądarki użytkownika, co powoduje ich niezgodność z globalnymi zmiennymi. 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 detektory zdarzeń muszą być statycznie zarejestrowane w globalnym zakresie skryptu service worker. Innymi słowy, odbiorniki zdarzeń nie powinny być zagnieżdżane w funkcjach asynchronicznych. Dzięki temu w przypadku ponownego uruchomienia skryptu service worker Chrome może mieć pewność, że wszystkie moduły obsługi zdarzeń zostaną przywrócone.

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

manifest.json:

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

Teraz zarejestruj detektory zdarzeń w omniboksie na najwyższym poziomie skryptu. Gdy użytkownik wpisze słowo kluczowe w omniboksie (api) na pasku adresu, a następnie tabulator lub spację, Chrome wyświetli listę sugestii na podstawie przechowywanych słów kluczowych. Za wypełnianie tych sugestii odpowiada zdarzenie onInputChanged(), które przyjmuje bieżące dane wejściowe użytkownika i obiekt suggestResult.

sw-omniboks.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ę dokumentacji interfejsu 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 ostatnie wyszukiwane hasło może zostać później użyte jako sugestia omniboksu.

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 zadań opóźnionych lub okresowych. 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ą korzystać z interfejsu API chrome.alarms.

Zacznij od zgłoszenia żądania uprawnienia "alarms" w pliku manifestu. Aby pobrać wskazówki dotyczące rozszerzenia z hostowanej lokalizacji zdalnej, musisz poprosić o uprawnienia dotyczące 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 sprawdzić, czy alarm istnieje, a jeśli nie, utworzyć go.

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

Rozszerzenia korzystają ze skryptów treści do odczytywania i modyfikowania zawartości strony. Gdy użytkownik odwiedza stronę z informacjami o interfejsie API Chrome, skrypt treści rozszerzenia aktualizuje tę stronę o wskazówkę dnia. Wysyła wiadomość z prośbą o podanie wskazówki dnia do usługi.

Zacznij od zadeklarowania skryptu treści w pliku manifestu i dodaj wzorzec dopasowania odpowiadający dokumentacji referencyjnej 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 dodaj przycisk, który otworzy wyskakujące okienko z poradami dotyczącymi rozszerzenia. Ten kod korzysta z nowego interfejsu Popover API na platformie internetowej.

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 serwisu workera, który odpowiada na skrypt treści, wysyłając odpowiedź z poradami na dany dzień.

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 projektu wygląda tak:

Zawartość folderu rozszerzeń: images folder, 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 „spację”.
  3. Wpisz pełną nazwę interfejsu API.
    • LUB wybrać z listy poprzednich wyszukiwań.
  4. Otworzy się nowa strona z dokumentacją interfejsu API Chrome.

Powinien on wyglądać podobnie do tego:

Szybka dokumentacja interfejsu API otwierająca dokumentację interfejsu API w czasie wykonywania
Szybkie rozszerzenie interfejsu API otwierające interfejs Runtime API.

Otwieranie wskazówki dnia

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

Otwórz codzienny tip
Szybkie rozszerzenie interfejsu API – wskazówka dnia.

🎯 Możliwe ulepszenia

Na podstawie tego, czego się nauczyłeś(-aś) dzisiaj, spróbuj wykonać jedną z tych czynności:

  • Poznaj inny sposób implementowania sugestii w polu wyszukiwania.
  • Utwórz własny modal do wyświetlania wskazówki dotyczącej rozszerzenia.
  • Otwórz dodatkową stronę na stronach referencyjnych interfejsu API rozszerzeń internetowych 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 w 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: