子应用

非官方提案草案

本版本:
https://WICG.github.io/sub-apps
问题跟踪:
GitHub
规范内问题
编辑:
Google

摘要

子应用 API 允许父应用上下文以编程方式安装、列出和移除与父应用共享源、存储和生命周期边界的辅助 应用,同时向操作系统呈现不同的名称、图标和窗口标识。

本文档状态

本规范由 Web 平台孵化 社区组发布。 它既不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C 社区贡献者许可协议 (CLA), 退出权受到限制,并且还适用其他条件。 进一步了解 W3C 社区组和商务组

1. 简介

子应用 API 允许父应用以编程方式安装、列出和移除满足以下条件的辅助 应用(子应用):

  1. 对于操作系统和用户而言,它们表现为完全独立的应用(具有单独的启动器图标、 不同的任务栏/搁板窗口以及各自的操作系统集成)。

  2. 与父应用共享底层资源、源、存储、权限和更新生命周期。

此 API 仅限于隔离上下文,以确保安全性和数据完整性。

2. 概念

一个已安装 Web 应用具有关联的父应用,其值为 null 或一个已安装 Web 应用

一个已安装 Web 应用具有关联的子应用集合,它是由集合形式保存的已安装 Web 应用

一个 Document 具有关联的已安装 Web 应用,它是该 Document 作为其组成部分呈现的已安装 Web 应用。此关联的确切机制由实现定义

如果一个 Document 具有关联的已安装 Web 应用,并且 其关联的已安装 Web 应用父应用不为 null,则该 Document 是一个子应用文档

3. Window 接口的扩展

[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
  [SameObject] readonly attribute SubApps subApps;
};

3.1. subApps 属性

每个 Window 对象都有一个关联的 subApps,它是与该 Window 一同创建的 SubApps 实例。

subApps 获取器步骤如下:
  1. 返回此对象subApps

4. SubApps 接口

// 表示 https://w3c.github.io/manifest/#id-member
typedef USVString ManifestId;

