コンテンツ索引

編集者草案、

このバージョン:
https://wicg.github.io/content-index/spec/
課題追跡:
GitHub
仕様内インライン
編集者:
Google
Google

概要

ウェブサイトがオフライン対応コンテンツをブラウザーに登録するためのAPI。

この文書のステータス

この仕様は、Web Platform Incubator Community Groupにより公開された。 これはW3C標準ではなく、W3C標準化過程上にもない。 W3C Community Contributor License Agreement (CLA)の下では、限定的なオプトアウトおよびその他の条件が適用されることに注意されたい。 W3C Community and Business Groupsについてさらに学ぶ。

1. 導入

現在、高品質なオフライン対応ウェブコンテンツは、ユーザーにとって容易に発見できるものではない。ユーザーがオフライン時に コンテンツを閲覧できるようにするには、どのウェブサイトがオフラインで動作するかを 知っている必要があるか、インストール済みのPWAが必要になる。 これは、利用可能なコンテンツを発見するための入口が存在しないため、優れたユーザー体験ではない。これに対処するため、 この仕様は、開発者が自身の特定のコンテンツについてブラウザーに伝えることを可能にする新しいAPIを 扱う。

コンテンツ索引により、ウェブサイトはオフライン対応コンテンツをブラウザーに登録できる。ブラウザーは その後、ウェブサイトのオフライン機能を改善し、ユーザーがオフライン時に閲覧できるコンテンツを提供できる。 このデータは、端末上の検索を改善し、閲覧履歴を補強するためにも使用できる。

このAPIを使用すると、次の例のユースケースについて、ユーザーがより容易にコンテンツを発見できるようになる:

1.1.

service worker内でオフラインニュース記事を登録する。
function deleteArticleResources(id) {
  return Promise.all([
    caches.open('offline-articles')
        .then(articlesCache => articlesCache.delete(`/article/${id}`)),
    // この関数が `contentdelete` イベントの結果として呼び出された場合、
    // これは何もしない。
    self.registration.index.delete(id),
  ]);
}

self.addEventListener('activate', event => {
  // service workerがアクティブ化されたとき、古いコンテンツを削除する。
  event.waitUntil(async function() {
    const descriptions = await self.registration.index.getAll();
    const oldDescriptions =
        descriptions.filter(description => shouldRemoveOldArticle(description));
    await Promise.all(
        oldDescriptions.map(description => deleteArticleResources(description.id)));
  }());
});

self.addEventListener('push', event => {
  const payload = event.data.json();

  // 記事を取得して保存し、その後それを登録する。
  event.waitUntil(async function() {
    const articlesCache = await caches.open('offline-articles');
    await articlesCache.add(`/article/${payload.id}`);
    await self.registration.index.add({
      id: payload.id,
      title: payload.title,
      description: payload.description,
      category: 'article',
      icons: payload.icons,
      url: `/article/${payload.id}`,
    });

    // 緊急の場合は通知を表示する。
  }());
});

self.addEventListener('contentdelete', event => {
  // ユーザーによる削除後、基礎となるコンテンツを消去する。
  event.waitUntil(deleteArticleResources(event.id));
});    

上記の例では、shouldRemoveOldArticleは開発者定義の関数である。

2. プライバシーに関する考慮事項

contentdeleteイベントを発火すると、ユーザーがページを離れた後にユーザーのIPアドレスが明らかになる可能性がある。 これを悪用すると、位置履歴の追跡に使用できる。ユーザーエージェントは、イベントの持続時間を制限することにより、 追跡を制限するべきである。

contentdeleteイベントを発火するとき、ユーザーエージェントは、ウェブサイトが新しいコンテンツを追加することを 防ぐべきである。 これにより、スパム的なウェブサイトが同じコンテンツを再追加すること、およびユーザーに削除したばかりのコンテンツが 表示されることを防ぐ。

登録済みコンテンツをすべて表示すると、 悪意あるウェブサイトが露出を最大化するために、自身のコンテンツでユーザーをスパムする原因となり得る。 ユーザーエージェントには、すべてのコンテンツを表示するのではなく、ユーザー体験の改善を目的とした ユーザーエージェント定義のシグナルの集合に基づいて、表示する 適切なコンテンツを選択することが強く推奨される。

3. 基盤

3.1. service worker登録への拡張

service worker登録は、さらに以下を持つ:

3.2. コンテンツ索引エントリー

コンテンツ索引 エントリーは、以下で構成される:

3.2.1. 表示

