Service Worker でイベントを処理する

拡張機能 Service Worker のコンセプトに関するチュートリアル

概要

このチュートリアルでは、Chrome 拡張機能の Service Worker の概要について説明します。その一環として チュートリアルでは、ユーザーが Chrome API リファレンスにすばやくアクセスできる拡張機能を作成します。 アドレスバーで移動することもできます。ここでは以下について学びます。

  • Service Worker を登録し、モジュールをインポートします。
  • 拡張機能の Service Worker をデバッグします。
  • 状態を管理し、イベントを処理する。
  • 定期的なイベントをトリガーします。
  • コンテンツ スクリプトを使用してやり取りする。

始める前に

このガイドは、基本的なウェブ開発の経験があることを前提としています。おすすめの方法: 拡張機能 101Hello World: いくつかあります。

拡張機能を作成する

まず、拡張機能ファイルを格納する quick-api-reference という名前の新しいディレクトリを作成します。 GitHub サンプル リポジトリからソースコードをダウンロードします。

ステップ 1: Service Worker を登録する

プロジェクトのルートにマニフェスト ファイルを作成し、次のコードを追加します。

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

拡張機能は、マニフェストに Service Worker を登録します。マニフェストは JavaScript ファイルを 1 つだけ受け取ります。 ウェブページのように navigator.serviceWorker.register() を呼び出す必要はありません。

images フォルダを作成し、そこにアイコンをダウンロードします。

マニフェスト内の拡張機能のメタデータアイコンについて詳しくは、読み上げ時間のチュートリアルの最初のステップをご覧ください。

ステップ 2: 複数の Service Worker モジュールをインポートする

この Service Worker は 2 つの機能を実装しています。保守性を高めるために、各機能を個別のモジュールに実装します。まず、マニフェストで Service Worker を ES モジュールとして宣言する必要があります。これにより、Service Worker にモジュールをインポートできます。

manifest.json:

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

service-worker.js ファイルを作成し、2 つのモジュールをインポートします。

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 で複数のファイルをインポートするその他の方法については、スクリプトのインポートをご覧ください。

省略可: Service Worker のデバッグ

Service Worker ログを見つけて、ログがいつ終了したかを確認する方法を解説します。まず、パッケージ化されていない拡張機能を読み込むの手順に沿って操作します。

30 秒後に「Service Worker (inactive)」と表示されるService Worker が終了したことを意味します。[Service Worker(非アクティブ)] をリンクをクリックして詳細を確認します。次のアニメーションは、これを示しています。

Service Worker を調べると、起動したことに気づきましたか?devtools で Service Worker を開くと、Service Worker はアクティブのままになります。Service Worker が終了したときに拡張機能が正しく動作するように、DevTools は必ず閉じてください。

では、拡張機能を破って、エラーの場所を確認します。「.js」を削除して、service-worker.js ファイルの './sw-omnibox.js' インポートから。Chrome で Service Worker を登録できません。

chrome://extensions に戻り、拡張機能を更新します。次の 2 つのエラーが表示されます。

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

拡張機能の Service Worker をデバッグするその他の方法については、拡張機能のデバッグをご覧ください。

ステップ 4: 状態を初期化する

Service Worker が不要な場合、Chrome は Service Worker をシャットダウンします。Service Worker セッション間で状態を保持するには、chrome.storage API を使用します。ストレージにアクセスするには、マニフェストで権限をリクエストする必要があります。

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

Service Worker にはウィンドウ オブジェクトに直接アクセスできないため、 window.localStorage: 値を保存します。また、Service Worker は有効期間が短い実行環境でもあります。 ユーザーのブラウザ セッション中に繰り返し終了するため、 使用します。代わりに、ローカルマシンにデータを保存する chrome.storage.local を使用してください。

拡張機能 Service Worker のその他のストレージ オプションについては、グローバル変数ではなくデータを保持するをご覧ください。

ステップ 5: イベントを登録する

