Web Application Manifest 확장 및 인큐베이션을 위한 기능 명세로, Chromium은 이미 출시했지만 다른 사용자 에이전트의 약속 / 구현은 없는 기능을 다룬다. 이러한 기능을 explainer로 유지하는 대신, 여기에서 더 공식적으로 문서화한다.

이는 비공식 제안이다.

display_override 멤버

고급 사용을 위해, [=manifest/display_override=] 멤버는 개발자가 웹 애플리케이션에 대해 선호하는 display mode를 선택할 수 있도록 display mode list values의 사용자 지정 fallback 순서를 지정하는 데 사용할 수 있다. 그 값은 [=display mode=]이다.

[=application manifest=]의 [=manifest/display_override=] 멤버는 [=display mode/window-controls-overlay=] 및 [=display mode/unframed=]와 같은 확장을 포함하는 display mode list valuessequence이다. 이 멤버는 [=display mode=]들에 대한 개발자의 선호 fallback 체인을 나타낸다. 이 필드는 [=manifest/display=] 멤버를 재정의한다. 사용자 에이전트가 여기에 지정된 [=display mode=] 중 어느 것도 지원하지 않는 경우, 다시 [=manifest/display=] 멤버를 고려한다. 알고리즘 단계는 display 멤버 처리를 참조한다.

다음 단계들은 웹 앱의 선택된 display mode 결정에서 [=application manifest/processing extension-point=]에 추가된다:

  1. |manifest:ordered map|.[=manifest/display_override=] 멤버의 각 |candidate_display_mode:DisplayModeType|에 대해 [=list/For each=]:
    1. display modes list가 |candidate_display_mode:DisplayModeType|를 포함하면, 그 |candidate_display_mode:DisplayModeType|를 반환한다
    2. |candidate_display_mode:DisplayModeType|가 [=display mode/window-controls-overlay=]이고 사용자 에이전트가 이를 지원하면, 그 |candidate_display_mode:DisplayModeType|를 반환한다.
    3. |candidate_display_mode:DisplayModeType|가 [=display mode/tabbed=]이고 사용자 에이전트가 이를 지원하면, 그 |candidate_display_mode:DisplayModeType|를 반환한다.

이 멤버는 개발자가 자신의 display mode fallback 순서를 명시적으로 제어하려는 경우, 또는 기본 display modes list에서 사용할 수 없는 mode를 위한 고급 사례에서만 사용하도록 의도되었다. 그 외에는 대부분의 사용 사례에서 [=manifest/display=] 멤버로 충분하다.

개념

Display mode 확장

일반적인 display modes에 추가로, [=manifest/display_override=]는 이에 대한 특정 확장도 지원한다.

unframed
격리된 웹 애플리케이션에는 표시되는 host-native title bar나 [=window controls=]가 없으며, 웹 콘텐츠가 전체 title bar 영역까지 확장된다. 앱은 웹 콘텐츠에 [=draggable region=]을 지정하여 사용자 지정 title bar를 만들 수 있다.
[=display mode/window-controls-overlay=]
tabbed
웹 애플리케이션은 여러 [=application contexts=]를 하나의 운영체제 수준 창에 결합할 수 있다. 예를 들어, 이는 사용자 에이전트가 사용자가 application contexts 사이를 전환할 수 있도록 tab strip UI를 표시한다는 의미일 수 있다.

[=manifest/display_override=] 사용 예제

다음은 standalone보다 minimal-ui display mode를 선호하지만, minimal-ui가 지원되지 않으면 browser가 아닌 standalone으로 fallback하는 [=manifest=]를 보여준다.

            {
              "name": "Recipe Zone",
              "description": "All of the recipes!",
              "icons": [{
                "src": "icon/hd_hi",
                "sizes": "128x128"
              }],
              "start_url": "/index.html",
              "display_override": ["minimal-ui"],
              "display": "standalone",
              "theme_color": "yellow",
              "background_color": "red"
            }
          

드래그 가능한 영역 정의

[=app-region=] CSS 속성은 어떤 사용자 에이전트에서도 구현되지 않았으므로 at risk 상태이다. 일부 사용자 에이전트는 같은 목적을 위해 비표준 CSS 속성 `-webkit-app-region`을 사용한다는 점에 유의한다.

`app-region` 속성은 예를 들어 title bar에서 어떤 영역이나 요소가 드래그 가능한지 CSS로 정의하는 데 사용할 수 있다.

manifest 처리에 대한 확장

이 명세가 추가하는 모든 새로운 확장 및 인큐베이션 기능을 용이하게 하기 위해, 사용자 에이전트는 [=processing a manifest=]의 extension point 중에 다음을 실행해야 한다(SHOULD). 이때 [=URL=] |document URL:URL|, [=URL=] |manifest URL:URL|, [=ordered map=] |json:ordered map| 및 [=ordered map=] |manifest:ordered map|에 접근할 수 있다:

  1. |json|, |manifest| 및 |manifest URL|을 전달하여 [=Process the `tab_strip` member=]를 수행한다.
  2. |json|, |manifest| 및 |manifest URL|을 전달하여 [=Process the `note_taking` member=]를 수행한다.
  3. |json| 및 |manifest|를 전달하여 [=Process the `protocol_handlers` member=]를 수행한다.
  4. |json|, |manifest| 및 |manifest URL|을 전달하여 [=Process the `file_handlers` member=]를 수행한다.
  5. |json| 및 |manifest|를 전달하여 [=Process the `related_applications` member=]를 수행한다.
  6. |json| 및 |manifest|를 전달하여 [=Process the `scope_extensions` member=]를 수행한다.
  7. |json|, |manifest| 및 |manifest URL|을 전달하여 [=Process the `migrate_from` member=]를 수행한다.
  8. |json|, |manifest| 및 |manifest URL|을 전달하여 [=Process the `migrate_to` member=]를 수행한다.

tab_strip 멤버

Web Application Manifest의 `tab_strip` 멤버는 [=display mode/tabbed=] display mode에서 애플리케이션이 어떻게 동작하도록 의도되었는지에 대한 정보를 담는 object이다. 다음 멤버들을 가진다:

home_tab 멤버

[=tab_strip=] object의 `home_tab` 멤버는 애플리케이션의 최상위 메뉴로 작동하도록 의도된 특별한 "home tab"에 대한 정보를 담는 ordered map이다. 다음 멤버들을 포함한다:

scope_patterns 멤버는 [=manifest URL=]을 기준으로 [=within home tab scope|home tab의 scope=]를 정의하는 {{URLPatternInput}}의 리스트이다.

애플리케이션의 적용된 [=display mode=]가 [=display mode/tabbed=]이고 [=Document/processed manifest=]가 [=tab_strip=] 멤버의 null이 아닌 [=home_tab=] 멤버를 포함하는 경우, 애플리케이션은 home tab을 가진다.

home tab context는 다른 application contexts와 비교하여 특별한 속성을 가지는 선택적 [=application context=]이다. 애플리케이션이 [=has a home tab=]이면, 모든 application window는 [=home tab context=]를 포함해야 한다(SHOULD). 그렇지 않으면, application windows는 [=home tab context=]를 가져서는 안 된다(SHOULD NOT).

[=home tab context=]가 어떻게 표시되는지는 사용자 에이전트의 재량에 맡겨지지만, 일반 application contexts와 다른 외형을 가져야 한다(SHOULD).

[=URL=] |url:URL|은 다음 경우에만 within home tab scope라고 한다:

URL이 [=within home tab scope=]가 아니면 outside of home tab scope이다.

모든 창은 home tab을 가진다

애플리케이션이 [=has a home tab=]이면, 새 application window가 생성될 때마다(예: 애플리케이션을 실행할 때 또는 tab을 새 창으로 이동할 때) 사용자 에이전트는 그 창에 새로운 [=home tab context=]를 생성해야 한다. 새로 생성된 [=home tab context=]는 [=start URL=]로 탐색되어야 하며(SHOULD), 이는 정의상 [=within home tab scope=]이다.

home tab scope와 관련된 탐색

[=home tab context=]와 연결된 [=top-level traversable=]을 [=outside of home tab scope=]인 [=URL=] |url:URL|로 [=navigate|탐색=]할 때, 다음 단계들이 실행된다:

  1. [=top-level traversable=] |tab:toplevel traversable|를 "_blank" target과 noopener true로 navigable을 선택한 결과로 둔다.
  2. home-tab traversable을 [=navigating=]하는 대신, 같은 매개변수로 |tab|을 [=navigate=]한다.
  3. 현재 [=application manifest=]를 |tab|의 [=top-level browsing context=]에 [=applied|적용=]한다.
  4. 사용자 에이전트는 |tab|을 home-tab navigable과 같은 창에 배치해야 한다(SHOULD).
  5. |tab|에 포커스한다.

