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

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

Обзор

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

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

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

Данное руководство предполагает наличие у вас базового опыта веб-разработки. Мы рекомендуем ознакомиться с курсами «Основы разработки расширений» и «Привет, мир!» для ознакомления с основами разработки расширений.

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

Для начала создайте новую директорию с именем 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"
  }
}

Extensions register their service worker in the manifest, which only takes a single JavaScript file. There's no need to call navigator.serviceWorker.register() , like you would in a web page.

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

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

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

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

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

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

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

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

Через 30 секунд вы увидите сообщение "service worker (inactive)", что означает, что сервис-воркер завершил работу. Щелкните ссылку "service worker (inactive)", чтобы проверить это. Следующая анимация демонстрирует это.

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

Теперь давайте разберемся с расширением, чтобы понять, где искать ошибки. Один из способов сделать это — удалить ".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 и, следовательно, не могут использовать window.localStorage для хранения значений. Кроме того, сервис-воркеры представляют собой кратковременные среды выполнения; они многократно завершаются в течение сеанса работы браузера пользователя, что делает их несовместимыми с глобальными переменными. Вместо этого используйте chrome.storage.local , который хранит данные на локальном компьютере.

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

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

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

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

manifest.json:

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

Now, register the omnibox event listeners at the top level of the script. When the user enters the omnibox keyword ( api ) in the address bar followed by tab or space, Chrome will display a list of suggestions based on the keywords in storage. The onInputChanged() event, which takes the current user input and a suggestResult object, is responsible for populating these suggestions.

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() откроет соответствующую страницу справочника 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);
});

Функция 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, скрипт содержимого расширения обновляет страницу, добавляя совет дня. Он отправляет сообщение с запросом на получение совета дня от сервис-воркера.

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

manifest.json:

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

Создайте новый файл контента. Следующий код отправляет сообщение сервис-воркеру с запросом на подсказку. Затем добавляет кнопку, которая откроет всплывающее окно с подсказкой расширения. Этот код использует новый 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;
}

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

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, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js, и content.js

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

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

Откройте справочную страницу

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

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

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

Откройте чаевые дня

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

Открыто ежедневно для приема отходов.
Быстрое расширение API — совет дня.

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

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

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

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

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

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

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

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