콘텐츠 색인

편집자 초안,

이 버전:
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. 예시

서비스 워커에서 오프라인 뉴스 기사를 등록합니다.
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 => {
  // 서비스 워커가 활성화되면 오래된 콘텐츠를 제거합니다.
  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 주소가 드러날 수 있습니다. 이를 악용하면 위치 기록을 추적하는 데 사용할 수 있습니다. 사용자 에이전트는 이벤트의 지속 시간을 제한하여 추적을 제한해야 합니다(SHOULD).

contentdelete 이벤트를 발생시킬 때, 사용자 에이전트는 웹사이트가 새 콘텐츠를 추가하지 못하도록 방지해야 합니다(SHOULD). 이는 스팸성 웹사이트가 같은 콘텐츠를 다시 추가하는 것과 사용자가 방금 삭제한 콘텐츠를 다시 보게 되는 것을 방지합니다.

등록된 모든 콘텐츠를 표시하면 악의적인 웹사이트가 노출을 최대화하기 위해 사용자에게 콘텐츠를 스팸처럼 표시할 수 있습니다. 사용자 에이전트는 모든 콘텐츠를 노출하지 말고, 사용자 경험 개선을 목표로 하는 사용자 에이전트 정의 신호 집합에 기반하여 표시할 적절한 콘텐츠를 선택할 것을 강력히 권장합니다.

3. 인프라

3.1. 서비스 워커 등록에 대한 확장

서비스 워커 등록은 추가로 다음을 가집니다:

3.2. 콘텐츠 색인 항목

콘텐츠 색인 항목은 다음으로 구성됩니다:

3.2.1. 표시

사용자 에이전트는 entry서비스 워커 등록content index entries 안에 존재하는 한, 언제든 콘텐츠 색인 항목 (entry)을 표시할 수 있습니다(MAY).

참고: 사용자 에이전트는 사용자에게 너무 많은 항목을 보이지 않도록 노출되는 콘텐츠를 제한해야 합니다.

콘텐츠 색인 항목 (entry)을 표시하려면, 사용자 에이전트는 다음 규칙을 따르는 사용자 인터페이스를 제시해야 합니다(MUST):

3.2.2. 표시 해제

콘텐츠 색인 항목 (entry)을 표시 해제하려면, 사용자 에이전트는 entry에서 표시를 실행하는 것과 관련된 모든 UI를 제거해야 합니다(MUST).

4. 알고리즘

4.1. 콘텐츠 색인 항목 삭제

entry (콘텐츠 색인 항목)에 대해 콘텐츠 색인 항목을 삭제하려면, 다음 단계를 실행합니다:
  1. identrydescriptionid로 둡니다.

  2. contentIndexEntriesentryservice worker registrationcontent index entries로 둡니다.

  3. entryservice worker registrationentry edit queue다음 단계를 큐에 넣습니다:

    1. entry표시 해제합니다.

    2. contentIndexEntries[id]를 제거합니다.

    3. entry에 대해 콘텐츠 삭제 이벤트 발생을 수행합니다.

4.2. 콘텐츠 색인 항목 활성화

entry (콘텐츠 색인 항목)에 대해 콘텐츠 색인 항목을 활성화하려면, 다음 단계를 실행합니다:
  1. activeWorkerentryservice worker registrationactive worker로 둡니다.

  2. activeWorker가 null이면 이 단계를 중단합니다.

  3. newContext를 새 최상위 탐색 컨텍스트로 둡니다.

  4. newContextWindow 객체의 환경 설정 객체책임 이벤트 루프에서 사용자 상호작용 태스크 소스를 사용하여 다음 단계를 실행하도록 태스크를 큐에 넣습니다:

    1. HandleNavigate: 탐색을 수행하여, newContextentrylaunch url로 이동시키며, 예외 활성화대체 활성화를 사용합니다.

    2. HandleNavigate로 표시된 단계에서 호출된 알고리즘 단계가 예외를 던지면, 이 단계를 중단합니다.

    3. frameType을 "`top-level`"로 둡니다.

    4. visibilityStatenewContext활성 문서visibilityState 속성 값으로 둡니다.

    5. focusStatenewContext활성 문서를 인수로 하여 포커스 있음 단계를 실행한 결과로 둡니다.

    6. ancestorOriginsListnewContext활성 문서관련 전역 객체Location 객체의 조상 출처 목록의 관련 목록으로 둡니다.

    7. serviceWorkerEventLoopactiveWorker전역 객체이벤트 루프로 둡니다.

    8. serviceWorkerEventLoop에서 DOM 조작 태스크 소스를 사용하여 다음 단계를 실행하도록 태스크를 큐에 넣습니다:

      1. newContextWindow 객체의 환경 설정 객체생성 URL출처activeWorker출처동일하지 않으면, 이 단계를 중단합니다.

      2. newContextWindow 객체의 환경 설정 객체, frameType, visibilityState, focusStateancestorOriginsList를 인수로 하여 Create Window Client를 실행합니다.

여기서 새 Browsing Context를 만드는 것이 올바른 일인가? (issue)

4.3. 콘텐츠 삭제 이벤트 발생

entry (콘텐츠 색인 항목)에 대해 콘텐츠 삭제 이벤트를 발생시키려면, entryservice worker registration에서 다음 속성을 사용하여 ContentIndexEvent로 이름이 "contentdelete"인 기능 이벤트를 발생시킵니다:
id

entrydescriptionid.

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

ServiceWorkerRegistrationcontent index (ContentIndex)를 가지며, 초깃값은 그 service worker registration컨텍스트 객체서비스 워커 등록인 새 ContentIndex입니다.

index 속성의 getter는 컨텍스트 객체content index를 반환해야 합니다.

5.3. ContentIndex

ContentIndex

현재 하나의 엔진에만 있습니다.

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

현재 하나의 엔진에만 있습니다.

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

ContentIndexservice worker registration (서비스 워커 등록)을 가집니다.

5.3.1. add()

ContentIndex/add

현재 하나의 엔진에만 있습니다.

Firefox없음Safari없음Chrome없음
Opera없음Edge없음
Edge (Legacy)없음IE없음
Firefox for Android없음iOS Safari없음Chrome for Android84+Android WebView84+Samsung Internet없음Opera Mobile60+
add(description) 메서드는 호출되면 새 promise promise를 반환하고 다음 단계를 병렬로 실행해야 합니다:
  1. registration컨텍스트 객체service worker registration으로 둡니다.

  2. registrationactive worker가 null이면, promiseTypeError거부하고 이 단계를 중단합니다.

  3. descriptionid, title, description, 또는 url 중 어느 하나가 빈 문자열이면, promiseTypeError거부하고 이 단계를 중단합니다.

  4. launchURLdescriptionurl컨텍스트 객체관련 설정 객체API 기준 URL파싱한 결과로 둡니다.

    참고: 더 좁은 범위를 가진 새 서비스 워커 등록이 나중에 도입될 수 있습니다.

  5. matchedRegistrationlaunchURL을 인수로 하여 Match Service Worker Registration 알고리즘을 실행한 결과로 둡니다.

  6. matchedRegistrationregistration과 같지 않으면, promiseTypeError거부하고 이 단계를 중단합니다.

  7. registrationactive worker확장 이벤트 집합FetchEvent포함하지 않으면, promiseTypeError거부하고 이 단계를 중단합니다.

  8. icons를 빈 목록으로 둡니다.

  9. 선택적으로, 사용자 에이전트는 descriptionicons에서 사용할 아이콘을 선택할 수 있습니다(MAY). 이 경우, 성공적으로 파싱된 뒤 선택된 descriptionicons의 각 이미지 리소스 (resource)에 대해 다음 단계를 반복합니다:

    1. 다음 속성을 가진 새 요청을 사용하여 fetch를 기다린 결과를 response로 둡니다:

      URL

      resourcesrc.

      Client

      컨텍스트 객체관련 설정 객체.

      Keepalive flag

      설정됨.

      Destination

      "`image`".

      Mode

      "`no-cors`".

      Credentials mode

      "`include`".

    2. response네트워크 오류이면, promiseTypeError거부하고 이 단계를 중단합니다.

    3. response를 이미지로 디코딩할 수 없으면, promiseTypeError거부하고 이 단계를 중단합니다.

    4. responseicons추가합니다.

  10. entry를 다음을 가진 새 콘텐츠 색인 항목으로 둡니다:

    description

    description.

    launch url

    launchURL

    service worker registration

    registration.

    icons

    icons

  11. iddescriptionid로 둡니다.

  12. contentIndexEntriesregistrationcontent index entries로 둡니다.

  13. registrationentry edit queue다음 단계를 큐에 넣습니다:

    1. contentIndexEntries[id]를 entry설정합니다.

    2. 선택적으로 사용자 에이전트는 entry표시할 수 있습니다(MAY).

    3. promise를 undefined로 이행합니다.

참고: 기존 ID를 가진 설명을 추가하면 이전 값을 덮어씁니다.

5.3.2. delete()

ContentIndex/delete

현재 하나의 엔진에만 있습니다.

Firefox없음Safari없음Chrome없음
Opera없음Edge없음
Edge (Legacy)없음IE없음
Firefox for Android없음iOS Safari없음Chrome for Android84+Android WebView84+Samsung Internet없음Opera Mobile60+
delete(id) 메서드는 호출되면 새 promise promise를 반환하고 다음 단계를 병렬로 실행해야 합니다:
  1. registration컨텍스트 객체service worker registration으로 둡니다.

  2. contentIndexEntriesregistrationcontent index entries로 둡니다.

  3. registrationentry edit queue다음 단계를 큐에 넣습니다:

    1. contentIndexEntries[id]를 표시 해제합니다.

    2. contentIndexEntries[id]를 제거합니다.

    3. promise를 undefined로 이행합니다.

5.3.3. getAll()

getAll() 메서드는 호출되면 새 promise promise를 반환하고 다음 단계를 병렬로 실행해야 합니다:
  1. registration컨텍스트 객체service worker registration으로 둡니다.

  2. contentIndexEntriesregistrationcontent index entries로 둡니다.

  3. descriptions를 빈 목록으로 둡니다.

  4. registrationentry edit queue다음 단계를 큐에 넣습니다:

    1. contentIndexEntries의 각 id → entry에 대해 반복합니다:

      1. entrydescriptiondescriptions추가합니다.

    2. promisedescriptions이행합니다.

5.4. ContentIndexEvent

ContentIndexEvent

현재 하나의 엔진에만 있습니다.

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]

이 명세의 예제는 “예를 들어”라는 말로 도입되거나 규범 텍스트와 구분되도록 class="example"으로 표시됩니다. 다음과 같습니다:

이는 정보 제공 예제의 예입니다.

정보 제공 참고는 “참고”라는 말로 시작하며 규범 텍스트와 구분되도록 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. Key words for use in RFCs to Indicate Requirement Levels. 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)