[=display mode/tabbed=]의 [=display mode=]를 가지지만 [=home tab context=]와 연결되지 않은 [=top-level traversable=] (즉, non-home tab)을 [=within home tab scope=]인 [=URL=] |url:URL|로 [=navigate|탐색=]할 때, 다음 단계들이 실행된다:

  1. |hometab:toplevel traversable|를 현재 창과 연결된 [=home tab context=]의 [=top-level traversable=]로 둔다.
  2. top-level traversable을 [=navigating=]하는 대신, 같은 매개변수로 |hometab|을 [=navigate=]한다.
  3. |hometab|에 포커스한다.

Home tab 불변 조건

위 규칙들은 [=has a home tab|home tab을 가지는=] 애플리케이션에 대해 다음 불변 조건들이 항상 참이 되도록 보장하기 위한 것이다:

  • 모든 application window는 정확히 하나의 [=home tab context=]를 가진다.
  • 모든 [=home tab context=]의 active document의 [=Document/URL=]은 [=within home tab scope=]이다(document의 URL이 생성 이후 변경된 경우는 제외).
  • 모든 non-home-tab [=application context=]의 active document의 [=Document/URL=]은 [=outside of home tab scope=]이다(document의 URL이 생성 이후 변경된 경우는 제외).

사용자 에이전트는 document들이 [=URL/fragment=]를 변경하거나, {{History}} API를 사용하여 표시 URL을 home tab scope 안팎으로 수정하더라도 home-tab과 non-home-tab contexts 사이에서 동적으로 document를 이동하지 않는다. 탐색이 발생하지 않기 때문이다. 이러한 이유로, 위 불변 조건들은 document가 생성될 당시 가지고 있던 [=Document/URLs=]만 고려한다.

URL을 수정하여 탐색하는 척하는 single-page applications의 경우, 이는 위 불변 조건을 깨는 바람직하지 않은 동작을 초래할 수 있다 (예: 사용자가 home tab에서 링크를 클릭하여 URL을 non-home page로 동적으로 변경해도, 실제로 [=navigating=]하지 않기 때문에 home tab 안에 머무르게 된다). 이 상황을 피하기 위해, 애플리케이션은 tabbed application mode에 있는지 감지하고, URL을 수정하는 대신 home tab scope 안팎으로 실제 탐색을 수행하도록 동작을 변경할 수 있다.

new_tab_button 멤버

[=tab_strip/new_tab_button=] 멤버는 클릭/활성화되었을 때 application window 내에 새 [=application context=]를 여는 UI affordance (예: 버튼)의 동작을 설명하는 ordered map이다. 다음 멤버들을 가진다:

url 멤버는 [=Document/processed manifest=]의 [=manifest/within scope=]인 [=manifest URL=] 기준의 URL을 나타내는 문자열이다.

애플리케이션은 [=Document/processed manifest=]의 [=new_tab_button=]의 [=new_tab_button/url=] 멤버가 [=outside of home tab scope=]이면 new tab button을 가진다. 애플리케이션이 [=has a new tab button|new tab button을 가지지=] 않으면, 사용자 에이전트는 최종 사용자에게 "new tab" affordance를 제공해서는 안 된다(SHOULD NOT).

최종 사용자가 new tab button을 활성화하면, 다음 단계들이 실행된다:

  1. 현재 창에 [=Create a new application context=]하고 포커스한다.
  2. 이를 [=new_tab_button=]의 [=new_tab_button/url=] 멤버 값으로 탐색한다.

`tab_strip` 멤버 처리

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process the `tab_strip` member하려면, [=processing a manifest=]의 extension point 중에 다음을 실행한다:

  1. |manifest|["tab_strip"]를 새 [=ordered map=]으로 설정한다.
  2. |json|["tab_strip"]가 존재하지 않거나 |json|["tab_strip"]가 [=ordered map=]이 아니면:
    1. |manifest|["tab_strip"]["new_tab_button"]["url"]을 |manifest|["start_url"]로 설정한다.
    2. 반환한다.
  3. |json|["tab_strip"], |manifest|["tab_strip"] 및 |manifest URL|을 전달하여 [=Process the `home_tab` member=]를 수행한다.
  4. |json|["tab_strip"], |manifest|["tab_strip"], |manifest URL| 및 |manifest|["start_url"]를 전달하여 [=Process the `new_tab_button` member=]를 수행한다.

`home_tab` 멤버 처리

[=ordered map=] |json tab strip:ordered map|, [=ordered map=] |manifest tab strip:ordered map| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process the `home_tab` member하려면, 다음을 실행한다:

  1. |json tab strip|["home_tab"]가 존재하지 않거나 |json tab strip|["home_tab"]가 [=ordered map=]이 아니면, 반환한다.
  2. |home tab:ordered map|을 새 [=ordered map=]으로 둔다.
  3. |json tab strip|["home_tab"]["scope_patterns"], |home tab| 및 |manifest URL|을 전달하여 [=Process the `scope_patterns` member=]를 수행한다.
  4. |manifest tab strip|["home_tab"]를 |home tab|으로 설정한다.

`new_tab_button` 멤버 처리

[=ordered map=] |json tab strip:ordered map|, [=ordered map=] |manifest tab strip:ordered map|, [=URL=] |manifest URL:URL| 및 [=URL=] |start URL:URL|가 주어졌을 때 process the `new_tab_button` member하려면, 다음을 실행한다:

  1. |manifest tab strip|["new_tab_button"]["url"]을 |start URL|로 설정한다.
  2. |json tab strip|["new_tab_button"]가 존재하지 않거나 |json tab strip|["new_tab_button"]가 [=ordered map=]이 아니면, 반환한다.
  3. |url:URL|을 |manifest URL|을 base URL로 하여 |json tab strip|["new_tab_button"]["url"]을 [=URL Parser|파싱=]한 결과로 둔다.
  4. |url|이 failure이면, 반환한다.
  5. |url|이 |manifest URL|의 [=URL/within scope=]가 아니면, 반환한다.
  6. |manifest tab strip|["new_tab_button"]["url"]을 |url|로 설정한다.

`scope_patterns` 멤버 처리

[=ordered map=] |json home tab:ordered map|, [=ordered map=] |manifest home tab:ordered map| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process the `scope_patterns` member하려면, 다음을 실행한다:

  1. |processed scope patterns:list|를 새 [=list=]로 둔다.
  2. |manifest home tab|["scope_patterns"]를 |processed scope patterns|로 설정한다.
  3. |json home tab|["scope_patterns"]가 존재하지 않거나 |json home tab|["scope_patterns"]가 [=list=]가 아니면, 반환한다.
  4. |json home tab|["scope_patterns"]의 각 |entry:URLPatternInit|에 대해:
    1. |pattern:URL pattern|을 |manifest URL|이 주어졌을 때 |entry| [=build a URL pattern from an infra value|infra value로부터 URL pattern 생성=] 결과로 둔다. 이 과정이 throw하거나 null을 반환하면, 계속한다.
    2. |pattern|을 |processed scope patterns|에 추가한다.

사용 예제

        {
          "name": "Tabbed App Example",
          "start_url": "/",
          "display": "standalone",
          "display_override": ["tabbed"],
          "tab_strip": {
            "home_tab": {
              "scope_patterns": [
                {"pathname": "/"},
                {"pathname": "/index.html"}
              ]
            },
            "new_tab_button": {
              "url": "/create"
            }
          }
        }
        

이 예제는 tabbed mode가 지원되지 않을 경우 단일-document standalone 창으로 fallback하는 tabbed 웹 앱이다. 메인 index 페이지 (/ 또는 /index.html)로의 모든 탐색은 [=home tab context=]에서 열린다. new tab button은 /create에서 새 tab을 연다.

[=tab_strip/home_tab/scope_patterns=]에 대해 매칭할 때는 URL의 [=URL/query=] 부분이 기본적으로 무시된다(따라서 /index.html?utm_source=foo로의 탐색은 home tab에서 열린다). 그러나 [=start URL=]에 대해 매칭할 때는 [=URL/query=]가 정확히 일치해야 하므로, query를 무시하려는 사이트는 [=start URL=]의 [=URL/path=]를 scope pattern으로 명시적으로 포함하는 것이 권장된다.

