Обработка событий с помощью сервисных работников

Учебное пособие, охватывающее концепции работников службы расширения

Обзор

В этом руководстве представлено введение в сервис-воркеры расширений Chrome. В рамках этого руководства вы создадите расширение, которое позволит пользователям быстро переходить к справочным страницам API Chrome с помощью омнибокса. Вы узнаете, как:

  • Зарегистрируйте своего сервисного работника и импортируйте модули.
  • Выполните отладку вашего работника службы расширения.
  • Управление состоянием и обработка событий.
  • Запускать периодические события.
  • Общайтесь с помощью контент-скриптов.

Прежде чем начать

Это руководство предполагает наличие у вас базового опыта веб-разработки. Рекомендуем ознакомиться с курсами «Расширения 101» и «Hello World» для получения вводных знаний о разработке расширений.

Построить расширение

Начните с создания нового каталога с именем quick-api-reference для хранения файлов расширения или загрузите исходный код из нашего репозитория примеров GitHub .

Шаг 1: Зарегистрируйте сервисного работника

Создайте файл манифеста в корне проекта и добавьте следующий код:

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"
  }
}

Расширения регистрируют свой сервис-воркер в манифесте, который занимает всего один JavaScript-файл. Нет необходимости вызывать navigator.serviceWorker.register() , как это делается на веб-странице.

Создайте папку images , а затем загрузите в нее иконки .

Ознакомьтесь с первыми шагами руководства по чтению, чтобы узнать больше о метаданных расширения и значках в манифесте.

Шаг 2: Импорт нескольких модулей Service Worker

Наш сервис-воркер реализует две функции. Для удобства поддержки мы реализуем каждую функцию в отдельном модуле. Во-первых, нам нужно объявить сервис-воркер как модуль ES в нашем манифесте, что позволит нам импортировать модули в наш сервис-воркер:

manifest.json:

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

Создайте файл service-worker.js и импортируйте два модуля:

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

Создайте эти файлы и добавьте в каждый из них журнал консоли.

sw-omnibox.js:

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

sw-tips.js:

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

См. раздел Импорт скриптов , чтобы узнать о других способах импорта нескольких файлов в Service Worker.

Необязательно: отладка сервисного работника

Я объясню, как найти логи сервис-воркера и определить момент его завершения. Сначала следуйте инструкциям по загрузке распакованного расширения .

Через 30 секунд вы увидите сообщение «Service Worker (неактивен)», что означает, что сервис-воркер завершил работу. Нажмите на ссылку «Service Worker (неактивен)», чтобы просмотреть её. Это показано на следующей анимации.

Вы заметили, что проверка сервис-воркера активировала его? Открытие сервис-воркера в DevTools сохранит его активным. Чтобы убедиться, что ваше расширение работает корректно после завершения работы сервис-воркера, не забудьте закрыть DevTools.

Теперь сломайте расширение, чтобы узнать, где искать ошибки. Один из способов сделать это — удалить «.js» из импорта './sw-omnibox.js' в файле service-worker.js . Chrome не сможет зарегистрировать сервис-воркер.

Вернитесь на страницу chrome://extensions и обновите расширение. Вы увидите две ошибки:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Дополнительные способы отладки работника службы расширений см. в разделе Отладка расширений .

Шаг 4: Инициализация состояния

Chrome отключит сервис-воркеры, если они не нужны. Мы используем API chrome.storage для сохранения состояния между сеансами сервис-воркеров. Для доступа к хранилищу необходимо запросить разрешение в манифесте:

manifest.json:

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

Сначала сохраните предложения по умолчанию в хранилище. Мы можем инициализировать состояние при первой установке расширения, прослушивая событие 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']
    });
  }
});

Сервис-воркеры не имеют прямого доступа к объекту окна и, следовательно, не могут использовать window.localStorage для хранения значений. Кроме того, сервис-воркеры — это кратковременные среды выполнения; они многократно завершаются в течение сеанса браузера пользователя, что делает их несовместимыми с глобальными переменными. Вместо этого используйте chrome.storage.local , который хранит данные на локальном компьютере.

Ознакомьтесь с разделом Сохранение данных вместо использования глобальных переменных, чтобы узнать о других вариантах хранения для работников служб расширения.

Шаг 5: Зарегистрируйте свои мероприятия

Все обработчики событий должны быть статически зарегистрированы в глобальной области видимости сервис-воркера. Другими словами, обработчики событий не должны быть вложены в асинхронные функции. Таким образом, Chrome может гарантировать восстановление всех обработчиков событий в случае перезагрузки сервис-воркера.