ユーザーエージェントは、entryコンテンツ索引 エントリーentry)として、service worker登録コンテンツ索引エントリー内に存在する限り、任意の時点でentry表示してもよい。

注: ユーザーエージェントは、ユーザーに表示されるエントリーが多すぎることを避けるため、表面化するコンテンツを制限するべきである。

コンテンツ索引 エントリーentry)を表示するには、ユーザーエージェントは 次の規則に従うユーザーインターフェイスを提示しなければならない:

3.2.2. 非表示

コンテンツ索引エントリーentry)を非表示にするには、ユーザーエージェントは、entryに対して表示を 実行することに関連付けられたすべてのUIを削除しなければならない。

4. アルゴリズム

4.1. コンテンツ索引 エントリーを削除する

entryコンテンツ索引エントリー)についてコンテンツ索引 エントリーを削除するには、次の手順を実行する:
  1. idを、entry記述idとする。

  2. contentIndexEntriesを、entryservice worker 登録コンテンツ索引エントリーとする。

  3. entryservice worker 登録エントリー編集キューに、次の手順をキューに入れる:

    1. entry非表示にする

    2. contentIndexEntries[id]を削除する

    3. entryについてcontent deleteイベントを発火する

4.2. コンテンツ索引 エントリーをアクティブ化する

entryコンテンツ索引エントリー)についてコンテンツ索引 エントリーをアクティブ化するには、次の手順を実行する:
  1. activeWorkerを、entryservice worker 登録active workerとする。

  2. activeWorkerがnullである場合、これらの手順を中止する。

  3. newContextを新しいトップレベル閲覧コンテキストとする。

  4. newContextWindow オブジェクトの環境設定オブジェクト担当イベントループ上で、ユーザー操作タスクソースを用いて、次の手順を実行するタスクをキューに入れる:

    1. HandleNavigate: newContextを、entry起動URLへ、例外有効および置換 有効ナビゲートする

    2. HandleNavigateとラベル付けされた手順で呼び出されたアルゴリズム手順が例外を投げる場合、 これらの手順を中止する。

    3. frameTypeを"`top-level`"とする。

    4. visibilityStateを、newContextアクティブ文書visibilityState 属性値とする。

    5. focusStateを、newContextアクティブ文書を 引数としてhas focus手順を実行した結果とする。

    6. ancestorOriginsListを、newContextアクティブ文書関連するグローバルオブジェクトLocation オブジェクトの祖先originリストの 関連付けられたリストとする。

    7. serviceWorkerEventLoopを、activeWorkerグローバルオブジェクトイベントループとする。

    8. serviceWorkerEventLoop上で、DOM操作 タスクソースを用いて、次の手順を実行するタスクをキューに入れる:

      1. newContextWindow オブジェクトの環境設定オブジェクト作成URLoriginが、activeWorkerorigin同じでない場合、これらの手順を中止する。

      2. newContextWindow オブジェクトの環境設定オブジェクトframeTypevisibilityStatefocusState、および ancestorOriginsListを引数として、Create Window Clientを実行する。

ここで新しい Browsing Contextを作成することは正しいことか? (issue

4.3. content deleteイベントを発火する

entryコンテンツ索引エントリー)についてcontent deleteイベントを 発火するには、entryservice worker登録上で、次のプロパティを用いて、 ContentIndexEventを使用する "contentdelete"という名前の機能イベントを発火する:
id

entry記述id

5. API

5.1. ServiceWorkerGlobalScopeへの拡張

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler oncontentdelete;
};

5.1.1. イベント

以下は、ServiceWorker インターフェイスを実装するすべてのオブジェクトにより、イベントハンドラーIDL属性として サポートされなければならないイベントハンドラー(および対応するイベントハンドラーイベント型)である:

イベントハンドラーイベント型 イベントハンドラー インターフェイス
contentdelete oncontentdelete ContentIndexEvent

5.2. ServiceWorkerRegistrationへの拡張

partial interface ServiceWorkerRegistration {
  [SameObject] readonly attribute ContentIndex index;
};

ServiceWorkerRegistrationは、 コンテンツ索引ContentIndex)を持ち、初期値は そのservice worker登録コンテキスト オブジェクトservice worker登録である 新しいContentIndexである。

index属性の取得子は、 コンテキストオブジェクトコンテンツ索引を返さなければならない。

5.3. ContentIndex

ContentIndex

現在のエンジン1つのみ。

FirefoxなしSafariなしChromeなし
OperaなしEdgeなし
Edge (Legacy)なしIEなし
Firefox for AndroidなしiOS SafariなしChrome for Android84+Android WebView84+Samsung InternetなしOpera Mobile60+
enum ContentCategory {
  "",
  "homepage",
  "article",
  "video",
  "audio",
};

