Sub Apps

Unofficial Proposal Draft,

This version:
https://WICG.github.io/sub-apps
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)

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, storage, permissions, and update lifecycle of the parent application.

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

2. Concepts

An installed web application has an associated parent app, which is either null or an installed web application.

An installed web application has an associated sub-apps set, which is a set of installed web applications.

A Document has an associated installed web application, which is the installed web application that the Document is presented as part of. The exact mechanism for this association is implementation-defined.

A Document is a sub-app document if it has an associated installed web application and its associated installed web application’s parent app is not null.

3. Extensions to the Window interface

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

3.1. subApps attribute

Each Window object has an associated subApps, which is a SubApps instance created alongside the Window.

The subApps getter steps are:
  1. Return this’s subApps.

4. SubApps interface

// Represents the 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();
};

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 document base URL as the base URL is not failure.

4.1. add() method

The add(install_paths) method steps are:

The install_paths argument is a list of relative paths pointing to the sub-apps' start HTML pages.

  1. Let promise be a new promise.

  2. Let document be the relevant global object’s associated Document.

  3. If document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  4. If document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  5. Let parsedUrls be an empty list.

  6. For each installPath in install_paths:

    1. If installPath is not a valid relative path for document, reject promise with a "TypeError" DOMException and return promise.

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

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

    4. Append absoluteUrl to parsedUrls.

  7. Let parentApp be document’s associated installed web application.

  8. Let currentSubAppsCount be the size of parentApp’s sub-apps set.

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

  10. Let subApps be this.

  11. Run the following steps in parallel:

    1. 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).

    2. If userConsent is denied, queue a global task on the relevant global object of subApps to reject promise with a "NotAllowedError" DOMException, and abort these steps.

    3. Let installedApps be an empty map.

    4. Let failedApps be an empty map.

    5. For each absoluteUrl in parsedUrls:

      1. Let installPath be absoluteUrl’s path.

      2. Let manifest be the result of fetch and process the manifest given absoluteUrl.

      3. If manifest is failure, perform the following steps:

        1. Set failedApps[installPath] to a new "DataError" DOMException.

        2. Continue.

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

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

        1. Set failedApps[installPath] to a new "InvalidStateError" DOMException.

        2. Continue.

      6. If the scope of parentApp’s manifest is a prefix of the manifest’s scope, or the manifest’s scope is a prefix of the scope of any currently installed sub-app in parentApp’s sub-apps set, or the scope of any currently installed sub-app in parentApp’s sub-apps set is a prefix of the manifest’s scope, or the absoluteUrl points to parentApp’s manifest itself, perform the following steps:

        1. Set failedApps[installPath] to a new "ConstraintError" DOMException.

        2. Continue.

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

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

        1. Set failedApps[installPath] to a new "OperationError" DOMException.

        2. Continue.

      9. Set installedApps[installPath] to manifestId.

    6. Let response be a new SubAppsAddResponse dictionary with:

    7. Queue a global task on the relevant global object of subApps to resolve promise with response.

  12. Return promise.

4.2. remove() method

The manifest_ids argument is a list of ids of the sub-apps to remove.

The remove(manifest_ids) method steps are:

  1. Let promise be a new promise.

  2. Let document be the relevant global object’s associated Document.

  3. If document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  4. If document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  5. Let parentApp be document’s associated installed web application.

  6. Let parsedManifestIds be an empty list.

  7. For each manifestId in manifest_ids:

    1. If manifestId is not a valid relative path for document, reject promise with a "TypeError" DOMException and return promise.

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

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

    4. Append parsedUrl to parsedManifestIds.

  8. Let subApps be this.

  9. 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 parsedUrl’s path.

      2. If there is no installed web application in parentApp’s sub-apps set whose id is manifestId, perform the following steps:

        1. Set failedApps[manifestId] to a new "NotFoundError" DOMException.

        2. Continue.

      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:

        1. Set failedApps[manifestId] to a new "OperationError" DOMException.

        2. Continue.

      5. Append manifestId to removedApps.

    4. Let response be a new SubAppsRemoveResponse dictionary with:

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

  10. Return promise.

4.3. list() method

The list() method steps are:

  1. Let promise be a new promise.

  2. Let document be the relevant global object’s associated Document.

  3. If document is not allowed to use the policy-controlled feature named "sub-apps", reject promise with a "SecurityError" DOMException and return promise.

  4. If document is a sub-app document, reject promise with a "NotSupportedError" DOMException and return promise.

  5. Let parentApp be document’s associated installed web application.

  6. Let subApps be this.

  7. Run the following steps in parallel:

    1. Let listResult be an empty map.

    2. Retrieve the list of all currently installed sub-apps for parentApp 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 subApps to reject promise with an "OperationError" DOMException, and abort these steps.

    4. For each installed sub-app subApp:

      1. Let manifestId be subApp’s 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. Set listResult[manifestId] to resultEntry.

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

  8. Return promise.

4.4. Fetch and process the manifest

write the "fetch and process the manifest" algorithm. [Issue #2]

To fetch and process the manifest given a url (a URL), run the following steps:

  1. Return failure.

5. 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.

5.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.

5.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).

5.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.

5.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.

5.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.

6. Integrations

6.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 'none'. User agents MAY override this to 'self' for particular origins (for example based on user decision).

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
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[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;
};

// Represents the 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();
};

Issues Index

write the "fetch and process the manifest" algorithm. [Issue #2]