Content Indexing API を使ったオフライン対応ページのインデックス登録

サービス ワーカーを有効にして、オフラインで動作するページをブラウザに通知する

プログレッシブ ウェブアプリを使用すると、ネットワーク接続の現在の状態に関係なく、ユーザーが関心を持つ情報(画像、動画、記事など)にアクセスできます。Service WorkerCache Storage APIIndexedDB などのテクノロジーは、ユーザーが PWA を直接操作する際にデータを保存して提供するための構成要素を提供します。ただし、高品質のオフライン ファースト PWA を構築するのは、ほんの一部にすぎません。オフラインでもウェブアプリのコンテンツを利用できることをユーザーが認識していないと、その機能を実装するために費やした労力は十分に活用されません。

これは検出の問題です。ユーザーが利用可能なコンテンツを見つけて表示できるように、PWA でオフライン対応コンテンツをユーザーに知らせるにはどうすればよいでしょうか。Content Indexing API は、この問題の解決策です。このソリューションのデベロッパー向け部分は、サービス ワーカーの拡張機能です。これにより、デベロッパーは、オフライン対応ページの URL とメタデータを、ブラウザによって管理されるローカル インデックスに追加できます。この機能強化は Chrome 84 以降で利用できます。

インデックスに PWA のコンテンツと、インストールされている他の PWA のコンテンツが入力されると、次のようにブラウザに表示されます。

Chrome の新しいタブページの [ダウンロード] メニュー項目のスクリーンショット。
まず、Chrome の新しいタブページで [ダウンロード] メニュー項目を選択します。
インデックスに追加されたメディアと記事。
インデックスに追加されたメディアと記事は、[おすすめの記事] セクションに表示されます。

また、Chrome では、ユーザーがオフラインであることを検出した場合、コンテンツをプロアクティブに推奨できます。

Content Indexing API は、コンテンツをキャッシュに保存する代替方法ではありません。これは、Service Worker によってすでにキャッシュされているページに関するメタデータを提供する方法です。これにより、ユーザーがページを閲覧しようとする可能性が高いときに、ブラウザがページを表示できます。Content Indexing API は、キャッシュに保存されたページの見つけやすさに役立ちます。

実例を見る

Content Indexing API を使いこなすには、サンプル アプリケーションを試すのが一番です。

  1. サポートされているブラウザとプラットフォームを使用していることを確認します。現在、Android 版 Chrome 84 以降に限定されています。about://version に移動して、実行している Chrome のバージョンを確認します。
  2. https://contentindex.dev にアクセスします。
  3. リストの 1 つ以上の項目の横にある [+] ボタンをクリックします。
  4. (省略可)デバイスの Wi-Fi 接続とモバイルデータ接続を無効にするか、機内モードを有効にして、ブラウザがオフラインになることをシミュレートします。
  5. Chrome のメニューから [ダウンロード] を選択し、[おすすめの記事] タブに切り替えます。
  6. 以前に保存したコンテンツを参照します。

GitHub にあるサンプル アプリケーションのソースを確認する。

別のサンプル アプリケーションである Scrapbook PWA は、Content Indexing API と Web Share Target API の使用を示しています。このコードでは、Cache Storage API を使用してウェブアプリで保存されたアイテムと Content Indexing API の同期を維持する手法の例を示しています。

API の使用

この API を使用するには、アプリにサービス ワーカーとオフラインで移動可能な URL が必要です。ウェブアプリに Service Worker がまだない場合は、Workbox ライブラリを使用して簡単に作成できます。

オフライン対応としてインデックスに登録できる URL のタイプは次のうちどれですか。

この API は、HTML ドキュメントに対応する URL のインデックス登録をサポートしています。たとえば、キャッシュに保存されたメディア ファイルの URL は直接インデックスに登録できません。代わりに、メディアを表示するオフラインで機能するページの URL を指定する必要があります。

推奨されるパターンは、基になるメディア URL をクエリ パラメータとして受け取る「閲覧者」HTML ページを作成し、ファイルの内容を表示することです。追加のコントロールやコンテンツがページに追加される可能性があります。

ウェブアプリは、現在の Service Worker のスコープ内にある URL のみをコンテンツ インデックスに追加できます。つまり、ウェブアプリは、まったく異なるドメインに属する URL をコンテンツ インデックスに追加できませんでした。

概要

Content Indexing API は、メタデータの追加、一覧表示、削除の 3 つのオペレーションをサポートしています。これらのメソッドは、ServiceWorkerRegistration インターフェースに追加された新しいプロパティ index から公開されます。

コンテンツのインデックス登録の最初のステップは、現在の ServiceWorkerRegistration への参照を取得することです。最も簡単な方法は navigator.serviceWorker.ready を使用することです。

const registration = await navigator.serviceWorker.ready;

// Remember to feature-detect before using the API:
if ('index' in registration) {
  // Your Content Indexing API code goes here!
}

ウェブページ内ではなく、サービス ワーカー内から Content Indexing API を呼び出す場合は、registration を介して ServiceWorkerRegistration を直接参照できます。ServiceWorkerGlobalScope. の一部としてすでに定義されています