dictionary ContentDescription {
  required DOMString id;
  required DOMString title;
  required DOMString description;
  ContentCategory category = "";
  sequence<ImageResource> icons = [];
  required USVString url;
};

[Exposed=(Window,Worker)]
interface ContentIndex {
  Promise<undefined> add(ContentDescription description);
  Promise<undefined> delete(DOMString id);
  Promise<sequence<ContentDescription>> getAll();
};

ContentIndex/getAll

現在のエンジン1つのみ。

FirefoxなしSafariなしChromeなし
OperaなしEdgeなし
Edge (Legacy)なしIEなし
Firefox for AndroidなしiOS SafariなしChrome for Android84+Android WebView84+Samsung InternetなしOpera Mobile60+

ContentIndexは、 service worker登録service worker登録)を持つ。

5.3.1. add()

ContentIndex/add

現在のエンジン1つのみ。

FirefoxなしSafariなしChromeなし
OperaなしEdgeなし
Edge (Legacy)なしIEなし
Firefox for AndroidなしiOS SafariなしChrome for Android84+Android WebView84+Samsung InternetなしOpera Mobile60+
add(description)メソッドは、呼び出されたとき、 新しいpromisepromiseを返し、 次の手順を並列に実行しなければならない:
  1. registrationを、コンテキスト オブジェクトservice worker登録とする。

  2. registrationactive workerがnullである場合、promiseTypeError拒否し、 これらの手順を中止する。

  3. descriptionidtitledescription、 またはurlのいずれかが空の文字列である場合、promiseTypeError拒否し、 これらの手順を中止する。

  4. launchURLを、コンテキストオブジェクト関連する設定オブジェクトAPI基底URLで、descriptionurlパースする結果とする。

    注: より狭いスコープを持つ新しいservice worker登録が 後に導入される可能性がある。

  5. matchedRegistrationを、launchURLを引数としてMatch Service Worker Registration アルゴリズムを実行した結果とする。

  6. matchedRegistrationregistrationと等しくない場合、promiseTypeError拒否し、これらの手順を中止する。

  7. registrationactive worker拡張イベントの集合FetchEvent含まない場合、 promiseTypeError拒否し、これらの手順を中止する。

  8. iconsを空のリストとする。

  9. 任意で、ユーザーエージェントはdescriptioniconsから使用するアイコンを選択してもよい。 その場合、正常にパースされた後の、descriptioniconsの 選択済みアイコンの各画像リソースresourceについて、 次の手順を実行する:

    1. 次のプロパティを持つ新しいリクエストを用いたfetchを待機した結果を responseとする:

      URL

      resourcesrc

      クライアント

      コンテキストオブジェクト関連する設定オブジェクト

      Keepaliveフラグ

      設定済み。

      宛先

      "`image`"。

      モード

      "`no-cors`"。

      資格情報モード

      "`include`"。

    2. responseネットワークエラーである場合、promiseTypeError拒否し、 これらの手順を中止する。

    3. responseを画像としてデコードできない場合、promiseTypeError拒否し、 これらの手順を中止する。

    4. responseicons付加する

  10. entryを、次を持つ新しいコンテンツ索引エントリーとする:

    記述

    description

    起動URL

    launchURL

    service worker 登録

    registration

    アイコン

    icons

  11. idを、descriptionidとする。

  12. contentIndexEntriesを、registrationコンテンツ索引 エントリーとする。

  13. registrationエントリー編集キューに、次の手順をキューに入れる:

    1. contentIndexEntries[id]を entry設定する

    2. 任意で、ユーザーエージェントはentry表示してもよい。

    3. promiseをundefinedで解決する

注: 既存のIDを持つ記述を追加すると、 前の値が上書きされる。

5.3.2. delete()

ContentIndex/delete

現在のエンジン1つのみ。

FirefoxなしSafariなしChromeなし
OperaなしEdgeなし
Edge (Legacy)なしIEなし
Firefox for AndroidなしiOS SafariなしChrome for Android84+Android WebView84+Samsung InternetなしOpera Mobile60+
delete(id)メソッドは、呼び出されたとき、 新しいpromisepromiseを返し、次の 手順を並列に実行しなければならない:
  1. registrationを、コンテキスト オブジェクトservice worker登録とする。

  2. contentIndexEntriesを、registrationコンテンツ索引 エントリーとする。

  3. registrationエントリー編集キューに、次の手順をキューに入れる:

    1. contentIndexEntries[id]を非表示にする

    2. contentIndexEntries[id]を 削除する

    3. promiseをundefinedで解決する

