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

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

Обзор

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

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

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

В этом руководстве предполагается, что у вас есть базовый опыт веб-разработки. Мы рекомендуем просмотреть Extensions 101 и Hello World , чтобы получить представление о разработке расширений.

Создайте расширение

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

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

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

манифест.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-модуль в нашем манифесте, что позволит нам импортировать модули в наш сервис-воркер:

манифест.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");

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

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

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

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

Вы заметили, что проверка сервисника разбудила его? Открытие сервисного работника в инструментах разработчика сохранит его активным. Чтобы убедиться, что ваше расширение работает правильно после завершения работы вашего сервис-воркера, не забудьте закрыть 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 для сохранения состояния во время сеансов сервис-воркера. Для доступа к хранилищу нам нужно запросить разрешение в манифесте:

манифест.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 в манифесте:

манифест.json:

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

Теперь зарегистрируйте прослушиватели событий омнибокса на верхнем уровне скрипта. Когда пользователь вводит ключевое слово омнибокса ( api ) в адресную строку, а затем табуляцию или пробел, 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" в манифесте. Кроме того, чтобы получить советы по расширению из удаленного расположения, вам необходимо запросить разрешение хоста :

манифест.json:

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

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

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

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

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

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

манифест.json:

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

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

контент.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;
  }
});

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

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

Содержимое папки расширения: папка images, манифест.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 среды выполнения.
Расширение Quick API, открывающее API среды выполнения.

Откройте совет дня

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

Открыть ежедневный совет в
Расширение быстрого API, открывающее совет дня.

🎯 Возможные улучшения

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

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

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

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

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

Продолжить изучение

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