Sub Apps

Unofficial Proposal Draft,

This version:
https://explainers-by-googlers.github.io/sub-apps
Issue Tracking:
GitHub
Editor:
(Google)
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.


Abstract

The Sub Apps API allows a parent application context to programmatically install, list, and remove auxiliary applications sharing the parent’s origin, storage, and lifecycle bounds, while presenting distinct names, icons, and window identities to the operating system.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

The Sub Apps API allows a parent application to programmatically install, list, and remove auxiliary applications (Sub Apps) that:

  1. Appear to the operating system and user as fully distinct applications (separate launcher icons, distinct taskbar/shelf windows, and individual OS integrations).

  2. Share the underlying resources, origin identification, storage, permissions, and update lifecycle of the parent application.

This API is restricted to isolated contexts to ensure security and data integrity.

2. Extensions to the Window interface

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

2.1. subApps attribute

When getting, the subApps attribute always returns the same instance of the SubApps object.

3. SubApps interface

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

dictionary SubAppsRemoveResponse {
  sequence<USVString> 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<USVString> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};

Each application has a primary application context (the parent app). A Document is a sub-app document if it is not loaded within the primary application context of the parent app.

A string path is a valid relative path for a Document document if all of the following conditions are met:

  1. path is not a valid absolute URL.

  2. path starts with "/".

  3. path is not empty.

  4. path does not start with "//".

  5. The result of parsing path with document’s origin as the base URL is not failure.

3.1. add() method

The add(install_paths) method steps are:

  1. Let promise be a new promise.

  2. If the relevant global object’s associated Document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  3. If the relevant global object’s associated Document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  4. Let parsedUrls be an empty list.

  5. For each installPath in install_paths:

    1. If installPath is not a valid relative path for the relevant global object’s associated Document, reject promise with a "TypeError" DOMException and return promise.

    2. Let absoluteUrl be the result of parsing installPath with the parent app’s origin as the base URL.

    3. If absoluteUrl is failure, reject promise with a "TypeError" DOMException and return promise.

    4. Append absoluteUrl to parsedUrls.

  6. Let parentApp be the parent app.

  7. Let currentSubAppsCount be the number of sub-apps currently installed for parentApp.

  8. If currentSubAppsCount + install_paths’s size is greater than 20, reject promise with a "QuotaExceededError" DOMException and return promise.

  9. Let userConsent be the result of requesting user consent for installing the sub-apps in install_paths (e.g. by presenting a unified installation dialog).

  10. If userConsent is denied, reject promise with a "NotAllowedError" DOMException and return promise.

  11. Run the following steps in parallel:

    1. Let installedApps be an empty map.

    2. Let failedApps be an empty map.

    3. For each absoluteUrl in parsedUrls:

      1. Let installPath be the relative path portion of absoluteUrl.

      2. Fetch the resource at absoluteUrl and parse its web manifest [APPMANIFEST].

      3. If fetching or parsing the web manifest fails, perform the following steps:

        1. Add a mapping from installPath to a new "DataError" DOMException to failedApps.

        2. Continue.

      4. Let manifest be the parsed web manifest.

      5. Let manifestId be manifest’s id. If it is not defined, fall back to manifest’s start_url (without reference/hash fragment).

      6. If parentApp already has a sub-app with manifestId installed, perform the following steps:

        1. Add a mapping from installPath to a new "InvalidStateError" DOMException to failedApps.

        2. Continue.

      7. If the scope of manifest overlaps with the scope of parentApp, or the scope of manifest overlaps with any currently installed sub-app’s scope under parentApp, or the absoluteUrl points to parentApp’s manifest itself, perform the following steps:

        1. Add a mapping from installPath to a new "ConstraintError" DOMException to failedApps.

        2. Continue.

      8. Try to install the sub-app on the platform’s application launcher.

      9. If the installation fails due to a system or database error:

        1. Add a mapping from installPath to a new "OperationError" DOMException to failedApps.

        2. Continue.

      10. Add a mapping from installPath to manifestId to installedApps.

    4. Let response be a new SubAppsAddResponse dictionary with:

      • installedApps set to installedApps.

      • failedApps set to failedApps.

    5. Queue a global task on the relevant global object of this to resolve promise with response.

  12. Return promise.

3.2. remove() method

