本规范为 Web Application Manifest 扩展和孵化功能提供特性规范,这些功能 Chromium 已经发布,但 其他用户代理尚未承诺支持/实现。这些特性并非继续作为解释器保存, 而是在此处以更正式的方式记录。

这是一个非正式提案。

display_override 成员

对于高级用法,可以使用 [=manifest/display_override=] 成员来 指定 显示模式 列表值 的自定义后备顺序,以便 开发者为 Web 应用程序选择其首选的 显示模式。其 值是一个 [=display mode=]。

[=application manifest=] 的 [=manifest/display_override=] 成员是 序列,由 显示模式列表 值 组成, 包括 [=display mode/window-controls-overlay=] 和 [=display mode/unframed=] 等扩展。此成员表示开发者 对 [=display mode=] 的首选后备链。此字段会覆盖 [=manifest/display=] 成员。如果用户代理不支持此处指定的任何 [=display mode=],则会回退到考虑 [=manifest/display=] 成员。 算法步骤见处理 display 成员

以下步骤会添加到 确定 Web 应用的选定显示模式 中的 [=application manifest/processing extension-point=]:

  1. [=list/For each=] |candidate_display_mode:DisplayModeType|,其属于 |manifest:ordered map|.[=manifest/display_override=] 成员:
    1. 如果 显示 模式列表 包含 |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|。

此成员旨在仅用于高级场景,在这些场景中, 开发者希望显式控制其显示模式的后备顺序,或者用于基本 显示模式 列表 中不可用的模式。否则,对于 大多数用例,[=manifest/display=] 成员已足够。

概念

显示模式扩展

除了普通的 显示模式 之外, [=manifest/display_override=] 还支持其某些扩展。

unframed
隔离的 Web 应用程序没有任何可见的宿主原生标题栏 或 [=window controls=],且 Web 内容扩展到整个标题栏区域。 应用可以在 Web 内容中指定 [=draggable region=], 以创建自定义标题栏。
[=display mode/window-controls-overlay=]
tabbed
Web 应用程序可以在单个操作系统级窗口中组合多个 [=application contexts=]。例如,这可能意味着用户代理显示 一个标签栏 UI,以允许用户在这些应用程序上下文之间切换。

[=manifest/display_override=] 使用示例