dictionary SubAppsAddResponse {
  record<USVString, ManifestId> installedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsRemoveResponse {
  sequence<ManifestId> removedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsListResult {
  required DOMString appName;
};

[
  Exposed=Window,
  SecureContext,
  IsolatedContext
] interface SubApps {
  Promise<SubAppsAddResponse> add(sequence<USVString> install_paths);
  Promise<SubAppsRemoveResponse> remove(sequence<ManifestId> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};

如果满足以下所有条件,则字符串 pathDocument document有效相对路径

  1. path 不是有效的绝对 URL。

  2. path"/" 开头。

  3. path 不为空。

  4. path 不以 "//" 开头。

  5. document文档基准 URL作为基准 URL解析 path 的结果不是失败。

4.1. add() 方法

add(install_paths) 方法的步骤如下:

install_paths 参数是一个相对路径列表,这些路径指向子应用的起始 HTML 页面。

  1. promise一个新的 promise

  2. document相关全局对象关联 Document

  3. 如果不允许 document 使用名为“策略控制特性sub-apps”,则以一个“SecurityErrorDOMException 拒绝 promise 并返回 promise

  4. 如果 document 是一个子应用文档,则以一个“NotSupportedErrorDOMException 拒绝 promise 并返回 promise

  5. parsedUrls 为空列表。

  6. 对于 install_paths 中的每个 installPath

    1. 如果 installPath 不是 document有效相对路径,则以一个“TypeErrorDOMException 拒绝 promise 并返回 promise

    2. absoluteUrl 为以 document文档基准 URL 作为基准 URL,解析 installPath 的结果。

    3. 如果 absoluteUrl 为失败,则以一个“TypeErrorDOMException 拒绝 promise 并返回 promise

    4. absoluteUrl 追加到 parsedUrls

  7. parentAppdocument关联的已安装 Web 应用

  8. currentSubAppsCountparentApp子应用集合大小

  9. 如果 install_paths大小大于 20,则以一个“QuotaExceededErrorDOMException 拒绝 promise 并返回 promise

  10. 如果 currentSubAppsCount + install_paths大小大于 50,则以一个“QuotaExceededErrorDOMException 拒绝 promise 并返回 promise

  11. subApps此对象

  12. 并行运行以下步骤:

    1. userConsent 为请求用户同意安装 install_paths 中子应用的结果(例如,通过呈现统一的安装对话框)。

    2. 如果 userConsent 被拒绝,则在 subApps相关全局对象排入一个全局任务,以一个“NotAllowedErrorDOMException 拒绝 promise, 并中止这些步骤。

    3. installedApps 为空映射。

    4. failedApps 为空映射。

    5. 对于 parsedUrls 中的每个 absoluteUrl

      1. installPathabsoluteUrl路径

      2. manifest 为给定 absoluteUrl获取并处理清单的结果。

      3. 如果 manifest 为失败,则执行以下步骤:

        1. failedApps[installPath] 设置为一个新的“DataErrorDOMException

        2. 继续

      4. manifestIdmanifestid。如果未 定义,则回退到 manifeststart_url(不含引用/散列片段)。

      5. 如果 parentApp 已安装具有 manifestId 的子应用, 则执行以下步骤:

        1. failedApps[installPath] 设置为一个新的“InvalidStateErrorDOMException

        2. 继续

      6. 如果 parentApp清单作用域manifest作用域前缀,或者 manifest作用域parentApp子应用集合中任何当前已安装子应用的作用域前缀,或者 parentApp子应用集合中任何当前已安装子应用的作用域manifest作用域前缀,或者 absoluteUrl 指向 parentApp清单本身,则执行以下步骤:

        1. failedApps[installPath] 设置为一个新的“ConstraintErrorDOMException

        2. 继续

      7. 尝试在平台的应用启动器中安装该子应用。

      8. 如果安装因系统或数据库错误而失败:

        1. failedApps[installPath] 设置为一个新的“OperationErrorDOMException

        2. 继续

      9. installedApps[installPath] 设置manifestId

    6. response 为一个新的 SubAppsAddResponse 字典,其中:

    7. subApps相关全局对象排入一个全局任务,以使用 response 兑现 promise

  13. 返回 promise

4.2. remove() 方法

manifest_ids 参数是要移除的子应用的 id 列表。

remove(manifest_ids) 方法的步骤如下:

  1. promise一个新的 promise

  2. document相关全局对象关联 Document

  3. 如果不允许 document 使用名为“策略控制特性sub-apps”,则以一个“SecurityErrorDOMException 拒绝 promise 并返回 promise

  4. 如果 document 是一个子应用文档,则以一个“NotSupportedErrorDOMException 拒绝 promise 并返回 promise

  5. parentAppdocument关联的已安装 Web 应用

  6. parsedManifestIds 为空列表。

  7. 对于 manifest_ids 中的每个 manifestId

    1. 如果 manifestId 不是 document有效相对路径,则以一个“TypeErrorDOMException 拒绝 promise 并返回 promise

    2. parsedUrl 为以 document文档基准 URL 作为基准 URL,解析 manifestId 的结果。

    3. 如果 parsedUrl 为失败,则以一个“TypeErrorDOMException 拒绝 promise 并返回 promise

    4. parsedUrl 追加到 parsedManifestIds

  8. subApps此对象

  9. 并行运行以下步骤:

    1. removedApps 为空序列。

    2. failedApps 为空映射。

    3. 对于 parsedManifestIds 中的每个 parsedUrl

      1. manifestIdparsedUrl路径

      2. 如果 parentApp子应用集合中不存在 已安装 Web 应用,且其 idmanifestId,则执行以下 步骤:

        1. failedApps[manifestId] 设置为一个新的“NotFoundErrorDOMException

        2. 继续

      3. 尝试从系统启动器和注册表中卸载 ID 为 manifestId 的子应用。

      4. 如果卸载因系统错误而失败,则执行以下步骤:

        1. failedApps[manifestId] 设置为一个新的“OperationErrorDOMException

        2. 继续

      5. manifestId 追加到 removedApps

    4. response 为一个新的 SubAppsRemoveResponse 字典,其中:

    5. subApps相关全局对象排入一个全局任务,以使用 response 兑现 promise

  10. 返回 promise

4.3. list() 方法

list() 方法的步骤如下:

  1. promise一个新的 promise

  2. document相关全局对象关联 Document

  3. 如果不允许 document 使用名为“策略控制特性sub-apps”,则以一个“SecurityErrorDOMException 拒绝 promise 并返回 promise

  4. 如果 document 是一个子应用文档,则以一个“NotSupportedErrorDOMException 拒绝 promise 并返回 promise

  5. parentAppdocument关联的已安装 Web 应用

  6. subApps此对象

  7. 并行运行以下步骤:

    1. listResult 为空映射。

    2. 从平台注册表中检索 parentApp 的所有当前已安装子应用的列表。

    3. 如果因平台错误而无法检索该列表,则在 subApps相关全局对象排入一个全局任务,以一个“OperationErrorDOMException 拒绝 promise, 并中止这些步骤。

    4. 对于每个已安装的子应用 subApp

      1. manifestIdsubAppid

      2. appName 为从该子应用的 Web 清单中提取的子应用名称

      3. resultEntry 为一个新的 SubAppsListResult 字典,并将 appName 设置为 appName

      4. listResult[manifestId] 设置resultEntry

    5. subApps相关全局对象排入一个全局任务,以使用 listResult 兑现 promise

  8. 返回 promise

4.4. 获取并处理清单

编写“获取并 处理清单”算法。[议题 #2]

给定一个 url(一个 URL),要获取并 处理清单,运行以下步骤:

  1. 返回失败。

5. 安全和隐私注意事项

安装和管理辅助应用是一项强大特性。如果没有明确许可,用户代理不得允许 Web 应用 安装或管理子应用。

本节概述所考虑的威胁,以及用户代理为缓解这些威胁而必须遵循的规范性要求。

5.1. 共享源身份

子应用不具有独立的安全源。它与其父应用共享完全相同的和本地数据存储(例如 Cookie、IndexedDB、LocalStorage 和 Cache Storage)。标准 Web 安全边界(例如同源策略)将父应用及其所有子应用视为单一实体。

5.2. 权限继承

所有权限均由父应用及其子应用共享。向子应用授予某项权限(例如相机、文件 系统访问、USB)会自动将该权限授予父应用,反之亦然。

要访问子应用 API,父应用的文档必须显式声明权限策略 sub-apps。为子应用声明的权限策略不起作用。

必须针对特定取得用户同意。调用 add() 时,用户代理必须向用户呈现统一的安装对话框,其中显示所有请求安装的 子应用。如果一次添加多个子应用,则应在单个提示中呈现它们,以避免 对话框泛滥。

用户代理必须显示一个权限提示,清楚指出哪个源正在请求访问,并 向用户提供足够的信息以作出知情决定(例如,显示正在安装的子应用的名称 和图标)。

5.4. 身份欺骗风险

由于开发者可以自定义子应用的名称和图标,因此存在恶意应用 创建模仿系统对话框或受信任第三方应用的子应用的风险。 为缓解此风险,子应用 API 仅限于隔离上下文,这种上下文可保证完整性和签名验证。

5.5. 操作系统集成扩展风险

子应用能够注册自己的操作系统集成(例如协议处理程序或文件类型 关联)。这意味着应用可能将其在操作系统中的影响范围扩展到远远超出 父应用主清单中声明的范围。 此风险因大多数操作系统集成都需要用户明确批准(例如, 选择子应用作为某种文件类型的默认应用)才能生效而得到缓解。

5.6. 配额和限制

为了保护宿主操作系统和用户的应用启动器免受潜在的资源耗尽或滥用,平台 强制实施以下两个限制:
  1. 每个父应用最多可安装 50 个子应用

  2. 每次权限提示最多可安装 20 个子应用

如果批量安装调用超出平台限制,则整个 add() 调用都会以一个“QuotaExceededErrorDOMException 拒绝。

6. 集成

6.1. 权限策略

本规范定义了一项特性,用于控制是否可以使用 Window 对象上的 subApps 属性所公开的方法。

此特性的特性名称是“sub-apps”。

此特性的默认允许列表'none'。用户代理可以针对特定源将其覆盖为 'self'(例如,根据用户的决定)。

一致性

文档 约定

一致性要求通过描述性断言 与 RFC 2119 术语的组合来表达。 规范性部分中的关键词“MUST”“MUST NOT”“REQUIRED”“SHALL”“SHALL NOT”“SHOULD”“SHOULD NOT”“RECOMMENDED” “MAY”和“OPTIONAL” 应按照 RFC 2119 中的说明进行解释。 但是,为便于阅读, 本规范中的这些词并不全部以大写字母显示。

本规范的所有文本均为规范性内容, 明确标记为非规范性的章节、示例和注释除外。[RFC2119]

本规范中的示例以“例如”一词引入, 或使用 class="example" 与规范性文本区分开来, 如下所示:

这是一个资料性示例的例子。

资料性注释以“注”一词开头, 并使用 class="note" 与规范性文本区分开来, 如下所示:

注,这是一个资料性注释。

测试

与本规范内容相关的测试 可以记录在此类“测试”块中。 任何此类块均为非规范性内容。


索引

本规范定义的 术语

通过引用定义的 术语

参考文献

规范性参考文献

[APPMANIFEST]
Marcos Caceres; Daniel Murphy; Christian Liebel. Web 应用清单。URL:https://w3c.github.io/manifest/
[DOM]
Anne van Kesteren. DOM 标准。现行标准。 URL:https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML 标准。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。现行标准。URL:https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
隔离 上下文。社区组报告草案。URL:https://wicg.github.io/isolated-web-apps/isolated-contexts.html
[PERMISSIONS]
Marcos Caceres; Mike Taylor. 权限。URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. 权限 策略。URL:https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. 用于在 RFC 中 表示要求级别的关键词。1997 年 3 月。当前最佳实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren. URL 标准。现行标准。 URL:https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。现行 标准。URL:https://webidl.spec.whatwg.org/

IDL 索引

[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
  [SameObject] readonly attribute SubApps subApps;
};

// 表示 https://w3c.github.io/manifest/#id-member
typedef USVString ManifestId;

dictionary SubAppsAddResponse {
  record<USVString, ManifestId> installedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsRemoveResponse {
  sequence<ManifestId> removedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsListResult {
  required DOMString appName;
};

[
  Exposed=Window,
  SecureContext,
  IsolatedContext
] interface SubApps {
  Promise<SubAppsAddResponse> add(sequence<USVString> install_paths);
  Promise<SubAppsRemoveResponse> remove(sequence<ManifestId> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};

问题索引

编写“获取并处理清单”算法。[议题 #2]