5.3.3. getAll()

getAll()メソッドは、呼び出されたとき、新しいpromisepromiseを返し、 次の手順を並列に実行しなければならない:
  1. registrationを、コンテキスト オブジェクトservice worker登録とする。

  2. contentIndexEntriesを、registrationコンテンツ索引 エントリーとする。

  3. descriptionsを空のリストとする。

  4. registrationエントリー編集キューに、次の手順をキューに入れる:

    1. contentIndexEntriesの各id → entryについて反復する:

      1. entry記述descriptions付加する

    2. promisedescriptions解決する

5.4. ContentIndexEvent

ContentIndexEvent

現在のエンジン1つのみ。

FirefoxなしSafariなしChromeなし
OperaなしEdgeなし
Edge (Legacy)なしIEなし
Firefox for AndroidなしiOS SafariなしChrome for Android84+Android WebView84+Samsung InternetなしOpera Mobile60+
dictionary ContentIndexEventInit : ExtendableEventInit {
  required DOMString id;
};

[Exposed=ServiceWorker]
interface ContentIndexEvent : ExtendableEvent {
  constructor(DOMString type, ContentIndexEventInit init);
  readonly attribute DOMString id;
};

適合性

文書の 規約

適合性要件は、記述的な表明と RFC 2119用語の組み合わせによって表現される。 この文書の規範的部分におけるキーワード “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、 “MAY”、および“OPTIONAL”は、 RFC 2119で記述されているように解釈される。 ただし、読みやすさのため、 これらの語は、この仕様ではすべて大文字では表示されない。

この仕様のすべてのテキストは、 明示的に非規範的と印された節、例、および注を除き、規範的である。 [RFC2119]

この仕様における例は、“for example”という語で導入されるか、 規範的なテキストから class="example"により区別される。 次のように:

これは参考情報の例の一例である。

参考情報の注は、“Note”という語で始まり、 規範的なテキストから class="note"により区別される。 次のように:

注、これは参考情報の注である。

適合 アルゴリズム

アルゴリズムの一部として命令形で表現される要件 (たとえば“先頭の空白文字を取り除く” または“falseを返してこれらの手順を中止する”など)は、 そのアルゴリズムを導入する際に使用されるキーワード (“must”、“should”、“may”など)の意味で 解釈される。

アルゴリズムまたは特定の手順として表現される適合性要件は、 最終結果が等価である限り、 任意の方法で実装できる。 特に、この仕様で定義されるアルゴリズムは、 理解しやすいことを意図しており、 性能が高いことを意図していない。 実装者には最適化が奨励される。

索引

この仕様により定義される 用語

参照により定義される 用語

参照文献

規範的参照文献

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[IMAGE-RESOURCE]
Aaron Gustafson; Rayan Kanso; Marcos Caceres. Image Resource. 2021年3月29日. WD. URL: https://www.w3.org/TR/image-resource/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PAGE-VISIBILITY]
Jatinder Mann; Arvind Jain. Page Visibility (Second Edition). 2013年10月29日. REC. URL: https://www.w3.org/TR/page-visibility/
[RFC2119]
S. Bradner. RFCにおいて要件レベルを示すために使用する キーワード. 1997年3月. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1. 2019年11月19日. CR. URL: https://www.w3.org/TR/service-workers-1/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Boris Zbarsky. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/

IDL索引

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler oncontentdelete;
};

partial interface ServiceWorkerRegistration {
  [SameObject] readonly attribute ContentIndex index;
};

enum ContentCategory {
  "",
  "homepage",
  "article",
  "video",
  "audio",
};

dictionary ContentDescription {
  required DOMString id;
  required DOMString title;
  required DOMString description;
  ContentCategory category = "";
  sequence<ImageResource> icons = [];
  required USVString url;
};

[Exposed=(Window,Worker)]
interface ContentIndex {
  Promise<undefined> add(ContentDescription description);
  Promise<undefined> delete(DOMString id);
  Promise<sequence<ContentDescription>> getAll();
};

dictionary ContentIndexEventInit : ExtendableEventInit {
  required DOMString id;
};

[Exposed=ServiceWorker]
interface ContentIndexEvent : ExtendableEvent {
  constructor(DOMString type, ContentIndexEventInit init);
  readonly attribute DOMString id;
};

課題索引

ここで新しいBrowsing Contextを作成することは正しいことか? (issue