下面展示了一个 [=manifest=],它相比 standalone 更偏好 minimal-ui 显示模式, 但如果不支持 minimal-ui, 则回退到 standalone,而不是 browser

            {
              "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 属性尚未在任何用户代理中实现, 因此它处于风险中。请注意,一些用户代理会将非标准 CSS 属性 `-webkit-app-region` 用于相同目的。

`app-region` 属性可用于通过 CSS 定义哪些区域或元素(例如标题栏中的区域或元素) 是可拖动的。

对处理清单的扩展

为了便于使用本规范添加的所有新扩展和孵化功能, 用户代理应在 [=processing a manifest=] 中的 扩展 点 期间运行以下内容(可访问 [=URL=] |document URL:URL|、[=URL=] |manifest URL:URL|、[=ordered map=] |json:ordered map| 和 [=ordered map=] |manifest:ordered map|):

  1. [=Process the `tab_strip` member=],传入 |json|、|manifest| 和 |manifest URL|。
  2. [=Process the `note_taking` member=],传入 |json|、|manifest| 和 |manifest URL|。
  3. [=Process the `protocol_handlers` member=],传入 |json| 和 |manifest|。
  4. [=Process the `file_handlers` member=],传入 |json|、|manifest| 和 |manifest URL|。
  5. [=Process the `related_applications` member=],传入 |json| 和 |manifest|。
  6. [=Process the `scope_extensions` member=],传入 |json| 和 |manifest|。
  7. [=Process the `migrate_from` member=],传入 |json|、|manifest| 和 |manifest URL|。
  8. [=Process the `migrate_to` member=],传入 |json|、|manifest| 和 |manifest URL|。

tab_strip 成员

Web Application Manifest 的 `tab_strip` 成员是一个 对象,其中包含关于 应用程序在 [=display mode/tabbed=] 显示模式中预期如何行为的信息。 它具有以下成员:

home_tab 成员

[=tab_strip=] 对象的 `home_tab` 成员是一个有序映射, 其中包含关于一个特殊“主页标签页”的信息,该主页标签页 旨在作为应用程序的顶级菜单。它包含以下成员:

scope_patterns 成员是一个 {{URLPatternInput}} 列表,用于定义相对于 [=manifest URL=] 的 [=within home tab scope|主页标签页的作用域=]。

如果应用程序所应用的 [=display mode=] 是 [=display mode/tabbed=],且 [=Document/processed manifest=] 包含 [=tab_strip=] 成员的非 null [=home_tab=] 成员,则称该应用程序具有主页标签页

主页标签页上下文是一个可选的 [=application context=],与其他应用程序上下文相比具有特殊属性。 如果应用程序 [=has a home tab=],每个应用程序窗口都应具有一个 [=home tab context=]。否则,应用程序窗口不应具有 [=home tab context=]。

[=home tab context=] 如何呈现由用户代理决定,但它应具有 不同于普通应用程序上下文的外观。

当且仅当满足以下条件时,称 [=URL=] |url:URL| 在主页标签页作用域内

如果某 URL 不 [=within home tab scope=],则该 URL 在主页标签页作用域外

每个窗口都有一个主页标签页

如果应用程序 [=has a home tab=],则每当创建新的应用程序 窗口时(例如启动应用程序时,或将标签页移动到新窗口时), 用户代理必须在该窗口中创建一个新的 [=home tab context=]。 新创建的 [=home tab context=] 应被导航到 [=start URL=],该 URL 根据定义 [=within home tab scope=]。

与主页标签页作用域相关的导航

当将与 [=home tab context=] 关联的 [=top-level traversable=] [=navigate|导航=] 到 [=outside of home tab scope=] 的 [=URL=] |url:URL| 时,运行以下步骤:

  1. 令 [=top-level traversable=] |tab:toplevel traversable| 为 选择目标为 "_blank" 且 noopener 为 true 的可导航对象所得的结果。
  2. 不要 [=navigating=] 主页标签页 traversable, 而是以相同参数 [=navigate=] |tab|。
  3. 将当前 [=application manifest=] [=applied|应用=] 到 |tab| 的 [=top-level browsing context=]。
  4. 用户代理应将 |tab| 放置在与主页标签页可导航对象相同的窗口中。
  5. 聚焦 |tab|。

当将具有 [=display mode/tabbed=] [=display mode=] 且不与 [=home tab context=] 关联的 [=top-level traversable=] (即非主页标签页)[=navigate|导航=] 到 [=within home tab scope=] 的 [=URL=] |url:URL| 时,运行以下步骤:

  1. 令 |hometab:toplevel traversable| 为与当前窗口关联的 [=home tab context=] 的 [=top-level traversable=]。
  2. 不要 [=navigating=] 该顶级 traversable, 而是以相同参数 [=navigate=] |hometab|。
  3. 聚焦 |hometab|。

主页标签页不变量

上述规则旨在确保以下不变量对于 [=has a home tab|具有主页标签页=] 的应用程序始终为真:

  • 每个应用程序窗口恰好有一个 [=home tab context=], 且
  • 每个 [=home tab context=] 的活动文档的 [=Document/URL=] 是 [=within home tab scope=](除非该文档的 URL 自创建以来已更改),且
  • 每个非主页标签页 [=application context=] 的活动文档的 [=Document/URL=] 是 [=outside of home tab scope=] (除非该文档的 URL 自创建以来已更改)。

如果文档更改其 [=URL/fragment=],或使用 {{History}} API 将其显示 URL 修改为进入或离开主页标签页作用域,用户代理不会 在主页标签页和非主页标签页上下文之间动态移动文档,因为没有发生导航。 因此,上述不变量只关注文档创建时所具有的 [=Document/URLs=]。

对于通过修改其 URL 来“假装”导航的单页应用程序, 这可能导致不希望出现的行为,从而破坏上述不变量 (例如,如果用户在主页标签页中点击链接,将 URL 动态更改为非主页页面, 他们仍会停留在主页标签页内,因为实际上并没有导航)。 为避免这种情况,应用程序可以检测自己是否处于标签式应用程序模式, 并改变其行为,以执行真正进入和离开主页标签页作用域的导航, 而不是修改 URL。

new_tab_button 成员

[=tab_strip/new_tab_button=] 成员是一个有序映射, 用于描述一个 UI 便利项(例如按钮)的行为; 当该便利项被点击/激活时,会在应用程序窗口内打开一个新的 [=application context=]。它具有以下成员:

url 成员 是一个字符串,表示相对于 [=manifest URL=] 的 URL, 该 URL 是某个 [=Document/processed manifest=] 的 [=manifest/within scope=]。

如果 [=Document/processed manifest=] 的 [=new_tab_button=] 的 [=new_tab_button/url=] 成员 [=outside of home tab scope=], 则应用程序具有新标签页按钮。如果应用程序不 [=has a new tab button|具有新标签页 按钮=],用户代理不应向最终用户提供“新建标签页”便利项。

当最终用户激活新标签页按钮时,运行以下步骤:

  1. 在当前窗口中 [=Create a new application context=] 并 聚焦它。
  2. 将其导航到 [=new_tab_button=] 的 [=new_tab_button/url=] 成员的值。

处理 `tab_strip` 成员

处理 `tab_strip` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map| 和 [=URL=] |manifest URL:URL|,在 [=processing a manifest=] 的 扩展点期间运行以下步骤:

  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. [=Process the `home_tab` member=],传入 |json|["tab_strip"]、 |manifest|["tab_strip"] 和 |manifest URL|。
  4. [=Process the `new_tab_button` member=],传入 |json|["tab_strip"]、|manifest|["tab_strip"]、|manifest URL| 和 |manifest|["start_url"]。

处理 `home_tab` 成员

处理 `home_tab` 成员,给定 [=ordered map=] |json tab strip:ordered map|、[=ordered map=] |manifest tab strip:ordered map| 和 [=URL=] |manifest URL:URL|,运行 以下步骤:

  1. 如果 |json tab strip|["home_tab"] 不存在,或 |json tab strip|["home_tab"] 不是 [=ordered map=],返回。
  2. 令 |home tab:ordered map| 为一个新的 [=ordered map=]。
  3. [=Process the `scope_patterns` member=],传入 |json tab strip|["home_tab"]["scope_patterns"]、|home tab| 和 |manifest URL|。
  4. 将 |manifest tab strip|["home_tab"] 设置为 |home tab|。

处理 `new_tab_button` 成员

处理 `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|,运行以下步骤:

  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| 作为基准 URL [=URL Parser|解析=] |json tab strip|["new_tab_button"]["url"] 所得的结果。
  4. 如果 |url| 是失败,返回。
  5. 如果 |url| 不是 |manifest URL| 的 [=URL/within scope=],返回。
  6. 将 |manifest tab strip|["new_tab_button"]["url"] 设置为 |url|。

处理 `scope_patterns` 成员

处理 `scope_patterns` 成员,给定 [=ordered map=] |json home tab:ordered map|、[=ordered map=] |manifest home tab:ordered map| 和 [=URL=] |manifest URL:URL|,运行以下步骤:

  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 值构建 URL 模式=] 所得的结果。如果此过程抛出或返回 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"
            }
          }
        }
        

此示例是一个标签式 Web 应用;如果不支持标签式模式, 它会回退到单文档 standalone 窗口。任何到主索引页的导航 (即 //index.html)都会在 [=home tab context=] 中打开。新标签页按钮会在 /create 打开一个新标签页。

请注意,当与 [=tab_strip/home_tab/scope_patterns=] 进行匹配时, URL 的 [=URL/query=] 部分默认会被忽略(因此到 /index.html?utm_source=foo 的导航会在 主页标签页中打开)。但是,当与 [=start URL=] 匹配时, [=URL/query=] 必须完全匹配,因此希望忽略查询的站点 建议显式包含 [=start URL=] 的 [=URL/path=] 作为作用域模式。

`share_target` 成员

`share_target` 成员将 Web 应用程序注册为共享操作的“目标” (例如,用于共享文本、URL 或文件)。 `share_target` 成员是 [[[web-share-target]]] 规范的一部分。

note_taking 成员

Web Application Manifest 的 `note_taking` 成员是一个 对象,其中包含 与记笔记相关的信息。它具有以下成员:

用户代理可以使用这些成员,将 Web 应用程序作为具有记笔记能力的 应用程序进行区别对待(例如,与操作系统记笔记界面集成)。

new_note_url 成员

[=note_taking=] `new_note_url` 成员是一个 [=string=], 表示开发者希望用户代理在用户想使用 Web 应用程序创建新笔记时 加载的 URL (例如,从操作系统快捷方式图标或键盘快捷键启动时)。

`new_note_url` 成员纯粹是建议性的,用户代理可以 忽略它,或向 最终用户提供是否使用它的选择。用户代理可以向最终用户提供 修改它的选择。

使用示例

下面展示了一个记笔记应用程序的 [=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` 成员

处理 `note_taking` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map|、[=URL=] |manifest_URL:URL|,在 [=processing a manifest=] 的 扩展 点期间运行以下步骤:

  1. 如果 |json|["note_taking"] 不 [=map/exist=],返回。
  2. 如果 |json|["note_taking"] 的类型不是 [=ordered map=], 返回。
  3. 将 |manifest|["note_taking"] 设置为一个新的 [=ordered map=]。
  4. [=process the `new_note_url` member=],传入 |json|["note_taking"]、|manifest|["note_taking"] 和 |manifest URL|。

处理 `new_note_url` 成员

处理 `new_note_url` 成员,给定 [=ordered map=] |json_note_taking:ordered map|、[=ordered map=] |manifest_note_taking:ordered map|、[=URL=] |manifest_URL:URL|,运行 以下步骤:

  1. 如果 |json_note_taking|["new_note_url"] 不 [=map/exist=], 返回。
  2. 如果 |json_note_taking|["new_note_url"] 的类型不是 [=string=],返回。
  3. 令 |new_note_url:URL| 为以 |manifest URL| 作为基准 URL [=URL Parser|解析=] |json_note_taking|["new_note_url"] 所得的结果。
  4. 如果 |new_note_url| 是失败,返回。
  5. 如果 |new_note_url| 不是 |manifest URL| 的 [=manifest/within scope=],返回。
  6. 将 manifest_note_taking["new_note_url"] 设置为 |new_note_url|。

启动 `new_note_url`

启动 `new_note_url`,给定 已处理清单 |manifest:processed manifest|,运行以下步骤:

  1. 如果 |manifest|["note_taking"] 不 [=map/exist=],返回。
  2. 如果 |manifest|["note_taking"]["new_note_url"] 不 [=map/exist=],返回。
  3. 如果 |manifest|["note_taking"]["new_note_url"] 的类型不是 [=URL=],返回。
  4. 运行 [=launch a web application=] 的步骤,将 |manifest| 设置为 |manifest|,并将 |target URL| 设置为 |manifest|["note_taking"]["new_note_url"]。

用户代理可以随时为给定的 [=installed web application=] [=launch the `new_note_url`=],通常是响应用户便利项。

`protocol_handlers` 成员

[=manifest's=] protocol_handlers 成员是一个 协议处理器描述数组,允许 Web 应用程序处理 URL 协议。

在安装时,用户代理应通过符合以下内容的交互, 向操作系统注册协议处理器:

处理 `protocol_handlers` 成员

处理 `protocol_handlers` 成员,给定 [=object=] |json:JSON|、|manifest:ordered map|:

  1. 令 |processedProtocolHandlers| 为一个新的 [=list=], 由 |json:JSON|["|protocol_handlers|"] 组成。
  2. 将 manifest["|protocol_handlers|"] 设置为 |processedProtocolHandlers|。
  3. [=list/For each=] |protocol_handler|(协议处理器 描述):
    1. 如果 |protocol_handler|["protocol"] 或 |protocol_handler|["url"] 为 undefined,则 [=iteration/continue=]。
    2. 令 (|normalizedProtocol:string|, |normalizedUrl:URL|) 为使用 |manifest URL| 作为基准 URL,并使用 [=this=] 的相关 [=environment settings object=],以 |protocol_handler|["protocol"]、| protocol_handler|["url"] 运行 [=normalize protocol handler parameters=] 所得的结果。 如果结果为失败,则 [=iteration/continue=]。
    3. 如果 |normalizedUrl| 不是 |manifest| 的 [=manifest/within scope=],则 [=iteration/continue=]。
    4. 如果 |processedProtocolHandlers| [=list/contains=] |normalizedUrl|,则 [=iteration/continue=]。
    5. [=List/Append=] |protocol_handler| 到 |processedProtocolHandlers|。
  4. [=list/For each=] |processedProtocolHandlers|,用户代理 应 [=register a protocol handler=]。

用户代理应在将 [=protocol handler description=] protocol_handlers 注册为宿主操作系统中某协议的 默认处理器之前请求用户许可。用户代理可以截断所呈现的 [=protocol handler description=] protocol_handlers 列表,以保持与宿主操作系统的惯例或限制一致。

协议处理器项

每个 协议处理器描述 是一个 [=object=], 表示 Web 应用程序想要处理的协议,对应于 [=manifest/protocol_handlers=] 成员。它具有以下成员:

用户代理应使用这些值将 Web 应用程序注册为操作系统中的处理器。 当用户激活协议处理器 URL 时,用户代理应运行 处理协议启动

[[HTML]] 的 {{NavigatorContentUtils/registerProtocolHandler()}} 允许 Web 站点将自身注册为特定协议的可能处理器。 对 协议处理器描述 而言,什么构成有效的 [=protocol handler description/protocol=] 和 [=protocol handler description/url=] 值由该 API 定义。还请注意,[[HTML]] API 使用 scheme,而这里使用 [=protocol handler description/protocol=], 但适用相同的限制。

`protocol` 成员

协议处理器描述protocol 成员是一个 字符串,表示要处理的协议,例如 `mailto` 或 `web+auth`。

协议处理器描述 的 [=protocol handler description/protocol=] 成员等价于 [[HTML]] 中定义的 {{NavigatorContentUtils/registerProtocolHandler()}} 的 scheme 参数。

`url` 成员

协议处理器描述url 成员是相关协议 被激活时打开的应用程序 [=manifest/within scope=] 的 URL

协议处理器描述 的 [=protocol handler description/url=] 成员等价于 [[HTML]] 中定义的 {{NavigatorContentUtils/registerProtocolHandler()}} 的 url 参数。

处理协议启动

当具有 [=manifest=] manifest协议处理器描述 protocol_handler 被调用时,它会经过与 [=invoke a protocol handler=] 相同的步骤;其中用户代理不是 [=navigating=] 到 resultURL,而是应 [=launch a web application=],传入 manifestresultURL

不应以这种方式调用并修改 [=invoke a protocol handler=]。 resultURL 的计算应被提取为一个单独的算法, 供通用使用。

隐私考虑:默认协议处理器

根据操作系统能力,协议处理器可能会在用户没有明确知情的情况下 成为给定协议的“默认”处理器(例如自动处理给定协议的启动)。 为防止可能的滥用,用户代理可以采用如下保护措施:

如果用户代理移除协议处理器实体的注册,则应为用户提供 UX, 以便用户可重新向操作系统注册 Web 应用和协议。

`file_handlers` 成员

[=manifest's=] file_handlers 成员是一个 [=list=], 它提供关于应用如何处理源自应用自身之外的文件打开操作的指令。

已注册文件处理应用程序的管理、呈现和选择由操作系统决定。

处理 `file_handlers` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map|、[=URL=] |manifest_url:URL|:

  1. 令 |processedFileHandlers:list| 为一个新的 [=list=]。
  2. 将 |manifest|["file_handlers"] 设置为 |processedFileHandlers|。
  3. 如果 |json|["file_handlers"] 不 [=map/exist=],或 |json|["file_handlers"] 不是 [=list=],返回。
  4. [=list/For each=] |entry:ordered map|,其属于 |json|["file_handlers"]:
    1. 令 |file_handler:ordered map| 为以 |entry|、 [=manifest/start_url=]、[=manifest/scope=] 和 |manifest_url| 运行 [=process a file handler item=] 所得的结果。
    2. 如果 |file_handler| 是失败,则 [=iteration/continue=]。
    3. [=list/Append=] |file_handler| 到 |processedFileHandlers|。

在 [=installation=] 时,用户代理应运行 [=register file handlers=] 的过程。

文件处理器项

每个 文件处理器 表示 应用程序作用域内的一个 URL,该 URL 可处理带有其所接受 [=file types=] 的启动。它具有以下成员:

用户代理可以使用这些成员,在操作系统上将 Web 应用程序与 [=file type=] 关联。

文件类型可由 [=MIME type=] 和/或 [=file extension=] 定义。如果 OS 判定文件具有某个 [=MIME type=],和/或其名称以某个 [=file extension=] 结尾, 则该文件属于某个文件类型。文件扩展名是一个 以 "." 开头且只包含 有效 后缀代码 点的字符串。此外,[=file extensions=] 被限制为 16 个代码点的长度。

`action` 成员

[=file handler=] 的 action 成员是一个 字符串, 表示相对于 manifest URL 的 URL, 该 URL 是某个 已处理 清单 的 [=manifest/within scope=]。在 [=execute a file handler launch=] 的步骤中会导航到此 URL。

`name` 成员

[=file handler=] 的 name 成员是一个 字符串, 用于向用户标识文件类型。[=User agents=] 可以在文件处理器 注册期间将此信息传递给操作系统。

`icons` 成员

文件处理器icons 成员列出作为 [=file type=] 图形表示的图标。用户代理可以在文件处理器 注册期间将此信息传递给操作系统。

`accept` 成员

[=file handler=] 的 accept 成员是一个 字典, 将 [=MIME types=] 映射到 [=file extensions=] 列表。

[=User agents=] 必须强制执行 [=file handler/accept=] 条目 仅适用于具有匹配扩展名的文件。

为了 [=register file handlers=],某些操作系统需要 [=MIME types=],某些则需要 [=file extensions=]。因此, 对于每个 [=file handler/accept=] 条目,清单必须始终同时提供两者。

除了完整的 [=MIME types=] 之外,"*" 可用作 [=MIME type=] 的子类型,以匹配例如 "image/*" 的所有图像格式(这些格式也要适用于 所提供的 [=file extensions=] 列表)。

`launch_type` 成员

[=file handler=] 的 launch_type 成员是一个 字符串,用于确定应用如何针对路由到此处理器的文件启动。 可能的值为 `"single-client"` 和 `"multiple-clients"`。如果未提供,则默认为 `"single-client"`。

当某个 [=file handler=] 被确定为匹配一组文件时, [=user agent=] 应使用 [=file handler/launch_type=] 来控制 应用是为每个文件启动一次 (`"multiple-clients"`),还是为所有文件启动一次 (`"single-client"`)。见 {{LaunchParams/files}}。用户代理 不得将来自不同 [=file handlers=] 的文件合并到 单个启动事件中。

处理文件处理器项

处理文件处理器项,给定 [=ordered map=] |item:ordered map|、[=URL=] |start_url:URL|、[=URL=] |scope:URL| 和 [=URL=] |manifest URL:URL|:

  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| 作为基准 URL [=URL Parser|解析=] |item|["action"] 所得的结果。
  3. 如果 |url| 是 failure,返回 failure。
  4. 如果 |url| 不是 |scope| 的 [=manifest/within scope=], 返回 failure。
  5. 令 |launch_type:string| 为一个新的 [=string=],并初始化为 "single-client"。
  6. 如果 |item|["launch_type"] [=map/exists=] 且为 "multiple-clients",则将 |launch_type| 设置为 |item|["launch_type"]。
  7. 令 |accept:ordered map| 为一个新的 [=ordered map=]。
  8. [=map/for each=] |mime_type_string:string| → |extensions|,其属于 |item|["accept"]
    1. 如果 |extensions| 不是 [=list=],则 [=iteration/continue=]。
    2. 如果 |extensions| 是 [=list/empty=],则 [=iteration/continue=]。
    3. 如果 |extensions| [=list/contains=] 非 [=string=] 项,则 [=iteration/continue=]。
    4. 如果 |extensions| [=list/contains=] 不以字符 `.` 开头的字符串,则 [=iteration/continue=]。
    5. 如果 |extensions| [=list/contains=] 长度大于 16 个字符的字符串,则 [=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]] 中的顶级类型,则 [=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"。
  12. 返回 |file_handler|。

执行文件处理器启动

执行文件处理器启动的步骤由以下算法给出。 该算法接受 [=list=] |files:list| 和一个 [=ordered map=] |manifest:ordered_map|,该有序映射保存 [=processing a manifest=] 的结果。

  1. 令 |file_handlers:list| 为 |manifest|["file_handlers"]。
  2. 如果 |file_handlers:list| 为 null,返回。
  3. 令 |launches:ordered map| 为一个 [=ordered map=]。
  4. [=list/for each=] |filename:string|,其属于 |files|
    1. [=list/for each=] |file_handler:ordered_map|,其属于 |file_handlers:list|:
      1. [=map/for each=] |mime_type_string:string| → |extensions:list|,其属于 |file_handler|["accept"]
        1. [=list/for each=] |extension:string|,其属于 |extensions|:
          1. 如果 |filename| 不以 |extension| 结尾, 则 [=iteration/continue=]。
          2. 如果 |launches|[|file_handler|] [=map/exists=], 则 [=list/append=] |filename| 到 |launches|[|file_handler|]。
          3. 否则,将 |launches|[|file_handler|] 设置为 一个 [=list=],其中唯一元素为 |filename|。
          4. [=iteration/Continue=] 到 |files| 的下一个元素。
  5. [=map/for each=] |file_handler| → |files:list|,其属于 |launches|
    1. 如果 |file_handler|["launch_type"] 等于 "multiple-clients"
      1. [=list/for each=] |file|,其属于 |files|
        1. 令 |params:LaunchParams| 为一个新的 {{LaunchParams}}, 其中 {{LaunchParams/targetURL}} 设置为 |file_handler|["action"],且 {{LaunchParams/files}} 设置为 一个 {{FrozenArray}},其中唯一元素是与 |file| 对应的 {{FileSystemHandle}}。
        2. [=Launch a web application with handling=],传入 |manifest| 和 |params|。
    2. 否则,
      1. 令 |params:LaunchParams| 为一个新的 {{LaunchParams}}, 其中 {{LaunchParams/targetURL}} 设置为 |file_handler|["action"], 且 {{LaunchParams/files}} 设置为由与 |files| 指定的文件路径 对应的 {{FileSystemHandle}} 组成的 {{FrozenArray}}。
      2. [=Launch a web application with handling=],传入 |manifest| 和 |params|。

注册文件处理器

用户代理应以与以下内容一致的方式,在宿主操作系统中 注册文件处理器

用户代理可以截断 [=file extensions=] 的总集合,以保留功能并防止滥用。 用户代理可以阻止与某些文件类型建立关联。

隐私考虑:默认文件处理器。

根据操作系统能力,[=file handler=] 可能会在用户没有明确知情的情况下 成为给定 [=file type=] 的默认处理器,自动处理给定文件类型的启动。 为防止可能的误用,[=user agents=] 可以要求用户明确同意才开始 [=execute a file handler launch=] 的过程。如果请求同意,用户代理应允许用户指定该决定是永久的; 如果如此指定,则应移除 Web 应用程序作为文件处理实体的 OS 注册。

隐私考虑:名称和图标。

每个文件处理器的名称和图标可能涉及隐私和安全,因为没有指定 用户查看并确认这些内容的方式。因此,[=user agents=] 可以选择忽略 这些内容,转而使用替代字符串和图标,或回退到 OS 默认值。

带有文件处理器的示例清单

在以下示例中,Web 应用程序具有 3 个不同的文件处理器。

`related_applications` 成员

相关应用程序是底层应用程序平台可访问的应用程序, 它与 Web 应用程序存在某种关系。

[=manifest's=] related_applications 成员列出相关 应用程序,并作为 Web 应用程序与相关应用程序之间 存在这种关系的指示。此关系是单向的,除非所列应用程序声称 存在相同关系,否则用户代理不得假定存在双向认可。

`related_applications` 的使用示例可以是爬虫利用该信息收集 关于 Web 应用程序的更多信息,或浏览器在用户想要安装 Web 应用程序时 建议所列应用程序作为替代。

处理 `related_applications` 成员,给定 [=ordered map=] |json:ordered map| 和 [=ordered map=] |manifest:ordered map|:

  1. 令 |relatedApplications:list| 为一个新的 [=list=]。
  2. 将 |manifest|["related_applications"] 设置为 |relatedApplications|。
  3. 如果 |json|["related_applications"] 不 [=map/exist=] 或 |json|["related_applications"] 不是 [=list=],返回。
  4. [=list/For each=] app,其属于 |json|["related_applications"]:
    1. 如果 app["id"] 和 app["url"] 都不缺失:
      1. app["url"] 设置为给定 app["url"] 运行 处理应用程序的 `url` 成员所得的结果。
      2. [=list/append=] apprelatedApplications
  5. 设置 relatedApplications

`prefer_related_applications` 成员

[=manifest's=] `prefer_related_applications` 成员是一个 [=boolean=],用作给用户代理的提示,表示应优先使用 相关应用程序而不是 Web 应用程序。如果 `prefer_related_applications` 成员设置为 `true`,且用户代理想要 建议安装该 Web 应用程序,则用户代理可能想要改为建议安装其中一个 相关应用程序

`scope_extensions` 成员

[=manifest's=] scope_extensions 成员表示应用程序期望的 [=scope extensions=] 列表。

作用域扩展通过描述应被视为 [=within extended scope=] 的额外 [=URLs=],扩展 [=manifest/navigation scope of a manifest=]。

用户代理必须先 [=process the scope_extensions member=] 并 [=validate scope extensions=],之后才能 [=apply extended scope=], 以允许额外的 [=URLs=] 被视为 [=within extended scope=]。

如果 |target| 是清单的 [=manifest/within scope=],或 [=matches a validated scope extension=], 则 [=URL=] |target:URL| 在扩展作用域内于 |manifest:processed manifest|。

如果给定 [=URL=] |target:URL| 和 [=list=] |validated_scope_extensions:list| 时,以下算法返回 `true`, 则 [=URL=] |target:URL| 匹配已验证的作用域扩展

  1. [=list/For each=] |entry:ordered map|,其属于 |validated_scope_extensions:list|:
    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" }]
          }
        

下面展示了 2 个 [=web-app-origin-association=] 文件, 它们可从 `scope_extensions` 成员中列出的 [=origins=] 的 `.well-known` 路径下载。

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

此应用的导航作用域由 [=URL/within scope=] 于以下任一 [=URLs=] 的 URL 构成: `https://example.com/app`、`https://example.co.uk/app` 和 `https://help.example.com`。

处理 `scope_extensions` 成员

处理 `scope_extensions` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map| 和 [=URL=] |manifest_id:URL|:

  1. 令 |processedScopeExtensions:list| 为一个新的 [=list=]。
  2. 将 |manifest|["scope_extensions"] 设置为 |processedScopeExtensions|。
  3. 如果 |json|["scope_extensions"] 不 [=map/exist=] 或不是 [=list=],返回。
  4. [=list/For each=] |entry:ordered map|,其属于 |json|["scope_extensions"]:
    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| 为 [=URL Parser|解析=] |entry|["origin"] 所得的结果。
    5. 如果 |origin_url| 是具有 [=URL/scheme=] "https" 的有效 |URL|,则将 |entry|["origin"] 设置为 |origin_url| 的 [=URL/origin=]。
    6. 否则 [=iteration/continue=]。
    7. 使用 [=validate scope extensions=] 中的算法验证 |entry|,给定 [=ordered map=] |entry:ordered map| 和 [=URL=] |manifest_id:URL|。
    8. 如果上一步返回错误, 则 [=iteration/continue=]。
    9. 否则,将 |entry|["scope"] 设置为返回的 [=URL=]。
    10. [=list/Append=] |entry| 到 |processedScopeExtensions|。

验证作用域扩展

验证作用域扩展,给定 [=ordered map=] |extension:ordered map| 和 [=URL=] |manifest_id:URL|:

  1. 令 [=URL=] |origin_url:URL| 为 |extension|["origin"] 的 [=URL/origin=] 的 [=URL=]。
  2. 令 [=URL=] |download_url:URL| 为使用 |origin_url| 作为基准 [=URL=] [=URL Parser|解析=] "/.well-known/web-app-origin-association" 所得的结果。
  3. 令 [=ordered map=] |json:ordered map| 为给定 |download_url| 进行 [=fetch=] 所得的结果。
  4. 如果 [=fetch=] 失败,返回错误。
  5. 如果 |json|[|manifest_id|] 不 [=map/exist=] 或不是 [=ordered map=],返回错误。
  6. 令 [=string=] |scope:string| 为 "/"。
  7. 如果 |json|[|manifest_id|]["scope"] [=map/exists=] 且是 [=string=],则将 |scope| 设置为 |json|[|manifest_id|]["scope"]。
  8. 令 [=URL=] |resolved_scope:URL| 为以 |extension|["origin"] 作为基准 [=URL=] [=URL Parser|解析=] |scope| 所得的结果。
  9. 返回 |resolved_scope|。

应用作用域扩展

用户代理可以选择在某些场景中应用 应用扩展作用域,而在其他场景中应用 [=manifest/scope=]。

如果 [=application context=] 的 [=navigable/active document=] 的 [=URL=] 不是 [=manifest/within scope=],但 [=within extended scope=],则用户代理应提供 UI,允许用户确定该 [=URL=] 或至少其 [=origin=],包括它是否通过安全连接提供。该 UI 应区别于 当 [=URL=] 不是 [=application context=] 的 [=Document/processed manifest=] 的 [=manifest/within scope=] 时 使用的任何 UI,以便明显表明用户仍在导航该应用程序的预期内容, 但也让用户了解导航到不同 [=origin=] 的隐私和安全影响。

web-app-origin-association 文件

web-app-origin-association 文件是一个 JSON 文件, 可用于 [=validate scope extensions=] 或 [=validate origin migration=]。它确认其所在源与一个或多个 Web 应用程序之间的关联。 它通过引用清单 [=manifest/ids=] 来唯一标识应用。

给定 [=origin=] |origin:origin|,期望可从 [origin]/.well-known/web-app-origin-association 下载 [=web-app-origin-association=] 文件。

Web 应用程序源迁移

Web 应用程序源迁移允许 Web 应用程序向用户代理指示 它已从一个源移动到另一个源(在同一站点内)。这使用户代理能够 迁移已安装的 Web 应用程序,同时保留用户的安装状态以及可能的设置。

使用示例

以下示例展示了托管在 https://old.example.com 的 Web 应用程序如何迁移到 https://new.example.com。两个源必须是 [=same site=],并且必须明确同意迁移。

首先,旧应用程序的清单可以可选地包含一个 `migrate_to` 成员,用于向用户代理发出其打算移动的信号 (或者旧应用程序可以重定向到新应用程序):

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

接着,新应用程序的清单包含一个 `migrate_from` 成员, 用于声明从旧应用程序迁移:

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

最后,旧源必须托管一个 `web-app-origin-association` 文件,以最终验证它允许新应用程序接管。没有此文件, 恶意的新应用程序可能会未经许可声称从旧应用程序迁移。

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

migrate_from 成员

[=manifest/migrate_from=] 成员是一个由 [=strings=] 或 [=ordered maps=] 组成的 [=list=],用于标识正在从中迁移的 旧 Web 应用程序。

如果条目是 [=string=],则将其视为旧应用程序的 [=manifest/id=]。

如果条目是 [=ordered map=],它可以具有以下成员:

为了让用户代理处理 [=manifest/migrate_from=] 成员, 清单还需要在其 JSON 中包含有效的显式 id 成员。

migrate_to 成员

[=manifest/migrate_to=] 成员是一个可选的 [=ordered map=], 用于主动发出向新应用程序迁移的信号。它具有以下成员:

验证源迁移

验证源迁移,给定 [=URL=] |old_manifest_id:URL| 和 [=URL=] |new_manifest_id:URL|:

  1. 如果 |old_manifest_id| 的 [=URL/origin=] 和 |new_manifest_id| 的 [=URL/origin=] 不是 [=same site=],返回错误。
  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| 作为基准 [=URL=] [=URL Parser|解析=] "/.well-known/web-app-origin-association" 所得的结果。
  5. 令 [=ordered map=] |json:ordered map| 为给定 |download_url| 进行 [=fetch=] 所得的结果。
  6. 如果 [=fetch=] 失败,返回错误。
  7. 如果 |json|[|new_manifest_id|] 不 [=map/exist=] 或不是 [=ordered map=],返回错误。
  8. 如果 |json|[|new_manifest_id|]["allow_migration"] 不是 `true`, 返回错误。
  9. 返回 success。

处理 `migrate_from` 成员

处理 `migrate_from` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map| 和 [=URL=] |manifest URL:URL|:

  1. 如果 |json|["id"] 不 [=map/exist=] 或不是 [=string=], 返回。
  2. 如果 |json|["id"] 是空字符串,返回。
  3. 令 |base_origin:origin| 为 |manifest|["start_url"] 的 [=URL/origin=]。
  4. 令 |id_url:URL| 为以 |base_origin| 作为基准 URL [=URL Parser|解析=] |json|["id"] 所得的结果。
  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. [=list/For each=] |entry|,其属于 |json|["migrate_from"]:
    1. 令 |processed_entry:ordered map| 为一个新的 [=ordered map=]。
    2. 如果 |entry| 是 [=string=]:
      1. 令 |id:URL| 为以 |manifest URL| 作为基准 URL [=URL Parser|解析=] |entry| 所得的结果。
      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| 作为基准 URL [=URL Parser|解析=] |entry|["id"] 所得的结果。
      3. 如果 |id| 是 failure,则 [=iteration/continue=]。
      4. 将 |processed_entry|["id"] 设置为 |id|。
      5. 如果 |entry|["install_url"] [=map/exists=] 且是 [=string=]:
        1. 令 |install_url:URL| 为以 |manifest URL| 作为基准 URL [=URL Parser|解析=] |entry|["install_url"] 所得的结果。
        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. 使用 [=validate origin migration=] 中的算法验证 |processed_entry|["id"],给定 [=URL=] |processed_entry|["id"] 和 [=URL=] |id_url|。
    6. 如果上一步返回错误, 则 [=iteration/continue=]。
    7. [=list/Append=] |processed_entry| 到 |processed_migrate_from|。

处理 `migrate_to` 成员

处理 `migrate_to` 成员,给定 [=ordered map=] |json:ordered map|、[=ordered map=] |manifest:ordered map| 和 [=URL=] |manifest URL:URL|:

  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| 作为基准 URL [=URL Parser|解析=] |entry|["id"] 所得的结果。
  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| 作为 基准 URL [=URL Parser|解析=] |entry|["install_url"] 所得的结果。
    2. 如果 |install_url| 不是 failure,则将 |processed_migrate_to|["install_url"] 设置为 |install_url|。
  9. 将 |manifest|["migrate_to"] 设置为 |processed_migrate_to|。

隐私与安全考虑

为防止恶意行为者静默接管应用程序(例如,一个简单计算器将自身更新为模仿银行应用), [=validate origin migration=] 算法强制执行双向握手。 新旧应用程序源都必须通过 [=web-app-origin-association=] 文件 明确同意迁移。

此外,迁移被限制为 [=same site=],以确保它们用于组织控制范围内 的合法品牌重塑和架构变更,而不是将所有权转移给未经验证的第三方。

用户代理应考虑将显式用户确认对话框作为迁移流程的一部分, 尤其是在包含对安全敏感字段(例如应用名称和图标)的更新时。

外部应用程序资源

每个 外部 应用程序资源 表示一个与 Web 应用程序相关的应用程序。

[=external application resource=] 可以具有以下成员, 其中某些成员是成为 [=valid external application resource=] 所必需的:

有效外部应用程序资源必须具有 [=external application resource/platform=] 成员,以及 [=external application resource/url=] 或 [=external application resource/id=] 成员(或两者)。

`platform` 成员

platform 成员表示 此 [=external application resource=] 所关联的 [=platform=]。 平台 表示软件分发生态系统, 或可能表示操作系统。本规范不定义 platform 成员的具体值。

`url` 成员

[=external application resource's=] url 成员是可以找到该应用程序的 URL

处理应用程序的 `url` 成员

  1. 如果 application URL 缺失,返回 null。
  2. 否则,[=URL Parser|解析=] application URL;如果 结果不是 failure,返回该结果。否则返回 null。

`id` 成员

[=external application resource's=] id 成员表示 在平台上用于表示该应用程序的 id。

`min_version` 成员

[=external application resource's=] min_version 成员 表示被认为与此 Web 应用相关的应用程序最低版本。此版本是一个 字符串,具有平台特定的语法和语义。

`fingerprints` 成员

[=external application resource's=] fingerprints 成员 表示一个 [=fingerprints=] [=list=]。

指纹 表示一组用于验证 应用程序的加密指纹。指纹具有以下两个成员: typevalue。二者都是 字符串,但其语法和语义由平台定义。

安装提示

安装过程可以通过多种方式触发:

无论哪种情况,如果文档不可安装,用户代理都不得 呈现安装提示

用户代理可以在任何时候(仅当文档可安装时) 运行通知安装提示可用的步骤, 从而让站点有机会显示站点触发的 安装提示,而无需用户与用户代理 UI 交互。

呈现 安装提示

  1. 显示某种用户代理特定的 UI,询问用户是否 继续安装应用。该选择的 result 要么是 {{AppBannerPromptOutcome/"accepted"}},要么是 {{AppBannerPromptOutcome/"dismissed"}}。
  2. 返回 result,并且并行地
    1. 如果 result 是 {{AppBannerPromptOutcome/"accepted"}}, 则运行安装 Web 应用程序的步骤

通知安装提示可用的步骤由以下算法给出:

  1. 等待 top-level browsing context 的 {{Document}} 完全 加载
  2. 如果已经有一个 正在呈现的安装提示,或者 安装 Web 应用程序的步骤 当前正在 执行,则中止此步骤。
  3. application life-cycle task source排入一个任务以执行以下操作:
    1. 令 |mayShowPrompt| 为在 [=top-level browsing context=] 的 [=relevant global object=] 上 [=fire an event=] 名为 `"beforeinstallprompt"`、使用 {{BeforeInstallPromptEvent}} 接口并将 {{Event/cancelable}} 属性初始化为 `true` 所得的结果。
    2. 如果 |mayShowPrompt| 为 true,则用户代理可以并行地 使用 |event| 请求呈现安装提示

可安装 Web 应用程序

安装过程

安装 Web 应用程序的步骤由以下算法给出:

  1. manifest 为可安装文档的清单。
  2. 执行一系列未指定的操作,以尝试在用户的操作系统中注册 Web 应用程序(例如,创建启动 Web 应用程序的快捷方式、 在系统卸载菜单中注册应用程序等)。如果安装失败 (可能出于任何原因,例如 OS 拒绝授予用户代理将图标添加到 设备主屏幕的权限),则中止这些步骤。
  3. application life-cycle task source排入一个任务,以在发生安装的 [=top-level browsing context=] 的 [=relevant global object=] 上 触发事件,事件名为 `"appinstalled"`。

可安装性信号

按设计,本规范不向开发者提供显式 API 来“安装” Web 应用程序。相反,manifest 可作为给用户代理的 可安装性信号,表示 Web 应用程序可以被安装。 这些信号会因用户代理而异,因为每个用户代理都有自己的启发式方法 来确定 Web 站点是否符合安装提示的条件。实现者通常会提供文档, 描述其特定的可安装性信号,或 Web 应用程序需要满足的其他相关标准, 以便被视为可安装。

用户代理可能为 Web 应用程序实现的可安装性信号示例:

此列表并非详尽无遗,某些可安装性信号 可能并不适用于所有用户代理。用户代理如何利用这些 可安装性信号 来确定 Web 应用程序是否可安装, 留给实现者决定。

安装事件

本规范的 [=event|事件=] 依赖于 application life-cycle task source

BeforeInstallPromptEvent 接口

beforeinstallprompt 事件的命名有些不准确, 因为它不一定表示随后会发生手动安装 (取决于用户代理,它可能只是赋予站点触发安装提示的能力)。 该名称出于历史原因而保留。
          [Exposed=Window]
          interface BeforeInstallPromptEvent : Event {
            constructor(DOMString type, optional EventInit eventInitDict = {});
            Promise<PromptResponseObject> prompt();
          };

          dictionary PromptResponseObject {
            AppBannerPromptOutcome userChoice;
          };

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

当允许站点呈现站点触发的安装提示时, 或者在用户代理呈现自动安装提示之前, 会分派 {{BeforeInstallPromptEvent}}。它允许站点取消 自动安装提示,并手动呈现 站点触发的安装提示

如果 {{BeforeInstallPromptEvent}} 被取消, 用户代理则被允许向最终用户呈现安装提示 (具体而言,是自动安装提示)。取消默认动作 (通过 {{Event/preventDefault()}})会阻止用户代理 呈现安装提示。用户代理可在稍后时间再次运行 通知安装提示可用的步骤

PromptResponseObject 包含调用 {{BeforeInstallPromptEvent/prompt()}} 的结果。它包含一个成员, userChoice, 该成员表示用户选择的结果。

{{BeforeInstallPromptEvent}} 实例具有以下内部槽:

[[\didPrompt]]
一个布尔值,初始为 `false`。表示此事件是否已用于向最终用户 呈现安装提示
[[\userResponsePromise]]
一个 promise,表示呈现安装 提示的结果。

prompt() 方法

当调用 prompt 方法时,运行以下步骤:

  1. 令 |userResponsePromise| 为 {{BeforeInstallPromptEvent/[[userResponsePromise]]}}。
  2. 如果 |userResponsePromise| 是 pending:
    1. 如果 [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} 是 `true`,终止此算法。
    2. 如果此事件的 {{Event/isTrusted}} 属性是 `false`, 则以 {{"NotAllowedError"}} 拒绝 |userResponsePromise|, 并终止此算法。
    3. 将 [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} 设置为 `true`。
    4. 并行地,使用 [=this=] 请求呈现安装 提示。等待最终用户做出选择,可能会无限期等待。
  3. 返回 |userResponsePromise|。

要使用 {{BeforeInstallPromptEvent}} event 请求呈现安装提示

  1. 呈现安装提示,并令 |outcome| 为结果。
  2. 令 |response| 为新创建的 {{PromptResponseObject}}, 将其 {{PromptResponseObject/userChoice}} 初始化为 |outcome|。
  3. [=Resolve=] |event|.{{BeforeInstallPromptEvent/[[userResponsePromise]]}},值为 |response|。

使用示例

此示例展示了如何阻止自动安装提示显示,直到用户点击按钮来显示 站点触发的安装提示。这样,站点可以将安装留给用户自行决定 (而不是在任意时间提示),同时仍提供醒目的 UI 来执行安装。

              window.addEventListener("beforeinstallprompt", event => {
                // 抑制自动提示。
                event.preventDefault();

                // 显示(默认禁用的)安装按钮。此按钮在点击时
                // 解析 installButtonClicked promise。
                installButton.disabled = false;

                // 等待用户点击按钮。
                installButton.addEventListener("click", async e => {
                  // prompt() 方法只能使用一次。
                  installButton.disabled = true;

                  // 显示提示。
                  const userChoice = await event.prompt();
                  console.info(`user choice was: ${userChoice}`);
                });
              });
            

AppBannerPromptOutcome 枚举

AppBannerPromptOutcome 枚举的值表示 呈现安装提示后的结果。

"accepted":
最终用户表示希望用户代理安装该 Web 应用程序。
"dismissed":
最终用户关闭了安装提示。

对 `Window` 对象的扩展

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

onappinstalled 属性

onappinstalled 事件处理器 IDL 属性 处理 "appinstalled" 事件。

onbeforeinstallprompt 属性

onbeforeinstallprompt 事件处理器 IDL 属性处理 "beforeinstallprompt" 事件。