すべてのイベント リスナーを、Service Worker のグローバル スコープに静的に登録する必要があります。つまり、イベント リスナーを非同期関数でネストすべきではありません。これにより、Chrome では Service Worker が再起動した場合でもすべてのイベント ハンドラが復元されます。

この例では chrome.omnibox API を使用しますが、まずマニフェストでアドレスバーのキーワード トリガーを宣言する必要があります。

manifest.json:

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

次に、スクリプトのトップレベルにアドレスバーのイベント リスナーを登録します。ユーザーがアドレスバーにアドレスバーのキーワード(api)を入力してから Tab キーまたはスペースを入力すると、ストレージ内のキーワードに基づいて候補のリストが表示されます。現在のユーザー入力と suggestResult オブジェクトを受け取る onInputChanged() イベントは、これらの候補を生成する役割を担います。

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 が失敗する可能性があります。 ワーカーが終了します代わりに、拡張機能は chrome.alarms API を使用できます。

まず、マニフェストで "alarms" 権限をリクエストします。また、リモートでホストされている場所から拡張機能のヒントを取得するには、ホスト権限をリクエストする必要があります。

manifest.json:

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

この拡張機能はすべてのヒントを取得し、ランダムに 1 つ選択してストレージに保存します。チップを更新するために 1 日 1 回トリガーされるアラームを作成します。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: 他のコンテキストと通信する

拡張機能は、コンテンツ スクリプトを使用して、ページのコンテンツを読み取り、変更します。ユーザーが Chrome API リファレンス ページにアクセスすると、拡張機能のコンテンツ スクリプトによってページが更新されます。Service Worker にその日のチップをリクエストするメッセージを送信します。

まず、マニフェストでコンテンツ スクリプトを宣言し、Chrome API リファレンス ドキュメントに対応する一致パターンを追加します。

manifest.json:

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

新しいコンテンツ ファイルを作成します。次のコードは、チップをリクエストするメッセージを Service Worker に送信します。次に、拡張機能のヒントを含むポップオーバーを開くボタンを追加します。このコードは、新しいウェブ プラットフォームの 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;
}

最後のステップでは、メッセージ ハンドラを Service Worker に追加します。このメッセージ ハンドラは、コンテンツ スクリプトに毎日のヒントとともに返信します。

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. Tab キーを押します使用できます。
  3. API の完全な名前を入力します。
    • または、過去の検索内容の一覧から選択します
  4. 新しいページで Chrome API リファレンス ページが表示されます。

次のようになります。

<ph type="x-smartling-placeholder">
</ph> ランタイム API リファレンスを開くクイック API リファレンス <ph type="x-smartling-placeholder">
</ph> Runtime API を開く Quick API 拡張機能。

今日のヒントを開く

ナビゲーション バーにある [Tip] ボタンをクリックして、拡張機能のヒントを開きます。

<ph type="x-smartling-placeholder">
</ph> 今日のヒントを開く <ph type="x-smartling-placeholder">
</ph> 今日のヒントを提供するクイック API 拡張機能。
で確認できます。

🙁? 潜在的な強化

本日の学習内容に基づいて、次のいずれかを実施してください。

  • アドレスバーの提案機能を実装する別の方法を確認する。
  • 拡張機能のヒントを表示するための独自のカスタム モーダルを作成します。
  • MDN の Web Extensions リファレンス API ページへの追加ページを開きます。

構築を続けましょう。

以上でこのチュートリアルは終了です 🎉?。他のコースを修了してスキルをレベルアップさせましょう 初心者向けチュートリアル:

広告表示オプション 学習内容
読み上げ時間 特定のページセットに要素を自動的に挿入するため。
タブ マネージャー ブラウザのタブを管理するポップアップを作成するため。
フォーカス モード 拡張機能のアクションをクリックした後に、現在のページでコードを実行する。

引き続き探求を

拡張機能 Service Worker の学習プログラムを続行するには、次の記事をご覧ください。