`share_target` 멤버

`share_target` 멤버는 웹 애플리케이션을 share actions(예: 텍스트, URL 또는 파일 공유)에 대한 "target"으로 등록한다. `share_target` 멤버는 [[[web-share-target]]] 명세의 일부이다.

note_taking 멤버

Web Application Manifest의 `note_taking` 멤버는 note-taking과 관련된 정보를 담는 object이다. 다음 멤버들을 가진다:

사용자 에이전트는 이러한 멤버를 사용하여 웹 애플리케이션을 note-taking 기능을 가진 애플리케이션으로 다르게 취급할 수 있다(MAY)(예: 운영체제의 note-taking surface와 통합).

new_note_url 멤버

[=note_taking=] `new_note_url` 멤버는 사용자가 웹 애플리케이션을 사용하여 새 note를 작성하려고 할 때(예: 운영체제 바로 가기 아이콘이나 키보드 단축키에서) 개발자가 사용자 에이전트가 로드하기를 선호하는 URL을 나타내는 [=string=]이다.

`new_note_url` 멤버는 순수하게 권고적인 것이며, 사용자 에이전트는 이를 무시하거나 최종 사용자에게 사용할지 여부를 선택하게 할 수 있다(MAY). 사용자 에이전트는 최종 사용자에게 이를 수정할 선택권을 제공할 수 있다(MAY).

사용 예제

다음은 note-taking 애플리케이션을 위한 [=manifest=]를 보여준다.

          {
            "name": "My Note Taking App",
            "description": "You can take notes!",
            "icons": [{
              "src": "icon/hd_hi",
              "sizes": "128x128"
            }],
            "start_url": "/index.html",
            "display": "standalone",
            "note_taking": {
              "new_note_url": "/new_note.html"
            }
          }
        

`note_taking` 멤버 처리

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, [=URL=] |manifest_URL:URL|가 주어졌을 때 process the `note_taking` member하려면, [=processing a manifest=]의 extension point 중에 다음을 실행한다:

  1. |json|["note_taking"]이 [=map/exist=]하지 않으면, 반환한다.
  2. |json|["note_taking"]의 타입이 [=ordered map=]이 아니면, 반환한다.
  3. |manifest|["note_taking"]을 새 [=ordered map=]으로 설정한다.
  4. |json|["note_taking"], |manifest|["note_taking"] 및 |manifest URL|을 전달하여 [=process the `new_note_url` member=]를 수행한다.

`new_note_url` 멤버 처리

[=ordered map=] |json_note_taking:ordered map|, [=ordered map=] |manifest_note_taking:ordered map|, [=URL=] |manifest_URL:URL|가 주어졌을 때 process the `new_note_url` member하려면, 다음을 실행한다:

  1. |json_note_taking|["new_note_url"]이 [=map/exist=]하지 않으면, 반환한다.
  2. |json_note_taking|["new_note_url"]의 타입이 [=string=]이 아니면, 반환한다.
  3. |new_note_url:URL|을 |manifest URL|을 base URL로 하여 |json_note_taking|["new_note_url"]을 [=URL Parser|파싱=]한 결과로 둔다.
  4. |new_note_url|이 failure이면, 반환한다.
  5. |new_note_url|이 |manifest URL|의 [=manifest/within scope=]가 아니면, 반환한다.
  6. manifest_note_taking["new_note_url"]을 |new_note_url|로 설정한다.

`new_note_url` 실행

processed manifest |manifest:processed manifest|가 주어졌을 때 launch the `new_note_url`하려면, 다음 단계를 실행한다:

  1. |manifest|["note_taking"]이 [=map/exist=]하지 않으면, 반환한다.
  2. |manifest|["note_taking"]["new_note_url"]이 [=map/exist=]하지 않으면, 반환한다.
  3. |manifest|["note_taking"]["new_note_url"]의 타입이 [=URL=]이 아니면, 반환한다.
  4. |manifest|를 |manifest|로, |target URL|을 |manifest|["note_taking"]["new_note_url"]로 설정하여 [=launch a web application=] 단계를 실행한다.

사용자 에이전트는 주어진 [=installed web application=]에 대해 언제든지, 일반적으로 사용자 affordance에 대한 응답으로 [=launch the `new_note_url`=]할 수 있다(MAY).

`protocol_handlers` 멤버