The remove(manifest_ids) method steps are:

  1. Let promise be a new promise.

  2. If the relevant global object’s associated Document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  3. If the relevant global object’s associated Document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  4. Let parsedManifestIds be an empty list.

  5. For each manifestId in manifest_ids:

    1. If manifestId is not a valid relative path for the relevant global object’s associated Document, reject promise with a "TypeError" DOMException and return promise.

    2. Let parsedUrl be the result of parsing manifestId with the parent app’s origin as the base URL.

    3. If parsedUrl is failure, reject promise with a "TypeError" DOMException and return promise.

    4. Append parsedUrl to parsedManifestIds.

  6. Run the following steps in parallel:

    1. Let removedApps be an empty sequence.

    2. Let failedApps be an empty map.

    3. For each parsedUrl in parsedManifestIds:

      1. Let manifestId be the relative path portion of parsedUrl.

      2. If manifestId does not exist under the parent app, perform the following steps:

      3. Attempt to uninstall the sub-app with ID manifestId from the system launcher and registry.

      4. If uninstallation fails due to a system error, perform the following steps:

      5. Append manifestId to removedApps.

    4. Let response be a new SubAppsRemoveResponse dictionary with:

      • removedApps set to removedApps.

      • failedApps set to failedApps.

    5. Queue a global task on the relevant global object of this to resolve promise with response.

  7. Return promise.

3.3. list() method

The list() method steps are:

  1. Let promise be a new promise.

  2. If the relevant global object’s associated Document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  3. If the relevant global object’s associated Document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  4. Run the following steps in parallel:

    1. Let listResult be an empty map.

    2. Retrieve the list of all currently installed sub-apps for the parent app from the platform registry.

    3. If retrieving the list fails due to a platform error, Queue a global task on the relevant global object of this to reject promise with an "OperationError" DOMException, and abort these steps.

    4. For each installed sub-app subApp:

      1. Let manifestId be subApp’s manifest ID.

      2. Let appName be the name of the sub-app as extracted from its web manifest.

      3. Let resultEntry be a new SubAppsListResult dictionary with appName set to appName.

      4. Add a mapping from manifestId to resultEntry to listResult.

    5. Queue a global task on the relevant global object of this to resolve promise with listResult.

  5. Return promise.

4. Security and Privacy Considerations

Installing and managing auxiliary applications is a powerful feature. A user agent MUST NOT allow a web application to install or manage sub-apps without express permission.

This section outlines the threats considered and the normative requirements for user agents to mitigate them.

4.1. Shared Origin Identity

A sub-app does not possess a separate security origin. It shares the exact same origin and local data stores (such as Cookies, IndexedDB, LocalStorage, and Cache Storage) with its parent app. Standard web security boundaries (such as the Same-Origin Policy) treat the parent and all its sub-apps as a single entity.

4.2. Permission Inheritance

All permissions are shared across the parent app and its sub-apps. Granting a permission (e.g., camera, file system access, USB) to a sub-app automatically grants it to the parent, and vice versa.

To access the Sub Apps API, the parent app’s document must explicitly declare the permission policy sub-apps. Permission policies declared for a sub-app have no effect.

User consent MUST be obtained for a specific origin. When add() is called, the user agent MUST present a unified installation dialog to the user displaying all requested sub-apps. If multiple sub-apps are added at once, they should be presented inside a single prompt to avoid dialog spam.

The user agent MUST display a permission prompt that clearly indicates which origin is requesting access and provides the user with enough information to make an informed decision (for example, by displaying the names and icons of the sub-apps being installed).

4.4. Identity Spoofing Risks

Since developers can customize the names and icons of sub-apps, there is a risk that a malicious application could create a sub-app that mimics system dialogs or trusted third-party applications. To mitigate this risk, the Sub Apps API is restricted to isolated contexts, which guarantee integrity and signature verification.

4.5. OS Integration Extension Risk

Sub-apps have the ability to register their own OS integrations (such as protocol handlers or file type associations). This means an application could potentially extend its reach into the OS far beyond what was declared in the parent app’s primary manifest. This is mitigated by the fact that most operating system integrations require explicit user approval (for example, choosing the sub-app as the default application for a file type) before they become active.

4.6. Quota and Limits

To protect the host operating system and the user’s application launcher from potential exhaustion or abuse, the platform enforces a hard limit of 20 installed sub-apps per parent application. If a batch installation call exceeds the platform limit, the entire add() call rejects with a "QuotaExceededError" DOMException.

5. Integrations

5.1. Permissions Policy

This specification defines a feature that controls whether the methods exposed by the subApps attribute on the Window object may be used.

The feature name for this feature is "sub-apps".

The default allowlist for this feature is 'self'.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[APPMANIFEST]
Marcos Caceres; Daniel Murphy; Christian Liebel. Web Application Manifest. URL: https://w3c.github.io/manifest/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
Isolated Contexts. Draft Community Group Report. URL: https://wicg.github.io/isolated-web-apps/isolated-contexts.html
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

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

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

dictionary SubAppsRemoveResponse {
  sequence<USVString> 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<USVString> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};