インデックスへの追加

add() メソッドを使用して、URL とそれに関連するメタデータをインデックスに登録します。アイテムをインデックスに追加するタイミングは任意で選択できます。入力([オフラインに保存] ボタンのクリックなど)に応じてインデックスに追加することもできます。また、定期的なバックグラウンド同期などのメカニズムによってキャッシュに保存されたデータが更新されるたびに、アイテムを自動的に追加することもできます。

await registration.index.add({
  // Required; set to something unique within your web app.
  id: 'article-123',

  // Required; url needs to be an offline-capable HTML page.
  url: '/articles/123',

  // Required; used in user-visible lists of content.
  title: 'Article title',

  // Required; used in user-visible lists of content.
  description: 'Amazing article about things!',

  // Required; used in user-visible lists of content.
  icons: [{
    src: '/img/article-123.png',
    sizes: '64x64',
    type: 'image/png',
  }],

  // Optional; valid categories are currently:
  // 'homepage', 'article', 'video', 'audio', or '' (default).
  category: 'article',
});

エントリを追加しても、コンテンツ インデックスにのみ影響します。キャッシュには何も追加されません。

エッジケース: アイコンが fetch ハンドラに依存している場合は、window コンテキストから add() を呼び出す

add() を呼び出すと、Chrome は各アイコンの URL をリクエストし、インデックスに登録されたコンテンツのリストを表示する際に使用するアイコンのコピーを確実に取得します。

  • window コンテキスト(つまりウェブページ)から add() を呼び出すと、このリクエストによって Service Worker で fetch イベントがトリガーされます。

  • サービス ワーカー内で(別のイベント ハンドラ内など)add() を呼び出すと、リクエストによってサービス ワーカーの fetch ハンドラがトリガーされることはありません。アイコンは、サービス ワーカーを介さずに直接取得されます。アイコンが fetch ハンドラに依存している場合は、この点に注意してください。アイコンがローカル キャッシュにのみ存在し、ネットワークに存在しない場合があります。その場合は、window コンテキストからのみ add() を呼び出すようにしてください。

インデックスの内容を一覧表示する

getAll() メソッドは、インデックス付きエントリとそのメタデータのイテラブルリストの Promise を返します。返されるエントリには、add() で保存されたすべてのデータが含まれます。

const entries = await registration.index.getAll();
for (const entry of entries) {
  // entry.id, entry.launchUrl, etc. are all exposed.
}

インデックスからアイテムを削除する

インデックスからアイテムを削除するには、削除するアイテムの id を指定して delete() を呼び出します。

await registration.index.delete('article-123');

delete() の呼び出しはインデックスにのみ影響します。キャッシュから何も削除されません。

ユーザー削除イベントの処理

ブラウザにインデックス登録されたコンテンツが表示されるとき、削除メニュー アイテムを含む独自のユーザー インターフェースが含まれ、以前にインデックス登録されたコンテンツの表示を終了したことをユーザーが示すことができます。Chrome 80 の削除インターフェースは次のとおりです。

削除メニュー項目。

ユーザーがそのメニュー項目を選択すると、ウェブアプリの Service Worker が contentdelete イベントを受け取ります。このイベントの処理は任意ですが、サービス ワーカーが、ユーザーが使用を終了したことを示すローカルにキャッシュに保存されたメディア ファイルなどのコンテンツを「クリーンアップ」する機会となります。

contentdelete ハンドラ内で registration.index.delete() を呼び出す必要はありません。イベントがトリガーされた場合、関連するインデックスの削除はブラウザによってすでに実行されています。

self.addEventListener('contentdelete', (event) => {
  // event.id will correspond to the id value used
  // when the indexed content was added.
  // Use that value to determine what content, if any,
  // to delete from wherever your app stores it—usually
  // the Cache Storage API or perhaps IndexedDB.
});

API 設計に関するフィードバック

API に不便な点や、想定どおりに動作しない点はありますか?または、アイデアを実装するために必要な要素が不足しているでしょうか。

Content Indexing API の説明用 GitHub リポジトリで問題を報告するか、既存の問題にコメントを追加してください。

実装に関する問題

Chrome の実装にバグは見つかりましたか?

https://new.crbug.com でバグを報告します。できるだけ詳細な情報を含め、再現手順を簡単に説明します。[コンポーネント] を Blink>ContentIndexing に設定します。

API を使用する予定ですか?

ウェブアプリで Content Indexing API を使用する予定ですか?公開サポートは、Chrome が機能を優先付けするうえで役立ちます。また、他のブラウザ ベンダーに、サポートがどれほど重要であるかを示します。

  • ハッシュタグ #ContentIndexingAPI を使用して @ChromiumDev にツイートし、使用する場所や方法に関する詳細情報を送信します。

コンテンツのインデックス作成は、セキュリティとプライバシーにどのような影響を与えますか?

W3C のセキュリティとプライバシーに関するアンケートに対する回答で提供された回答をご確認ください。他にご不明な点がございましたら、プロジェクトの GitHub リポジトリでディスカッションをお気軽に開始してください。

ヒーロー画像(Maksym Kaharlytskyi 氏、Unsplash より)