[=manifest's=] protocol_handlers 멤버는 웹 애플리케이션이 URL protocols를 처리할 수 있게 하는 protocol handler description들의 배열이다.

설치 시, 사용자 에이전트는 다음과 일관된 상호작용을 통해 운영체제에 protocol handlers를 등록해야 한다(SHOULD):

`protocol_handlers` 멤버 처리

[=object=] |json:JSON|, |manifest:ordered map|가 주어졌을 때 process the `protocol_handlers` member하려면:

  1. |processedProtocolHandlers|를 |json:JSON|["|protocol_handlers|"]의 새 [=list=]로 둔다.
  2. manifest["|protocol_handlers|"]를 |processedProtocolHandlers|로 설정한다.
  3. 각 |protocol_handler| (protocol handler description)에 대해 [=list/For each=]:
    1. |protocol_handler|["protocol"] 또는 |protocol_handler|["url"]가 undefined이면, [=iteration/continue=]한다.
    2. (|normalizedProtocol:string|, |normalizedUrl:URL|)을 |manifest URL|을 base URL로 하고 [=this=]의 relevant [=environment settings object=]를 사용하여 |protocol_handler|["protocol"], | protocol_handler|["url"]로 [=normalize protocol handler parameters=]를 실행한 결과로 둔다. 결과가 failure이면, [=iteration/continue=]한다.
    3. |normalizedUrl|이 |manifest|의 [=manifest/within scope=]가 아니면, [=iteration/continue=]한다.
    4. |processedProtocolHandlers|가 |normalizedUrl|을 [=list/contains=]하면, [=iteration/continue=]한다.
    5. |protocol_handler|를 |processedProtocolHandlers|에 [=List/Append=]한다.
  4. |processedProtocolHandlers|의 각 항목에 대해 [=list/For each=], 사용자 에이전트는 [=register a protocol handler=]해야 한다(SHOULD).

사용자 에이전트는 host 운영체제에서 protocol의 기본 handler로 [=protocol handler description=] protocol_handler들을 등록하기 전에 사용자에게 권한을 요청해야 한다(SHOULD). 사용자 에이전트는 host 운영체제의 관례나 제한과 일관되게 유지하기 위해 표시되는 [=protocol handler description=] protocol_handlers 목록을 잘라낼 수 있다(MAY).

Protocol handler 항목

protocol handler description은 웹 애플리케이션이 처리하려는 protocol을 나타내는 [=object=]이며, [=manifest/protocol_handlers=] 멤버에 대응한다. 다음 멤버들을 가진다:

사용자 에이전트는 이러한 값을 사용하여 웹 애플리케이션을 운영체제에 handler로 등록해야 한다(SHOULD). 사용자가 protocol handler URL을 활성화하면, 사용자 에이전트는 handling a protocol launch를 실행해야 한다(SHOULD).

[[HTML]]의 {{NavigatorContentUtils/registerProtocolHandler()}}는 웹 사이트가 특정 protocols에 대한 가능한 handler로 자신을 등록할 수 있게 한다. protocol handler description의 유효한 [=protocol handler description/protocol=] 및 [=protocol handler description/url=] 값이 무엇인지는 해당 API에서 정의된다. 또한 [[HTML]] API는 여기서 사용하는 [=protocol handler description/protocol=] 대신 scheme을 사용하지만, 동일한 제한이 적용된다는 점에 유의한다.

`protocol` 멤버

protocol handler descriptionprotocol 멤버는 `mailto` 또는 `web+auth`와 같이 처리할 protocol을 나타내는 string이다.

protocol handler description의 [=protocol handler description/protocol=] 멤버는 [[HTML]]에 정의된 {{NavigatorContentUtils/registerProtocolHandler()}}의 scheme 인자와 동등하다.

`url` 멤버

protocol handler descriptionurl 멤버는 관련 protocol이 활성화될 때 열리는 애플리케이션의 [=manifest/within scope=]인 URL이다.

protocol handler description의 [=protocol handler description/url=] 멤버는 [[HTML]]에 정의된 {{NavigatorContentUtils/registerProtocolHandler()}}의 url 인자와 동등하다.

Protocol launch 처리

[=manifest=] manifest를 가지는 protocol handler description protocol_handler가 호출되면, 사용자 에이전트가 resultURL로 [=navigating=]하는 대신 manifestresultURL을 전달하여 [=launch a web application=]해야 한다는 점을 제외하고, [=invoke a protocol handler=]에 사용되는 동일한 단계를 거친다.

이는 이러한 방식으로 [=invoke a protocol handler=]를 호출하고 변경해서는 안 된다. resultURL의 계산은 일반적인 사용을 위한 별도 알고리즘으로 추출되어야 한다.

개인정보 고려 사항: 기본 protocol handler

운영체제의 기능에 따라, protocol handler는 사용자의 명시적 인지 없이 주어진 protocol의 'default' handler(예: 주어진 protocol의 실행을 자동으로 처리)가 될 수 있다. 가능한 오용을 방지하기 위해, 사용자 에이전트는 다음과 같은 보호 조치를 사용할 수 있다(MAY):

사용자 에이전트가 protocol handler entity의 등록을 제거하는 경우, 사용자가 웹 앱과 protocol을 운영체제에 다시 등록할 수 있는 UX를 제공해야 한다(SHOULD).

`file_handlers` 멤버

[=manifest's=] file_handlers 멤버는 앱 자체 외부에서 발생하는 file-opening actions를 앱이 어떻게 처리하는지에 대한 지침을 제공하는 [=list=]이다.

등록된 file-handling applications의 관리, 표시 및 선택은 운영체제의 재량에 맡겨진다.

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, [=URL=] |manifest_url:URL|가 주어졌을 때 process the `file_handlers` member하려면:

  1. |processedFileHandlers:list|를 새 [=list=]로 둔다.
  2. |manifest|["file_handlers"]를 |processedFileHandlers|로 설정한다.
  3. |json|["file_handlers"]가 [=map/exist=]하지 않거나 |json|["file_handlers"]가 [=list=]가 아니면, 반환한다.
  4. |json|["file_handlers"]의 각 |entry:ordered map|에 대해 [=list/For each=]:
    1. |file_handler:ordered map|을 |entry|, [=manifest/start_url=], [=manifest/scope=] 및 |manifest_url|을 사용하여 [=process a file handler item=]한 결과로 둔다.
    2. |file_handler|가 failure이면, [=iteration/continue=]한다.
    3. |file_handler|를 |processedFileHandlers|에 [=list/Append=]한다.

[=installation=] 시, 사용자 에이전트는 [=register file handlers=] 과정을 실행해야 한다(SHOULD).

File Handler 항목

file handler는 자신이 수락하는 [=file types=]로 실행을 처리할 수 있는 애플리케이션 scope 내의 URL을 나타낸다. 다음 멤버들을 가진다:

사용자 에이전트는 이러한 멤버를 사용하여 웹 애플리케이션을 운영체제의 [=file type=]과 연결할 수 있다.

file type은 [=MIME type=] 및/또는 [=file extension=]으로 정의될 수 있다. OS가 파일에 [=MIME type=]이 있다고 판단하거나 그 이름이 특정 [=file extension=]으로 끝나는 경우, 파일은 file type에 속한다. file extension은 "."로 시작하고 valid suffix code points만 포함하는 문자열이다. 또한 [=file extensions=]는 16 code points 길이로 제한된다.

`action` 멤버

[=file handler=]의 action 멤버는 manifest URL 기준의 URL을 나타내는 string이며, processed manifest의 [=manifest/within scope=]이다. 이 URL은 [=execute a file handler launch=] 단계에서 탐색된다.

`name` 멤버

[=file handler=]의 name 멤버는 사용자에게 file type을 식별하는 string이다. [=User agents=]는 file handler 등록 중 이 정보를 운영체제에 전달할 수 있다(MAY).

`icons` 멤버

file handlericons 멤버는 [=file type=]의 그래픽 표현으로 사용되는 icons를 나열한다. 사용자 에이전트는 file handler 등록 중 이 정보를 운영체제에 전달할 수 있다(MAY).

`accept` 멤버

[=file handler=]의 accept 멤버는 [=MIME types=]를 [=file extensions=] 리스트에 매핑하는 dictionary이다.

[=User agents=]는 [=file handler/accept=] 항목이 일치하는 확장자를 가진 파일에만 적용되도록 강제해야 한다(MUST).

[=register file handlers=]하기 위해, 일부 운영체제는 [=MIME types=]를 요구하고 일부는 [=file extensions=]를 요구한다. 따라서 manifest는 각 [=file handler/accept=] 항목에 대해 항상 둘 모두를 제공해야 한다(MUST).

완전한 [=MIME types=] 외에도, "*"를 [=MIME type=]의 subtype으로 사용하여 예를 들어 "image/*"로 모든 이미지 형식(제공된 [=file extensions=] 리스트에도 적용되는)을 매칭할 수 있다.

`launch_type` 멤버

[=file handler=]의 launch_type 멤버는 이 handler로 라우팅된 파일에 대해 앱이 어떻게 실행되는지를 결정하는 string이다. 가능한 값은 `"single-client"`와 `"multiple-clients"`이다. 제공되지 않으면 기본값은 `"single-client"`이다.

[=file handler=]가 파일 집합과 일치한다고 판단되면, [=user agent=]는 각 파일마다 한 번씩 앱을 실행할지(`"multiple-clients"`), 모든 파일에 대해 한 번만 실행할지(`"single-client"`) 제어하기 위해 [=file handler/launch_type=]을 사용해야 한다(SHOULD). {{LaunchParams/files}}를 참조한다. 사용자 에이전트는 서로 다른 [=file handlers=]의 파일을 하나의 launch event로 병합해서는 안 된다(MUST NOT).

File handler 항목 처리

[=ordered map=] |item:ordered map|, [=URL=] |start_url:URL|, [=URL=] |scope:URL| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process a file handler item하려면:

  1. 다음 중 하나라도 참이면 failure를 반환한다:
    • |item|["action"]이 [=map/exist=]하지 않거나 [=string=]이 아니다.
    • |item|["accept"]가 [=map/exist=]하지 않는다.
    • |item|["accept"]가 [=dictionary=]가 아니다.
    • |item|["accept"]가 [=map/is empty=]이다.
  2. |url:URL|을 |manifest_url URL|을 base URL로 하여 |item|["action"]을 [=URL Parser|파싱=]한 결과로 둔다.
  3. |url|이 failure이면, failure를 반환한다.
  4. |url|이 |scope|의 [=manifest/within scope=]가 아니면, failure를 반환한다.
  5. |launch_type:string|을 "single-client"로 초기화된 새 [=string=]으로 둔다.
  6. |item|["launch_type"]이 [=map/exists=]하고 "multiple-clients"이면, |launch_type|을 |item|["launch_type"]으로 설정한다.
  7. |accept:ordered map|을 새 [=ordered map=]으로 둔다.
  8. |item|["accept"]의 각 |mime_type_string:string| → |extensions|에 대해 [=map/for each=]
    1. |extensions|가 [=list=]가 아니면, [=iteration/continue=]한다.
    2. |extensions|가 [=list/empty=]이면, [=iteration/continue=]한다.
    3. |extensions|가 [=string=]이 아닌 항목을 [=list/contains=]하면, [=iteration/continue=]한다.
    4. |extensions|가 문자 `.`로 시작하지 않는 문자열을 [=list/contains=]하면, [=iteration/continue=]한다.
    5. |extensions|가 16자보다 긴 문자열을 [=list/contains=]하면, [=iteration/continue=]한다.
    6. |mime_type_parsed:mime type|을 |mime_type_string|에 대해 [=parse a mime type=] 단계를 실행한 결과로 둔다.
    7. |mime_type_parsed:mime type|이 failure이면, [=iteration/continue=]한다.
    8. |mime_type_parsed/type|이 [[IANA-MEDIA-TYPES]]의 top-level type으로 나열되어 있지 않으면, [=iteration/continue=]한다.
    9. |accept|[|mime_type_string|]을 |extensions|로 설정한다.
  9. |accept:ordered map|이 비어 있으면, failure를 반환한다.
  10. |file_handler:ordered map|을 |ordered map| «[ "action" → |url|, "name" → |item|["name"], "launch_type" → |launch_type|, "accept" → |accept| ]»로 둔다.
  11. |item|["icons"], |file_handler|, |manifest URL| 및 "icons"를 전달하여 Process image resources한다.
  12. |file_handler|를 반환한다.

File handler launch 실행

execute a file handler launch 단계는 다음 알고리즘으로 주어진다. 이 알고리즘은 [=list=] |files:list|와 [=processing a manifest=]의 결과를 보유하는 [=ordered map=] |manifest:ordered_map|을 취한다.

  1. |file_handlers:list|를 |manifest|["file_handlers"]로 둔다.
  2. |file_handlers:list|가 null이면, 반환한다.
  3. |launches:ordered map|을 [=ordered map=]으로 둔다.
  4. |files|의 각 |filename:string|에 대해 [=list/for each=]
    1. |file_handlers:list|의 각 |file_handler:ordered_map|에 대해 [=list/for each=]:
      1. |file_handler|["accept"]의 각 |mime_type_string:string| → |extensions:list|에 대해 [=map/for each=]
        1. |extensions|의 각 |extension:string|에 대해 [=list/for each=]:
          1. |filename|이 |extension|으로 끝나지 않으면, [=iteration/continue=]한다.
          2. |launches|[|file_handler|]가 [=map/exists=]하면, |filename|을 |launches|[|file_handler|]에 [=list/append=]한다.
          3. 그렇지 않으면, |launches|[|file_handler|]를 단일 요소 |filename|을 가진 [=list=]로 설정한다.
          4. |files|의 다음 요소로 [=iteration/Continue=]한다.
  5. |launches|의 각 |file_handler| → |files:list|에 대해 [=map/for each=]
    1. |file_handler|["launch_type"]이 "multiple-clients"와 같으면
      1. |files|의 각 |file|에 대해 [=list/for each=]
        1. |params:LaunchParams|를 {{LaunchParams/targetURL}}가 |file_handler|["action"]으로 설정되고 {{LaunchParams/files}}가 |file|에 대응하는 단일 요소 {{FileSystemHandle}}를 가진 {{FrozenArray}}로 설정된 새 {{LaunchParams}}로 둔다.
        2. |manifest| 및 |params|를 전달하여 [=Launch a web application with handling=]한다.
    2. 그렇지 않으면,
      1. |params:LaunchParams|를 {{LaunchParams/targetURL}}가 |file_handler|["action"]으로 설정되고 {{LaunchParams/files}}가 |files|로 명명된 파일 경로에 대응하는 {{FileSystemHandle}}들의 {{FrozenArray}}로 설정된 새 {{LaunchParams}}로 둔다.
      2. |manifest| 및 |params|를 전달하여 [=Launch a web application with handling=]한다.

File handlers 등록

사용자 에이전트는 다음과 일관되게 host 운영체제에 register file handlers해야 한다(SHOULD):

사용자 에이전트는 기능을 보존하고 남용을 방지하기 위해 [=file extensions=]의 전체 집합을 잘라낼 수 있다(MAY). 사용자 에이전트는 특정 filetypes와의 연결을 방지할 수 있다(MAY).

개인정보 고려 사항: 기본 file handler.

운영체제의 기능에 따라, [=file handler=]는 사용자의 명시적 인지 없이 주어진 [=file type=]의 기본 handler가 되어 해당 file type의 실행을 자동으로 처리할 수 있다. 가능한 오용을 방지하기 위해, [=user agents=]는 [=execute a file handler launch=] 과정을 시작하기 전에 명시적 사용자 동의를 요구할 수 있다(MAY). 동의를 요청하는 경우, 사용자 에이전트는 사용자가 그 결정을 영구적으로 지정할 수 있도록 허용해야 하며(SHOULD), 그렇게 지정된 경우 웹 애플리케이션의 OS file handling entity 등록을 제거해야 한다(SHOULD).

개인정보 고려 사항: 이름과 아이콘.

각 file handler의 이름과 아이콘은 개인정보 및 보안에 민감할 수 있다. 사용자가 이를 보고 확인하는 지정된 방법이 없기 때문이다. 이로 인해, [=user agents=]는 대체 문자열과 아이콘을 선호하거나 OS 기본값으로 fallback하기 위해 이를 무시하도록 선택할 수 있다(MAY).

File handlers가 있는 manifest 예제

다음 예제에서 웹 애플리케이션은 서로 다른 3개의 file handler를 가진다.

`related_applications` 멤버

related application은 웹 애플리케이션과 관계가 있는, underlying application platform에서 접근 가능한 애플리케이션이다.

[=manifest's=] related_applications 멤버는 related applications를 나열하며, 웹 애플리케이션과 related applications 사이의 그러한 관계를 나타내는 표시로 사용된다. 이 관계는 단방향이며, 나열된 애플리케이션이 같은 관계를 주장하지 않는 한, user agent는 양방향 endorsement를 가정해서는 안 된다(MUST NOT).

`related_applications` 사용 예로는, 그 정보를 사용하여 웹 애플리케이션에 대한 더 많은 정보를 수집하는 crawler나, 사용자가 웹 애플리케이션을 설치하려 할 때 나열된 애플리케이션을 대안으로 제안할 수 있는 browser가 있을 수 있다.

[=ordered map=] |json:ordered map| 및 [=ordered map=] |manifest:ordered map|가 주어졌을 때 process the `related_applications` member하려면:

  1. |relatedApplications:list|를 새 [=list=]로 둔다.
  2. |manifest|["related_applications"]를 |relatedApplications|로 설정한다.
  3. |json|["related_applications"]가 [=map/exist=]하지 않거나 |json|["related_applications"]가 [=list=]가 아니면, 반환한다.
  4. |json|["related_applications"]의 각 app에 대해 [=list/For each=]:
    1. app["id"]와 app["url"] 중 어느 것도 missing이 아니면:
      1. app["url"]을 app["url"]이 주어졌을 때 process the `url` member of an application을 실행한 결과로 설정한다.
      2. apprelatedApplications에 [=list/append=]한다.
  5. relatedApplications를 설정한다.

`prefer_related_applications` 멤버

[=manifest's=] `prefer_related_applications` 멤버는 related applications가 웹 애플리케이션보다 선호되어야 함을 사용자 에이전트에 알려주는 hint로 사용되는 [=boolean=]이다. `prefer_related_applications` 멤버가 `true`로 설정되어 있고, 사용자 에이전트가 웹 애플리케이션 설치를 제안하려는 경우, 사용자 에이전트는 대신 related applications 중 하나의 설치를 제안하려 할 수 있다.

`scope_extensions` 멤버

[=manifest's=] scope_extensions 멤버는 애플리케이션이 원하는 [=scope extensions=]의 리스트를 나타낸다.

scope extension은 [=within extended scope=]로 취급되어야 하는 추가 [=URLs=]를 설명함으로써 [=manifest/navigation scope of a manifest=]를 확장한다.

사용자 에이전트는 추가 [=URLs=]가 [=within extended scope=]가 되도록 [=apply extended scope=]할 수 있기 전에 [=process the scope_extensions member=]와 [=validate scope extensions=]를 수행해야 한다(MUST).

[=URL=] |target:URL|은 |target|이 manifest의 [=manifest/within scope=]이거나 [=matches a validated scope extension=]인 경우 |manifest:processed manifest|의 within extended scope이다.

[=URL=] |target:URL|은 [=URL=] |target:URL| 및 [=list=] |validated_scope_extensions:list|가 주어졌을 때 다음 알고리즘이 `true`를 반환하는 경우 matches a validated scope extension이다:

  1. |validated_scope_extensions:list|의 각 |entry:ordered map|에 대해 [=list/For each=]:
    1. |entry|가 [=ordered map=]이 아니거나, |entry|["scope"]가 [=map/exist=]하지 않거나 [=URL=]이 아니면, 계속한다.
    2. [=URL=] |resolved_scope:URL|을 |entry:ordered map|["scope"]로 둔다.
    3. |target|이 |resolved_scope|의 [=URL/within scope=]이면, `true`를 반환한다.
  2. `false`를 반환한다.

사용 예제

다음 예제는 `scope_extensions` 멤버를 사용하는 애플리케이션의 [=manifest=]를 보여준다.

          {
            "id": "https://example.com/app",
            "name": "My App",
            "icons": [{
              "src": "icon/hd_hi",
              "sizes": "128x128"
            }],
            "start_url": "/app/index.html",
            "scope": "/app",
            "display": "standalone",
            "scope_extensions": [
              { "type": "origin", "origin": "https://example.co.uk" },
              { "type": "origin", "origin": "https://help.example.com" }]
          }
        

다음은 `scope_extensions` 멤버에 나열된 [=origins=]의 `.well-known` path에서 다운로드할 수 있는 2개의 [=web-app-origin-association=] 파일을 보여준다.

          {
            "https://example.com/app": {
              "scope": "/app"   
            }
          }
        
          {
            "https://example.com/app": {
              "scope": "/"   
            }
          }
        

이 앱의 navigation scope는 다음 [=URLs=] 중 어느 것의 [=URL/within scope=]인 URL들로 구성된다: `https://example.com/app`, `https://example.co.uk/app`, 및 `https://help.example.com`.

`scope_extensions` 멤버 처리

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map| 및 [=URL=] |manifest_id:URL|가 주어졌을 때 process the `scope_extensions` member하려면:

  1. |processedScopeExtensions:list|를 새 [=list=]로 둔다.
  2. |manifest|["scope_extensions"]를 |processedScopeExtensions|로 설정한다.
  3. |json|["scope_extensions"]가 [=map/exist=]하지 않거나 [=list=]가 아니면, 반환한다.
  4. |json|["scope_extensions"]의 각 |entry:ordered map|에 대해 [=list/For each=]:
    1. |entry|["type"] 또는 |entry|["origin"]이 [=map/exist=]하지 않으면, [=iteration/continue=]한다.
    2. |entry|["type"]이 "origin"이 아니면, [=iteration/continue=]한다.
    3. |entry|["origin"]이 [=string=]이 아니면, [=iteration/continue=]한다.
    4. [=URL=] |origin_url:URL|을 |entry|["origin"]을 [=URL Parser|파싱=]한 결과로 둔다.
    5. |origin_url|이 [=URL/scheme=] "https"를 가진 유효한 |URL|이면, |entry|["origin"]을 |origin_url|의 [=URL/origin=]으로 설정한다.
    6. 그렇지 않으면 [=iteration/continue=]한다.
    7. [=ordered map=] |entry:ordered map| 및 [=URL=] |manifest_id:URL|가 주어졌을 때 [=validate scope extensions=]의 알고리즘을 사용하여 |entry|를 검증한다.
    8. 이전 단계가 error를 반환했으면, [=iteration/continue=]한다.
    9. 그렇지 않으면, |entry|["scope"]를 반환된 [=URL=]로 설정한다.
    10. |entry|를 |processedScopeExtensions|에 [=list/Append=]한다.

Scope extensions 검증

[=ordered map=] |extension:ordered map| 및 [=URL=] |manifest_id:URL|가 주어졌을 때 validate scope extensions하려면:

  1. [=URL=] |origin_url:URL|을 |extension|["origin"]의 [=URL/origin=]의 [=URL=]로 둔다.
  2. [=URL=] |download_url:URL|을 |origin_url|을 base [=URL=]로 사용하여 "/.well-known/web-app-origin-association"을 [=URL Parser|파싱=]한 결과로 둔다.
  3. [=ordered map=] |json:ordered map|을 |download_url|가 주어진 [=fetch=]의 결과로 둔다.
  4. [=fetch=]가 실패했으면, error를 반환한다.
  5. |json|[|manifest_id|]가 [=map/exist=]하지 않거나 [=ordered map=]이 아니면, error를 반환한다.
  6. [=string=] |scope:string|를 "/"로 둔다.
  7. |json|[|manifest_id|]["scope"]가 [=map/exists=]하고 [=string=]이면, |scope|를 |json|[|manifest_id|]["scope"]로 설정한다.
  8. [=URL=] |resolved_scope:URL|을 |extension|["origin"]을 base [=URL=]로 하여 |scope|를 [=URL Parser|파싱=]한 결과로 둔다.
  9. |resolved_scope|를 반환한다.

Scope extensions 적용

사용자 에이전트는 어떤 시나리오에서는 apply extended scope를 적용하고 다른 시나리오에서는 [=manifest/scope=]를 적용하도록 선택할 수 있다(MAY).

[=application context=]의 [=navigable/active document=]의 [=URL=]이 [=manifest/within scope=]가 아니지만 [=within extended scope=]이면, 사용자 에이전트는 사용자가 [=URL=] 또는 적어도 그 [=origin=]을, secure connection을 통해 제공되는지 여부를 포함하여 확인할 수 있게 하는 UI를 제공해야 한다(SHOULD). 이 UI는 [=URL=]이 [=application context=]의 [=Document/processed manifest=]의 [=manifest/within scope=]가 아닐 때 사용되는 UI와 달라야 하며(SHOULD), 사용자가 여전히 애플리케이션의 의도된 콘텐츠를 탐색하고 있음을 분명히 하면서도 다른 [=origin=]으로 탐색하는 것의 개인정보 및 보안 영향을 계속 알 수 있게 해야 한다.

web-app-origin-association 파일

web-app-origin-association 파일은 [=validate scope extensions=] 또는 [=validate origin migration=]에 사용할 수 있는 JSON 파일이다. 이는 그 파일이 있는 origin과 하나 이상의 웹 애플리케이션 사이의 association을 확인한다. 이는 manifest [=manifest/ids=]를 참조하여 앱을 고유하게 식별한다.

[=origin=] |origin:origin|이 주어졌을 때, [=web-app-origin-association=] 파일은 [origin]/.well-known/web-app-origin-association에서 다운로드할 수 있을 것으로 기대된다.

Web Application Origin Migration

Web Application Origin Migration은 웹 애플리케이션이 한 origin에서 다른 origin(동일 site 내)으로 이동했음을 사용자 에이전트에 나타낼 수 있게 한다. 이는 사용자 에이전트가 사용자의 설치와 잠재적으로 설정을 보존하면서 설치된 웹 애플리케이션을 migrate할 수 있게 한다.

사용 예제

다음 예제는 https://old.example.com에 호스팅된 웹 애플리케이션을 https://new.example.com으로 migrate할 수 있는 방법을 보여준다. 두 origins는 [=same site=]여야 하며 migration에 명시적으로 동의해야 한다.

먼저, 이전 애플리케이션의 manifest는 사용자 에이전트에 이동 의도를 알리기 위해 선택적으로 `migrate_to` 멤버를 포함할 수 있다(또는 이전 애플리케이션이 새 애플리케이션으로 redirect할 수 있다):

          {
            "name": "My App",
            "id": "/",
            "migrate_to": {
              "id": "https://new.example.com/",
              "install_url": "https://new.example.com/install"
            }
          }
        

다음으로, 새 애플리케이션의 manifest는 이전 애플리케이션으로부터의 migration을 claim하기 위해 `migrate_from` 멤버를 포함한다:

          {
            "name": "My New App",
            "id": "/",
            "migrate_from": [
              {
                "id": "https://old.example.com/",
                "install_url": "https://old.example.com/install",
                "behavior": "force"
              }
            ]
          }
        

마지막으로, 이전 origin은 새 애플리케이션이 takeover하는 것을 허용한다고 definitively validate하기 위해 `web-app-origin-association` 파일을 호스트해야 한다. 이 파일이 없으면, 악의적인 새 애플리케이션이 허가 없이 이전 애플리케이션으로부터 migrate한다고 주장할 수 있다.

          {
            "https://new.example.com/": {
              "allow_migration": true
            }
          }
        

migrate_from 멤버

[=manifest/migrate_from=] 멤버는 migration 대상이 되는 이전 웹 애플리케이션을 식별하는 [=strings=] 또는 [=ordered maps=]의 [=list=]이다.

entry가 [=string=]이면, 이전 애플리케이션의 [=manifest/id=]로 취급된다.

entry가 [=ordered map=]이면, 다음 멤버들을 가질 수 있다:

사용자 에이전트가 [=manifest/migrate_from=] 멤버를 처리하려면, manifest는 JSON에 유효한 명시적 id 멤버도 포함해야 한다.

migrate_to 멤버

[=manifest/migrate_to=] 멤버는 새 애플리케이션으로의 migration을 능동적으로 알리는 선택적 [=ordered map=]이다. 다음 멤버들을 가진다:

Origin migration 검증

[=URL=] |old_manifest_id:URL| 및 [=URL=] |new_manifest_id:URL|가 주어졌을 때 validate origin migration하려면:

  1. |old_manifest_id|의 [=URL/origin=]과 |new_manifest_id|의 [=URL/origin=]이 [=same site=]가 아니면, error를 반환한다.
  2. |old_manifest_id|의 [=URL/origin=]이 |new_manifest_id|의 [=URL/origin=]과 [=same origin=]이면, success를 반환한다.
  3. [=URL=] |origin_url:URL|을 |old_manifest_id|의 [=URL/origin=]으로 둔다.
  4. [=URL=] |download_url:URL|을 |origin_url|을 base [=URL=]로 사용하여 "/.well-known/web-app-origin-association"을 [=URL Parser|파싱=]한 결과로 둔다.
  5. [=ordered map=] |json:ordered map|을 |download_url|가 주어진 [=fetch=]의 결과로 둔다.
  6. [=fetch=]가 실패했으면, error를 반환한다.
  7. |json|[|new_manifest_id|]가 [=map/exist=]하지 않거나 [=ordered map=]이 아니면, error를 반환한다.
  8. |json|[|new_manifest_id|]["allow_migration"]이 `true`가 아니면, error를 반환한다.
  9. success를 반환한다.

`migrate_from` 멤버 처리

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process the `migrate_from` member하려면:

  1. |json|["id"]가 [=map/exist=]하지 않거나 [=string=]이 아니면, 반환한다.
  2. |json|["id"]가 빈 문자열이면, 반환한다.
  3. |base_origin:origin|을 |manifest|["start_url"]의 [=URL/origin=]으로 둔다.
  4. |id_url:URL|을 |base_origin|을 base URL로 하여 |json|["id"]를 [=URL Parser|파싱=]한 결과로 둔다.
  5. |id_url|이 failure이면, 반환한다.
  6. |id_url|이 |manifest|["start_url"]와 [=same origin=]이 아니면, 반환한다.
  7. |processed_migrate_from:list|를 새 [=list=]로 둔다.
  8. |manifest|["migrate_from"]을 |processed_migrate_from|로 설정한다.
  9. |json|["migrate_from"]이 [=map/exist=]하지 않거나 [=list=]가 아니면, 반환한다.
  10. |json|["migrate_from"]의 각 |entry|에 대해 [=list/For each=]:
    1. |processed_entry:ordered map|을 새 [=ordered map=]으로 둔다.
    2. |entry|가 [=string=]이면:
      1. |id:URL|을 |manifest URL|을 base URL로 하여 |entry|를 [=URL Parser|파싱=]한 결과로 둔다.
      2. |id|가 failure이면, [=iteration/continue=]한다.
      3. |processed_entry|["id"]를 |id|로 설정한다.
    3. 그렇지 않고 |entry|가 [=ordered map=]이면:
      1. |entry|["id"]가 [=map/exist=]하지 않거나 [=string=]이 아니면, [=iteration/continue=]한다.
      2. |id:URL|을 |manifest URL|을 base URL로 하여 |entry|["id"]를 [=URL Parser|파싱=]한 결과로 둔다.
      3. |id|가 failure이면, [=iteration/continue=]한다.
      4. |processed_entry|["id"]를 |id|로 설정한다.
      5. |entry|["install_url"]이 [=map/exists=]하고 [=string=]이면:
        1. |install_url:URL|을 |manifest URL|을 base URL로 하여 |entry|["install_url"]을 [=URL Parser|파싱=]한 결과로 둔다.
        2. |install_url|이 failure가 아니면, |processed_entry|["install_url"]을 |install_url|로 설정한다.
      6. |entry|["behavior"]가 [=map/exists=]하고 [=string=]이면:
        1. |entry|["behavior"]가 "suggest" 또는 "force"이면, |processed_entry|["behavior"]를 |entry|["behavior"]로 설정한다.
    4. 그렇지 않으면, [=iteration/continue=]한다.
    5. [=URL=] |processed_entry|["id"] 및 [=URL=] |id_url|가 주어졌을 때 [=validate origin migration=]의 알고리즘을 사용하여 |processed_entry|["id"]를 검증한다.
    6. 이전 단계가 error를 반환했으면, [=iteration/continue=]한다.
    7. |processed_entry|를 |processed_migrate_from|에 [=list/Append=]한다.

`migrate_to` 멤버 처리

[=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map| 및 [=URL=] |manifest URL:URL|가 주어졌을 때 process the `migrate_to` member하려면:

  1. |json|["migrate_to"]가 [=map/exist=]하지 않거나 [=ordered map=]이 아니면, 반환한다.
  2. |entry:ordered map|을 |json|["migrate_to"]로 둔다.
  3. |entry|["id"]가 [=map/exist=]하지 않거나 [=string=]이 아니면, 반환한다.
  4. |id:URL|을 |manifest URL|을 base URL로 하여 |entry|["id"]를 [=URL Parser|파싱=]한 결과로 둔다.
  5. |id|가 failure이면, 반환한다.
  6. |processed_migrate_to:ordered map|을 새 [=ordered map=]으로 둔다.
  7. |processed_migrate_to|["id"]를 |id|로 설정한다.
  8. |entry|["install_url"]이 [=map/exists=]하고 [=string=]이면:
    1. |install_url:URL|을 |manifest URL|을 base URL로 하여 |entry|["install_url"]을 [=URL Parser|파싱=]한 결과로 둔다.
    2. |install_url|이 failure가 아니면, |processed_migrate_to|["install_url"]을 |install_url|로 설정한다.
  9. |manifest|["migrate_to"]를 |processed_migrate_to|로 설정한다.

개인정보 및 보안 고려 사항

악의적인 행위자가 조용히 애플리케이션을 takeover하는 것(예: 단순한 계산기 앱이 자신을 은행 앱처럼 보이도록 업데이트하는 것)을 방지하기 위해, [=validate origin migration=] 알고리즘은 양방향 handshake를 강제한다. 새 애플리케이션 origin과 이전 애플리케이션 origin 모두 [=web-app-origin-association=] 파일을 통해 migration에 명시적으로 동의해야 한다.

또한 migration은 검증되지 않은 제3자에게 ownership을 이전하는 것이 아니라 조직의 통제 내에서 이루어지는 정당한 rebranding 및 architecture 변경에 사용되도록 보장하기 위해 [=same site=]로 제한된다.

사용자 에이전트는 특히 app name 및 icons와 같은 보안에 민감한 필드의 업데이트가 포함된 경우, migration flow의 일부로 명시적 사용자 확인 dialog를 포함하는 것을 고려해야 한다.

외부 애플리케이션 리소스

external application resource는 웹 애플리케이션과 관련된 애플리케이션을 나타낸다.

[=external application resource=]는 다음 멤버들을 가질 수 있으며, 그중 일부는 [=valid external application resource=]가 되기 위해 필요하다:

valid external application resource는 반드시 [=external application resource/platform=] 멤버와, [=external application resource/url=] 또는 [=external application resource/id=] 멤버(또는 둘 다)를 가져야 한다(MUST).

`platform` 멤버

platform 멤버는 이 [=external application resource=]가 연결된 [=platform=]을 나타낸다. platform은 software distribution ecosystem 또는 가능하게는 operating system을 나타낸다. 이 명세는 platform 멤버의 특정 값을 정의하지 않는다.

`url` 멤버

[=external application resource's=] url 멤버는 애플리케이션을 찾을 수 있는 URL이다.

process the `url` member of an application하려면:

  1. application URL이 missing이면, null을 반환한다.
  2. 그렇지 않으면, application URL을 [=URL Parser|parse=]하고, 결과가 failure가 아니면 결과를 반환한다. 그렇지 않으면 null을 반환한다.

`id` 멤버

[=external application resource's=] id 멤버는 platform에서 애플리케이션을 나타내는 데 사용되는 id를 나타낸다.

`min_version` 멤버

[=external application resource's=] min_version 멤버는 이 웹 앱과 관련 있다고 간주되는 애플리케이션의 minimum version을 나타낸다. 이 version은 platform-specific syntax 및 semantics를 가진 string이다.

`fingerprints` 멤버

[=external application resource's=] fingerprints 멤버는 [=fingerprints=]의 [=list=]를 나타낸다.

fingerprint는 애플리케이션 검증에 사용되는 cryptographic fingerprints의 집합을 나타낸다. fingerprint는 typevalue라는 두 멤버를 가진다. 이들은 각각 string이지만, 그 syntax 및 semantics는 platform-defined이다.

설치 프롬프트

설치 과정을 트리거할 수 있는 여러 방법이 있다:

어떤 경우든, document가 installable이 아니면 사용자 에이전트는 present an install prompt해서는 안 된다(MUST NOT).

사용자 에이전트는 언제든지(단, document가 installable인 경우에만) steps to notify that an install prompt is available를 실행할 수 있으며(MAY), 이를 통해 사이트는 사용자가 사용자 에이전트 UI와 상호작용할 필요 없이 site-triggered install prompt를 표시할 기회를 얻는다.

install prompt를 표시하려면:

  1. 앱 설치를 진행할지 여부를 사용자에게 묻는 사용자 에이전트별 UI를 표시한다. 이 선택의 result는 {{AppBannerPromptOutcome/"accepted"}} 또는 {{AppBannerPromptOutcome/"dismissed"}}이다.
  2. result를 반환하고, in parallel로:
    1. result가 {{AppBannerPromptOutcome/"accepted"}}이면, steps to install the web application를 실행한다.

steps to notify that an install prompt is available는 다음 알고리즘으로 주어진다:

  1. top-level browsing context의 {{Document}}가 completely loaded될 때까지 기다린다.
  2. 이미 install prompt being presented가 있거나 steps to install the web application가 현재 실행 중이면, 이 단계를 abort한다.
  3. 다음을 수행하도록 application life-cycle task sourceQueue a task한다:
    1. |mayShowPrompt|를 `"beforeinstallprompt"`라는 이름의 [=fire an event=]를 [=top-level browsing context=]의 [=relevant global object=]에서 {{BeforeInstallPromptEvent}} 인터페이스를 사용하고 {{Event/cancelable}} 속성을 `true`로 초기화하는 단계와 함께 실행한 결과로 둔다.
    2. |mayShowPrompt|가 true이면, 사용자 에이전트는 in parallel로 |event|와 함께 request to present an install prompt할 수 있다(MAY).

설치 가능한 웹 애플리케이션

Installation 과정

steps to install the web application은 다음 알고리즘으로 주어진다:

  1. manifest를 installable document의 manifest로 둔다.
  2. 사용자의 운영체제에 웹 애플리케이션을 등록하려는 지정되지 않은 일련의 작업을 수행한다(예: 웹 애플리케이션을 실행하는 shortcuts 생성, system uninstall menu에 애플리케이션 등록 등). 설치가 실패하면 (예를 들어 OS가 사용자 에이전트에게 기기의 home screen에 아이콘을 추가할 권한을 거부하는 등 어떤 이유로든 가능하다) 이 단계들을 abort한다.
  3. 설치가 발생한 [=top-level browsing context=]의 [=relevant global object=]에 `"appinstalled"`라는 이름의 fire an event를 수행하도록 application life-cycle task sourceQueue a task한다.

Installability signals

설계상, 이 명세는 개발자에게 웹 애플리케이션을 "install"하는 명시적 API를 제공하지 않는다. 대신, manifest는 웹 애플리케이션을 설치할 수 있다는 사용자 에이전트에 대한 installability signal로 작동할 수 있다. 이러한 signals는 사용자 에이전트마다 달라지며, 각 사용자 에이전트는 웹 사이트가 install prompt를 받을 자격이 있는지 판단하기 위한 자체 heuristics를 가진다. 구현자는 일반적으로 특정 installabilty signals 또는 웹 애플리케이션이 installable로 간주되기 위해 충족해야 하는 기타 관련 기준을 설명하는 문서를 제공한다.

사용자 에이전트가 구현할 수 있는 웹 애플리케이션의 가능한 installability signals 예:

이 목록은 완전하지 않으며 일부 installability signals는 모든 사용자 에이전트에 적용되지 않을 수 있다. 사용자 에이전트가 이러한 installability signals를 사용하여 웹 애플리케이션을 설치할 수 있는지 결정하는 방법은 구현자에게 맡겨진다.

설치 이벤트

이 명세의 [=event|Events=]는 application life-cycle task source에 의존한다.

BeforeInstallPromptEvent 인터페이스

beforeinstallprompt 이벤트는 다소 잘못 명명되었다. 이는 (사용자 에이전트에 따라) 반드시 manual installation이 이어질 것임을 신호하는 것이 아니라, 사이트에 install prompt를 trigger할 수 있는 능력만 제공할 수도 있기 때문이다. 이 이름은 역사적 이유로 붙여졌다.
          [Exposed=Window]
          interface BeforeInstallPromptEvent : Event {
            constructor(DOMString type, optional EventInit eventInitDict = {});
            Promise<PromptResponseObject> prompt();
          };

          dictionary PromptResponseObject {
            AppBannerPromptOutcome userChoice;
          };

          enum AppBannerPromptOutcome {
            "accepted",
            "dismissed"
          };
        

{{BeforeInstallPromptEvent}}는 사이트가 site-triggered install prompt를 표시할 수 있도록 허용될 때, 또는 사용자 에이전트가 automated install prompt를 표시하기 전에 dispatch된다. 이는 사이트가 automated install prompt를 cancel할 수 있게 하고, 또한 site-triggered install prompt를 수동으로 표시할 수 있게 한다.

{{BeforeInstallPromptEvent}}가 cancel되지 않으면, 사용자 에이전트는 최종 사용자에게 present an install prompt (구체적으로는 automated install prompt)할 수 있다. 기본 동작을 cancel하는 것({{Event/preventDefault()}}를 통해)은 사용자 에이전트가 presenting an install prompt하지 못하게 한다. 사용자 에이전트는 나중에 steps to notify that an install prompt is available를 자유롭게 다시 실행할 수 있다.

PromptResponseObject는 {{BeforeInstallPromptEvent/prompt()}}를 호출한 결과를 포함한다. 이는 사용자가 선택한 outcome을 나타내는 하나의 멤버 userChoice를 포함한다.

{{BeforeInstallPromptEvent}}의 인스턴스는 다음 internal slots를 가진다:

[[\didPrompt]]
초기에 `false`인 boolean. 이 이벤트가 최종 사용자에게 present an install prompt하는 데 사용되었는지를 나타낸다.
[[\userResponsePromise]]
presenting an install prompt의 outcome을 나타내는 promise.

prompt() 메서드

prompt 메서드는 호출되면 다음 단계를 실행한다:

  1. |userResponsePromise|를 {{BeforeInstallPromptEvent/[[userResponsePromise]]}}로 둔다.
  2. |userResponsePromise|가 pending이면:
    1. [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}}가 `true`이면, 이 알고리즘을 종료한다.
    2. 이 이벤트의 {{Event/isTrusted}} 속성이 `false`이면, |userResponsePromise|를 {{"NotAllowedError"}}로 reject하고 이 알고리즘을 종료한다.
    3. [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}}를 `true`로 설정한다.
    4. In parallel로, [=this=]와 함께 request to present an install prompt한다. 최종 사용자가 선택할 때까지, 가능한 한 무기한으로 기다린다.
  3. |userResponsePromise|를 반환한다.

{{BeforeInstallPromptEvent}} event와 함께 request to present an install prompt하려면:

  1. Present an install prompt하고 |outcome|을 그 결과로 둔다.
  2. |response|를 새로 생성된 {{PromptResponseObject}}로 두고, {{PromptResponseObject/userChoice}}를 |outcome|으로 초기화한다.
  3. |event|.{{BeforeInstallPromptEvent/[[userResponsePromise]]}}를 |response|로 [=Resolve=]한다.

사용 예제

이 예제는 사용자가 버튼을 클릭하여 site-triggered install prompt를 표시할 때까지 automated install prompt가 표시되지 않도록 하는 방법을 보여준다. 이런 방식으로, 사이트는 설치를 임의의 시점에 prompting하는 대신 사용자의 재량에 맡길 수 있으며, 동시에 이를 위한 눈에 띄는 UI를 제공할 수 있다.

              window.addEventListener("beforeinstallprompt", event => {
                // Suppress automatic prompting.
                event.preventDefault();

                // Show the (disabled-by-default) install button. This button
                // resolves the installButtonClicked promise when clicked.
                installButton.disabled = false;

                // Wait for the user to click the button.
                installButton.addEventListener("click", async e => {
                  // The prompt() method can only be used once.
                  installButton.disabled = true;

                  // Show the prompt.
                  const userChoice = await event.prompt();
                  console.info(`user choice was: ${userChoice}`);
                });
              });
            

AppBannerPromptOutcome enum

AppBannerPromptOutcome enum의 값은 presenting an install prompt의 outcomes를 나타낸다.

"accepted":
최종 사용자가 사용자 에이전트가 웹 애플리케이션을 설치하기를 원한다고 나타냈다.
"dismissed":
최종 사용자가 install prompt를 dismiss했다.

`Window` object에 대한 확장

          partial interface Window {
            attribute EventHandler onappinstalled;
            attribute EventHandler onbeforeinstallprompt;
          };
        

onappinstalled 속성

onappinstalled event handler IDL attribute는 "appinstalled" 이벤트를 처리한다.

onbeforeinstallprompt 속성

onbeforeinstallprompt event handler IDL attribute는 "beforeinstallprompt" 이벤트를 처리한다.