В этом примере мы будем использовать API chrome.omnibox , но сначала нам необходимо объявить триггер ключевого слова omnibox в манифесте:

manifest.json:

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

Теперь зарегистрируйте прослушиватели событий омнибокса на верхнем уровне скрипта. Когда пользователь вводит ключевое слово омнибокса ( api ) в адресной строке, а затем нажимает клавишу Tab или пробел, Chrome отображает список подсказок на основе ключевых слов из хранилища. Событие onInputChanged() , которое принимает текущий пользовательский ввод и объект 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);
});

После того как пользователь выберет предложение, onInputEntered() откроет соответствующую страницу справки Chrome API.

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

Функция updateHistory() принимает входные данные омнибокса и сохраняет их в storage.local . Таким образом, последний поисковый запрос можно будет использовать позже в качестве подсказки для омнибокса.

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

Шаг 6: Настройте повторяющееся событие

Методы setTimeout() или setInterval() обычно используются для выполнения отложенных или периодических задач. Однако эти API могут давать сбои, поскольку планировщик отменяет таймеры при завершении работы сервис-воркера. Вместо этого расширения могут использовать API chrome.alarms .

Начните с запроса разрешения "alarms" в манифесте:

manifest.json:

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

Расширение соберёт все советы, выберет один случайным образом и сохранит его в хранилище. Мы создадим будильник, который будет срабатывать раз в день, чтобы обновить совет. Будильник не сохраняется при закрытии Chrome. Поэтому нам нужно проверить наличие будильника и создать его, если его нет.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://chrome.dev/f/extension_tips/');
  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);

Шаг 7: Общайтесь с другими контекстами

Расширения используют скрипты контента для чтения и изменения содержимого страницы. Когда пользователь посещает страницу со справочной информацией по API Chrome, скрипт контента расширения обновляет страницу, добавляя на неё совет дня. Он отправляет сообщение с запросом совета дня у сервис-воркера.

Начните с объявления скрипта содержимого в манифесте и добавьте шаблон соответствия, соответствующий справочной документации Chrome API .

manifest.json:

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

Создайте новый файл контента. Следующий код отправляет сообщение сервисному работнику с запросом подсказки. Затем добавляет кнопку, которая открывает всплывающее окно с подсказкой расширения. Этот код использует новый API веб-платформы Popover .

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

Последний шаг — добавление обработчика сообщений в наш сервис-воркер, который отправляет ответ на контент-скрипт с ежедневными чаевыми.

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

Проверьте, работает ли это

Убедитесь, что структура файла вашего проекта выглядит следующим образом:

Содержимое папки расширения: папка с изображениями, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js и content.js

Загрузите свое расширение локально

Чтобы загрузить распакованное расширение в режиме разработчика, следуйте инструкциям в разделе Hello world .

Открыть страницу со справочной информацией

  1. Введите ключевое слово «api» в адресную строку браузера.
  2. Нажмите «tab» или «пробел».
  3. Введите полное имя API.
    • ИЛИ выберите из списка прошлых поисков
  4. Откроется новая страница со справочной информацией по API Chrome.

Это должно выглядеть так:

Краткий справочник по API: открытие справочника по API среды выполнения
Быстрое расширение API, открывающее Runtime API.

Откройте кончик дня

Нажмите кнопку «Подсказка», расположенную на панели навигации, чтобы открыть подсказку по расширению.

Открыто ежедневно в
Быстрое расширение API открывает совет дня.

🎯 Потенциальные улучшения

Исходя из того, что вы узнали сегодня, попробуйте выполнить любое из следующих действий:

  • Изучите другой способ реализации предложений омнибокса.
  • Создайте собственное модальное окно для отображения подсказки по расширению.
  • Откройте дополнительную страницу к справочным страницам API веб-расширений MDN.

Продолжайте строить!

Поздравляю с завершением этого урока 🎉. Продолжайте совершенствовать свои навыки, проходя другие уроки для начинающих:

Расширение Чему вы научитесь
время чтения Для автоматической вставки элемента на определенный набор страниц.
Менеджер вкладок Создать всплывающее окно, управляющее вкладками браузера.
Режим фокусировки Для запуска кода на текущей странице после нажатия на действие расширения.

Продолжайте исследовать

Чтобы продолжить обучение специалистов по распространению знаний, мы рекомендуем вам изучить следующие статьи: