Feature specifications for Web Application Manifest extensions & incubations which Chromium has shipped but do not have committments / implementations from other user agents. Instead of keeping these features as explainers, they are documented more officially here.

This is an unofficial proposal.

display_override member

For advanced usages, the [=manifest/display_override=] member can be used to specify a custom fallback order of display mode list values for developers to choose their preferred display mode for the web application. Its value is a [=display mode=].

The [=manifest/display_override=] member of the [=application manifest=] is a sequence of display mode list values including extensions like [=display mode/window-controls-overlay=] and [=display mode/borderless=]. This member represents the developer's preferred fallback chain for [=display mode=]s. This field overrides the [=manifest/display=] member. If the user agent does not support any of the [=display mode=]s specified here, then it falls back to considering the [=manifest/display=] member. See processing the display members for the algorithm steps.

The following steps are added to the [=application manifest/processing extension-point=] in determining the web app's chosen display mode:

  1. [=list/For each=] |candidate_display_mode:DisplayModeType| of |manifest:ordered map|.[=manifest/display_override=] member:
    1. If display modes list contains |candidate_display_mode:DisplayModeType|, return that |candidate_display_mode:DisplayModeType|
    2. If |candidate_display_mode:DisplayModeType| is [=display mode/window-controls-overlay=] and the user agent supports this, then return that |candidate_display_mode:DisplayModeType|.
    3. If |candidate_display_mode:DisplayModeType| is [=display mode/tabbed=] and the user agent supports this, then return that |candidate_display_mode:DisplayModeType|.

This member is intended to be only used for advanced cases, where the developer wants explicit control over the fallback order of their display modes, or for modes that are not available in the basic display modes list. Otherwise, the [=manifest/display=] member is sufficient for most use cases.

Concepts

Display mode extensions

Additionally to the normal display modes, [=manifest/display_override=] also supports certain extensions to it.

borderless
The web application does not have any host-native title bar or [=window controls=] visible and with the web contents extended to the whole title bar area. The app can specify [=draggable region=]s in the web contents to create a customized title bar. The user agent may change the title bar state depending on various security considerations, like an out-of-scope navigation.
[=display mode/window-controls-overlay=]
tabbed
The web application can have multiple [=application contexts=] combined in a single operating-system-level window. For example, this could mean the user agent displays a tab strip UI to allow the user to switch between the application contexts.

[=manifest/display_override=] usage example

The following shows a [=manifest=] that prefers the minimal-ui display mode over standalone, but if minimal-ui isn't supported, falls back to standalone as opposed to 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"
            }
          

Defining draggable regions

[=app-region=] CSS property has not been implemented in any user agent, so it is at risk. Note that some user agents use the non-standard CSS property `-webkit-app-region` for the same purpose.

The `app-region` property can be used to define with CSS which regions or elements in for example a title bar are draggable.

Extensions to processing the manifest

To facilitate all of the new extension and incubation features added by this specification, the user agent SHOULD run the following during the extension point in [=processing a manifest=] (having access to [=URL=] |document URL:URL|, [=URL=] |manifest URL:URL|, [=ordered map=] |json:ordered map|, and [=ordered map=] |manifest:ordered map|):

  1. [=Process the `tab_strip` member=], passing |json|, |manifest| and |manifest URL|.
  2. [=Process the `note_taking` member=], passing |json|, |manifest| and |manifest URL|.
  3. [=Process the `protocol_handlers` member=], passing |json| and |manifest|.
  4. [=Process the `file_handlers` member=], passing |json|, |manifest| and |manifest URL|.

tab_strip member

The `tab_strip` member of the Web Application Manifest is an object that contains information about how the application is intended to behave in the [=display mode/tabbed=] display mode. It has the following members:

home_tab member

The `home_tab` member of the [=tab_strip=] object is an ordered map that contains information about a special "home tab" that is intended to serve as the top-level menu for the application. It contains the following members:

The scope_patterns member is a list of {{URLPatternInput}}s that define the [=within home tab scope|scope of the home tab=] relative to the [=manifest URL=].

An application has a home tab if the applied [=display mode=] of the application is [=display mode/tabbed=], and the [=Document/processed manifest=] includes a non-null [=home_tab=] member of the [=tab_strip=] member.

The home tab context is an optional [=application context=] that has special properties compared to other application contexts. If the application [=has a home tab=], every application window SHOULD feature a [=home tab context=]. If not, then the application windows SHOULD NOT have a [=home tab context=].

How the [=home tab context=] is presented is at the discretion of the user agent, but it SHOULD have a different appearance to normal application contexts.

A [=URL=] |url:URL| is said to be within home tab scope if and only if:

A URL is is outside of home tab scope if it is not [=within home tab scope=].

Every window has a home tab

If the application [=has a home tab=], whenever a new application window is created (for example when launching the application, or when moving a tab to a new window), the user agent MUST create a new [=home tab context=] in that window. A newly created [=home tab context=] SHOULD be navigated to the [=start URL=], which by definition is [=within home tab scope=].

Navigations concerning the home tab scope

When [=navigate|navigating=] the [=top-level traversable=] associated with a [=home tab context=] to a [=URL=] |url:URL| that is [=outside of home tab scope=], the following steps are run:

  1. Let [=top-level traversable=] |tab:toplevel traversable| be the result of choosing a navigable with a target of "_blank" and noopener true.
  2. Instead of [=navigating=] the home-tab traversable, [=navigate=] |tab| with the same parameters.
  3. [=applied|Apply=] the current [=application manifest=] to |tab|'s [=top-level browsing context=].
  4. The user agent SHOULD place |tab| in the same window as the home-tab navigable.
  5. Focus |tab|.

When [=navigate|navigating=] a [=top-level traversable=] with a [=display mode=] of [=display mode/tabbed=] that is not associated with a [=home tab context=] (i.e. a non-home tab) to a [=URL=] |url:URL| that is [=within home tab scope=], the following steps are run:

  1. Let |hometab:toplevel traversable| be the [=top-level traversable=] of the [=home tab context=] associated with the current window.
  2. Instead of [=navigating=] the top-level traversable, [=navigate=] |hometab| with the same parameters.
  3. Focus |hometab|.

Home tab invariants

The above rules are intended to ensure that the following invariants are always true, for applications that [=has a home tab|have a home tab=]:

  • every application window has exactly one [=home tab context=], and
  • every [=home tab context=]'s active document's [=Document/URL=] is [=within home tab scope=] (unless the document's URL has changed since it was created), and
  • every non-home-tab [=application context=]'s active document's [=Document/URL=] is [=outside of home tab scope=] (unless the document's URL has changed since it was created).

User agents will not dynamically move documents between home-tab and non-home-tab contexts if they change their [=URL/fragment=], or use the {{History}} API to modify their display URL into or out of the home tab scope, because no navigation is taking place. For this reason, the above invariants only care about the [=Document/URLs=] that documents had at the time of their creation.

For single-page applications that "pretend" to navigate by modifying their URLs, this may result in undesirable behaviour that breaks the above invariants (e.g. if the user clicks a link from the home tab to dynamically change the URL to a non-home page, they will stay inside the home tab because it is not actually navigating). To avoid this situation, the application can detect when it is in tabbed application mode and change its behavior to perform actual navigations into and out of the home tab scope, rather than modifying the URL.

new_tab_button member

The [=tab_strip/new_tab_button=] member is an ordered map that describes the behaviour of a UI affordance (such as a button) which, when clicked/activated, opens a new [=application context=] within the application window. It has the following members:

The url member is a string that represents a URL relative to the [=manifest URL=] that is [=manifest/within scope=] of a [=Document/processed manifest=].

An application has a new tab button if the [=Document/processed manifest=]'s [=new_tab_button=]'s [=new_tab_button/url=] member is [=outside of home tab scope=]. If the application does not [=has a new tab button|have a new tab button=], the user agent SHOULD NOT make the "new tab" affordance available to the end user.

When the new tab button is activated by the end user, the following steps are run:

  1. [=Create a new application context=] in the current window and focus it.
  2. Navigate it to the value of the [=new_tab_button/url=] member of [=new_tab_button=].

Processing the `tab_strip` member

To process the `tab_strip` member, given [=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, and [=URL=] |manifest URL:URL|, run the following during the extension point in [=processing a manifest=]:

  1. Set |manifest|["tab_strip"] to a new [=ordered map=].
  2. If |json|["tab_strip"] does not exist or |json|["tab_strip"] is not an [=ordered map=]:
    1. Set |manifest|["tab_strip"]["new_tab_button"]["url"] to |manifest|["start_url"].
    2. Return.
  3. [=Process the `home_tab` member=] passing |json|["tab_strip"], |manifest|["tab_strip"], and |manifest URL|.
  4. [=Process the `new_tab_button` member=] passing |json|["tab_strip"], |manifest|["tab_strip"], |manifest URL|, and |manifest|["start_url"].

Processing the `home_tab` member

To process the `home_tab` member, given [=ordered map=] |json tab strip:ordered map|, [=ordered map=] |manifest tab strip:ordered map|, and [=URL=] |manifest URL:URL|, run the following:

  1. If |json tab strip|["home_tab"] does not exist or |json tab strip|["home_tab"] not an [=ordered map=], return.
  2. Let |home tab:ordered map| be a new [=ordered map=].
  3. [=Process the `scope_patterns` member=] passing |json tab strip|["home_tab"]["scope_patterns"], |home tab| and |manifest URL|.
  4. Set |manifest tab strip|["home_tab"] to |home tab|.

Processing the `new_tab_button` member

To process the `new_tab_button` member, given [=ordered map=] |json tab strip:ordered map|, [=ordered map=] |manifest tab strip:ordered map|, [=URL=] |manifest URL:URL|, and [=URL=] |start URL:URL|, run the following:

  1. Set |manifest tab strip|["new_tab_button"]["url"] to |start URL|.
  2. If |json tab strip|["new_tab_button"] does not exist or |json tab strip|["new_tab_button"] is not an [=ordered map=], return.
  3. Let |url:URL| be the result of [=URL Parser|parsing=] |json tab strip|["new_tab_button"]["url"] with |manifest URL| as the base URL.
  4. If |url| is failure, return.
  5. If |url| is not [=URL/within scope=] of |manifest URL|, return.
  6. Set |manifest tab strip|["new_tab_button"]["url"] to |url|.

Processing the `scope_patterns` member

To process the `scope_patterns` member, given [=ordered map=] |json home tab:ordered map|, [=ordered map=] |manifest home tab:ordered map| and [=URL=] |manifest URL:URL|, run the following:

  1. Let |processed scope patterns:list| be a new [=list=].
  2. Set |manifest home tab|["scope_patterns"] to |processed scope patterns|.
  3. If |json home tab|["scope_patterns"] doesn't exist or |json home tab|["scope_patterns"] is not a [=list=], return.
  4. For each |entry:URLPatternInit| of |json home tab|["scope_patterns"]:
    1. Let |pattern:URL pattern| be the result of [=build a URL pattern from an infra value|building a URL pattern from an infra value=] |entry| given |manifest URL|. If this process throws or returns null, continue.
    2. Append |pattern| to |processed scope patterns|.

Usage Example

        {
          "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"
            }
          }
        }
        

This example is a tabbed web app that falls back to a single-document standalone window if tabbed mode is not supported. Any navigation to the main index page (either / or /index.html) is opened in the [=home tab context=]. The new tab button will open a new tab at /create.

Note that the [=URL/query=] part of the URL is ignored by default when matching against [=tab_strip/home_tab/scope_patterns=] (so a navigation to /index.html?utm_source=foo will open in the home tab). However, when matching against [=start URL=], the [=URL/query=] must match exactly, so sites that want to ignore the query are advised to explicitly include the [=start URL=]'s [=URL/path=] as a scope pattern.

`share_target` member

The `share_target` member registers a web application as "target" for share actions (e.g., for sharing a text, a URL, or a file). The `share_target` member is part of the [[[web-share-target]]] specification.

note_taking member

The `note_taking` member of the Web Application Manifest is an object that contains information related to note-taking. It has the following members:

A user agent MAY use these members to treat the web application differently as an application with note-taking capabilities (e.g., integrate with operating system note-taking surfaces).

new_note_url member

The [=note_taking=] `new_note_url` member is a [=string=] that represents the URL the developer would prefer the user agent load when the user wants to take a new note using the web application (e.g., from an operating system shortcut icon or keyboard shortcut).

The `new_note_url` member is purely advisory, and a user agent MAY ignore it or provide the end-user the choice of whether to use it. The user agent MAY provide the end-user the choice to modify it.

Usage Example

The following shows a [=manifest=] for a note-taking application.

          {
            "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"
            }
          }
        

Processing the `note_taking` member

To process the `note_taking` member, given [=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, [=URL=] |manifest_URL:URL|, run the following during the extension point in [=processing a manifest=]:

  1. If |json|["note_taking"] does not [=map/exist=], return.
  2. If the type of |json|["note_taking"] is not [=ordered map=], return.
  3. Set |manifest|["note_taking"] to a new [=ordered map=].
  4. [=process the `new_note_url` member=] passing |json|["note_taking"], |manifest|["note_taking"], and |manifest URL|.

Processing the `new_note_url` member

To process the `new_note_url` member, given [=ordered map=] |json_note_taking:ordered map|, [=ordered map=] |manifest_note_taking:ordered map|, [=URL=] |manifest_URL:URL|, run the following:

  1. If |json_note_taking|["new_note_url"] does not [=map/exist=], return.
  2. If the type of |json_note_taking|["new_note_url"] is not [=string=], return.
  3. Let |new_note_url:URL| be the result of [=URL Parser|parsing=] |json_note_taking|["new_note_url"] with |manifest URL| as the base URL.
  4. If |new_note_url| is failure, return.
  5. If |new_note_url| is not [=manifest/within scope=] of |manifest URL|, return.
  6. Set manifest_note_taking["new_note_url"] to |new_note_url|.

Launching the `new_note_url`

To launch the `new_note_url`, given processed manifest |manifest:processed manifest|, run the following steps:

  1. If |manifest|["note_taking"] does not [=map/exist=], return.
  2. If |manifest|["note_taking"]["new_note_url"] does not [=map/exist=], return.
  3. If the type of |manifest|["note_taking"]["new_note_url"] is not [=URL=], return.
  4. Run the steps to [=launch a web application=] setting |manifest| to |manifest| and |target URL| to |manifest|["note_taking"]["new_note_url"].

The user agent MAY [=launch the `new_note_url`=] for a given [=installed web application=] at any time, typically in response to a user affordance.

`protocol_handlers` member

The [=manifest's=] protocol_handlers member is an array of protocol handler descriptions that allows a web application to handle URL protocols.

On installation, a user agent SHOULD register protocol handlers with the Operating System via interactions that are consistent with:

Processing the `protocol_handlers` member

To process the `protocol_handlers` member, given [=object=] |json:JSON|, |manifest:ordered map|:

  1. Let |processedProtocolHandlers| be a new [=list=] of |json:JSON|["|protocol_handlers|"].
  2. Set manifest["|protocol_handlers|"] to |processedProtocolHandlers|.
  3. [=list/For each=] |protocol_handler| (protocol handler description):
    1. If |protocol_handler|["protocol"] or |protocol_handler|["url"] is undefined, [=iteration/continue=].
    2. Let (|normalizedProtocol:string|, |normalizedUrl:URL|) be the result of running [=normalize protocol handler parameters=] with |protocol_handler|["protocol"], | protocol_handler|["url"] using |manifest URL| as the base URL, and [=this=]'s relevant [=environment settings object=]. If the result is failure, [=iteration/continue=].
    3. If |normalizedUrl| is not [=manifest/within scope=] of |manifest|, [=iteration/continue=].
    4. If |processedProtocolHandlers| [=list/contains=] the |normalizedUrl|, [=iteration/continue=].
    5. [=List/Append=] |protocol_handler| to |processedProtocolHandlers|.
  4. [=list/For each=] |processedProtocolHandlers|, the user agent SHOULD [=register a protocol handler=].

A user agent SHOULD ask users for permission before registering a [=protocol handler description=] protocol_handlers as the default handler for a protocol with the host operating system. A user agent MAY truncate the list of [=protocol handler description=] protocol_handlers presented in order to remain consistent with the conventions or limitations of the host operating system.

Protocol handler items

Each protocol handler description is an [=object=] that represents a protocol that the web application wants to handle, corresponding to the [=manifest/protocol_handlers=] member. It has the following members:

A user agent SHOULD use these values to register the web application as a handler with the operating system. When the user activates a protocol handler URL, the user agent SHOULD run handling a protocol launch.

[[HTML]]'s {{NavigatorContentUtils/registerProtocolHandler()}} allows web sites to register themselves as possible handlers for particular protocols. What constitutes valid [=protocol handler description/protocol=] and [=protocol handler description/url=] values for protocol handler descriptions is defined in that API. Also note that the [[HTML]] API uses scheme where we use [=protocol handler description/protocol=] but the same restrictions apply.

`protocol` member

The protocol member of a protocol handler description is a string that represents the protocol to be handled, such as `mailto` or `web+auth`.

The [=protocol handler description/protocol=] member of a protocol handler description is equivalent to {{NavigatorContentUtils/registerProtocolHandler()}}'s scheme argument defined in [[HTML]].

`url` member

The url member of a protocol handler description is the URL [=manifest/within scope=] of the application that opens when the associated protocol is activated.

The [=protocol handler description/url=] member of a protocol handler description is equivalent to {{NavigatorContentUtils/registerProtocolHandler()}}'s url argument defined in [[HTML]].

Handling a protocol launch

When a protocol handler description protocol_handler having [=manifest=] manifest is invoked, it goes through the same steps used to [=invoke a protocol handler=] where the user agent, instead of [=navigating=] to resultURL, SHOULD [=launch a web application=] passing manifest and resultURL.

This should not invoke and alter [=invoke a protocol handler=] in this way. The computation of resultURL should be extracted out into a separate algorithm for general use.

Privacy consideration: Default protocol handler

Depending on the operating system capabilities, the protocol handler could become a 'default' handler (e.g. handling launches of a given protocol automatically) of a given protocol without the explicit knowledge of the user. To protect against possible misuse, user agents MAY employ protections such as:

If a user agent removes the the registration of the protocol handler entity it SHOULD provide UX for the user to re-register the web app and protocol with the operating system.

`file_handlers` member

The [=manifest's=] file_handlers member is a [=list=] that provides instructions for how the app handles file-opening actions that originate outside of the app itself.

The management, presentation, and selection of registered file-handling applications is at the discretion of the operating system.

To process the `file_handlers` member, given [=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, [=URL=] |manifest_url:URL|:

  1. Let |processedFileHandlers:list| be a new [=list=].
  2. Set |manifest|["file_handlers"] to |processedFileHandlers|.
  3. If |json|["file_handlers"] doesn't [=map/exist=] or |json|["file_handlers"] is not a [=list=], return.
  4. [=list/For each=] |entry:ordered map| of |json|["file_handlers"]:
    1. Let |file_handler:ordered map| be [=process a file handler item=] with |entry|, [=manifest/start_url=], [=manifest/scope=], and |manifest_url|.
    2. If |file_handler| is failure, [=iteration/continue=].
    3. [=list/Append=] |file_handler| to |processedFileHandlers|.

On [=installation=], a user agent SHOULD run the process to [=register file handlers=].

File Handler Items

Each file handler represents a URL in the scope of the application that can handle launches with [=file types=] it accepts. It has the following members:

A user agent can use these members to associate the web application with [=file type=] on the operating system.

A file type can be defined by a [=MIME type=] and/or [=file extension=]. A file belongs to a file type if the OS determines it to have a [=MIME type=] and/or its name ends with a certain [=file extension=]. A file extension is a string that start with a "." and only contains valid suffix code points. Additionally, [=file extensions=] are limited to a length of 16 code points.

`action` member

The [=file handler=]'s action member is a string that represents a URL relative to the manifest URL that is [=manifest/within scope=] of a processed manifest . This URL will be navigated to in the steps to [=execute a file handler launch=].

`name` member

The [=file handler=]'s name member is a string that identifies the file type to the user. [=User agents=] MAY pass this information to the operating system during file handler registration.

`icons` member

The file handler's icons member lists icons that serve as graphical representations of a [=file type=]. User agents MAY pass this information to the operating system during file handler registration.

`accept` member

The [=file handler=]'s accept member is a dictionary mapping [=MIME types=] to a list of [=file extensions=].

[=User agents=] MUST enforce that the [=file handler/accept=] entry only applies to files that have a matching extension.

In order to [=register file handlers=], some operating systems require [=MIME types=] and some require [=file extensions=]. Thus the manifest MUST always provide both for each [=file handler/accept=] entry.

In addition to complete [=MIME types=], "*" can be used as the subtype of a [=MIME type=] to match, for example, all image formats with "image/*" (that also apply to the provided list of [=file extensions=]).

`launch_type` member

The [=file handler=]'s launch_type member is a string that determines how the app is launched for files routed to this handler. The possible values are `"single-client"` and `"multiple-clients"`. If not provided, it defaults to `"single-client"`.

When a [=file handler=] is determined to match a set of files, the [=user agent=] SHOULD use [=file handler/launch_type=] to control whether the app is launched once for each file (`"multiple-clients"`), or one time for all files (`"single-client"`). See {{LaunchParams/files}}. The user agent MUST NOT coalesce files from different [=file handlers=] into a single launch event.

Processing file handler items

To process a file handler item, given [=ordered map=] |item:ordered map|, [=URL=] |start_url:URL|, [=URL=] |scope:URL|, and [=URL=] |manifest URL:URL|:

  1. Return failure if any of the following is true:
    • |item|["action"] doesn't [=map/exist=] or is not a [=string=].
    • |item|["accept"] doesn't [=map/exist=].
    • |item|["accept"] is not a [=dictionary=].
    • |item|["accept"] [=map/is empty=].
  2. Let |url:URL| be the result of [=URL Parser|parsing=] |item|["action"] with |manifest_url URL| as the base URL.
  3. If |url| is failure, return failure.
  4. If |url| is not [=manifest/within scope=] of |scope|, return failure.
  5. Let |launch_type:string| be a new [=string=] initialized to "single-client".
  6. If |item|["launch_type"] [=map/exists=] and is "multiple-clients", set |launch_type| to |item|["launch_type"].
  7. Let |accept:ordered map| be a new [=ordered map=].
  8. [=map/for each=] |mime_type_string:string| → |extensions| of |item|["accept"]
    1. If |extensions| is not a [=list=], [=iteration/continue=].
    2. If |extensions| is [=list/empty=], [=iteration/continue=].
    3. If |extensions| [=list/contains=] items that are not [=string=]s, [=iteration/continue=].
    4. If |extensions| [=list/contains=] strings that do not begin with the character `.`, [=iteration/continue=].
    5. If |extensions| [=list/contains=] strings that are greater than 16 characters long, [=iteration/continue=].
    6. Let |mime_type_parsed:mime type| be the result of running the steps of [=parse a mime type=] on |mime_type_string|.
    7. If |mime_type_parsed:mime type| is failure, [=iteration/continue=].
    8. If |mime_type_parsed/type| is not listed as a top-level type in [[IANA-MEDIA-TYPES]], [=iteration/continue=].
    9. Set |accept|[|mime_type_string|] to |extensions|.
  9. If |accept:ordered map| is empty, return failure.
  10. Let |file_handler:ordered map| be |ordered map| «[ "action" → |url|, "name" → |item|["name"], "launch_type" → |launch_type|, "accept" → |accept| ]».
  11. Process image resources passing |item|["icons"], |file_handler|, |manifest URL|, and "icons".
  12. Return |file_handler|.

Execute a file handler launch

The steps to execute a file handler launch are given by the following algorithm. The algorithm takes [=list=] |files:list| and a [=ordered map=] |manifest:ordered_map| which holds results from [=processing a manifest=].

  1. Let |file_handlers:list| be |manifest|["file_handlers"].
  2. If |file_handlers:list| is null, return.
  3. Let |launches:ordered map| be an [=ordered map=].
  4. [=list/for each=] |filename:string| of |files|
    1. [=list/for each=] |file_handler:ordered_map| of |file_handlers:list|:
      1. [=map/for each=] |mime_type_string:string| → |extensions:list| of |file_handler|["accept"]
        1. [=list/for each=] |extension:string| of |extensions|:
          1. If |filename| does not end in |extension|, [=iteration/continue=].
          2. If |launches|[|file_handler|] [=map/exists=], [=list/append=] |filename| to |launches|[|file_handler|].
          3. Else, set |launches|[|file_handler|] to a [=list=] with the single element |filename|.
          4. [=iteration/Continue=] to next element of |files|.
  5. [=map/for each=] |file_handler| → |files:list| of |launches|
    1. If |file_handler|["launch_type"] is equal to "multiple-clients"
      1. [=list/for each=] |file| of |files|
        1. Let |params:LaunchParams| be a new {{LaunchParams}} with {{LaunchParams/files}} set to a {{FrozenArray}} with a single element that is a {{FileSystemHandle}} corresponding to |file|.
        2. [=Launch a web application with handling=], passing |manifest| and |params|.
    2. Else,
      1. Let |params:LaunchParams| be a new {{LaunchParams}} with {{LaunchParams/files}} set to a {{FrozenArray}} of {{FileSystemHandle}}s that correspond to the file paths named by |files|.
      2. [=Launch a web application with handling=], passing |manifest| and |params|.

Registering file handlers

A user agent SHOULD register file handlers with the host operating system, consistent with:

A user agent MAY truncate the total set of [=file extensions=] to preserve functionality and prevent abuse. A user agent MAY prevent associations with certain filetypes.

Privacy consideration: Default file handler.

Depending on the operating system capabilities, the [=file handler=] could become a default handler of a given [=file type=] without the explicit knowledge of the user, handling launches of a given file type automatically. To protect against possible mis-use, [=user agents=] MAY require explicit user consent to begin with the process to [=execute a file handler launch=]. If consent is sought, the user agent SHOULD allow the user to specify that the decision is permanent and if so specified SHOULD remove the web application's OS registration as a file handling entity.

Privacy consideration: Name and icon.

The name and icon of each file handler can be sensitive to privacy and security, as there isn't a specified way for the user to see and confirm these. Due to this, [=user agents=] MAY choose to ignore these in favor of alternative strings and icons or fall back on OS defaults.

Example manifest with file handlers

In the following example, the web application has 3 different file handlers.

Installation prompts

There are multiple ways that the installation process can be triggered:

In any case, the user agent MUST NOT present an install prompt if the document is not installable.

The user agent MAY, at any time (only if the document is installable), run the steps to notify that an install prompt is available at any time, giving the site the opportunity to show a site-triggered install prompt without the user needing to interact with the user agent UI.

To present an install prompt:

  1. Show some user-agent-specific UI, asking the user whether to proceed with installing the app. The result of this choice is either {{AppBannerPromptOutcome/"accepted"}} or {{AppBannerPromptOutcome/"dismissed"}}.
  2. Return result, and in parallel:
    1. If result is {{AppBannerPromptOutcome/"accepted"}}, run the steps to install the web application.

The steps to notify that an install prompt is available are given by the following algorithm:

  1. Wait until the {{Document}} of the top-level browsing context is completely loaded.
  2. If there is already an install prompt being presented or if the steps to install the web application are currently being executed, then abort this step.
  3. Queue a task on the application life-cycle task source to do the following:
    1. Let |mayShowPrompt| be the result of [=fire an event=] named `"beforeinstallprompt"` at the [=top-level browsing context=]'s [=relevant global object=] using the {{BeforeInstallPromptEvent}} interface, with steps to initialize the {{Event/cancelable}} attribute to `true`.
    2. If |mayShowPrompt| is true, then the user agent MAY, in parallel, request to present an install prompt with |event|.

Installable web applications

Installation process

The steps to install the web application are given by the following algorithm:

  1. Let manifest be the manifest of an installable document.
  2. Perform an unspecified sequence of actions to attempt to register the web application in the user's operating system (e.g., create shortcuts that launch the web application, register the application in the system uninstall menu, etc.). If the installation fails (which can be for any reason, for example, the OS denying permission to the user agent to add an icon to the home screen of the device), abort these steps.
  3. Queue a task on the application life-cycle task source to fire an event named `"appinstalled"` at the [=top-level browsing context=]'s [=relevant global object=] for which the installation took place.

Installability signals

By design, this specification does not provide developers with an explicit API to "install" a web application. Instead, a manifest can serve as an installability signal to a user agent that a web application can be installed. These signals will vary per user agent, as each user agent will have its own heuristics to determine whether a web site is elegible of an install prompt. Implementers generally will provide documentation that describe their particular installabilty signals or other relevant criteria a web application needs to meet to be deemed installable.

Examples of possible installability signals for a web application that a user agent might implement:

This list is not exhaustive and some installability signals might not apply to all user agents. How a user agent makes use of these installability signals to determine if a web application can be installed is left to implementers.

Installation Events

[=event|Events=] of this specification rely on the application life-cycle task source.

BeforeInstallPromptEvent Interface

The beforeinstallprompt event is somewhat misnamed, as it does not necessarily signal that a manual installation will follow (depending on the user agent, it might just be giving the site the ability to trigger an install prompt). It is so named for historical reasons.
          [Exposed=Window]
          interface BeforeInstallPromptEvent : Event {
            constructor(DOMString type, optional EventInit eventInitDict = {});
            Promise<PromptResponseObject> prompt();
          };

          dictionary PromptResponseObject {
            AppBannerPromptOutcome userChoice;
          };

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

The {{BeforeInstallPromptEvent}} is dispatched when the site is allowed to present a site-triggered install prompt, or prior to the user agent presenting an automated install prompt. It allows the site to cancel the automated install prompt, as well as manually present the site-triggered install prompt.

If the {{BeforeInstallPromptEvent}} is not cancelled, the user agent is allowed to present an install prompt (specifically, an automated install prompt) to the end-user. Canceling the default action (via {{Event/preventDefault()}}) prevents the user agent from presenting an install prompt. The user agent is free to run steps to notify that an install prompt is available again at a later time.

The PromptResponseObject contains the result of calling {{BeforeInstallPromptEvent/prompt()}}. It contains one member, userChoice, which states the user's chosen outcome.

An instance of a {{BeforeInstallPromptEvent}} has the following internal slots:

[[\didPrompt]]
A boolean, initially `false`. Represents whether this event was used to present an install prompt to the end-user.
[[\userResponsePromise]]
A promise that represents the outcome of presenting an install prompt.

prompt() method

The prompt method, when called, runs the following steps:

  1. Let |userResponsePromise| be {{BeforeInstallPromptEvent/[[userResponsePromise]]}}.
  2. If |userResponsePromise| is pending:
    1. If [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} is `true`, terminate this algorithm.
    2. If this event's {{Event/isTrusted}} attribute is `false`, reject |userResponsePromise| with {{"NotAllowedError"}} and terminate this algorithm.
    3. Set [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} to `true`.
    4. In parallel, request to present an install prompt with [=this=]. Wait, possibly indefinitely, for the end-user to make a choice.
  3. Return |userResponsePromise|.

To request to present an install prompt with {{BeforeInstallPromptEvent}} event:

  1. Present an install prompt and let |outcome| be the result.
  2. Let |response| be a newly created {{PromptResponseObject}}, initializing its {{PromptResponseObject/userChoice}} to |outcome|.
  3. [=Resolve=] |event|.{{BeforeInstallPromptEvent/[[userResponsePromise]]}} with |response|.

Usage example

This example shows how one might prevent an automated install prompt from showing until the user clicks a button to show a site-triggered install prompt. In this way, the site can leave installation at the user's discretion (rather than prompting at an arbitrary time), whilst still providing a prominent UI to do so.

              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

The AppBannerPromptOutcome enum's values represent the outcomes from presenting an install prompt.

"accepted":
The end-user indicated that they would like the user agent to install the web application.
"dismissed":
The end-user dismissed the install prompt.

Extensions to the `Window` object

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

onappinstalled attribute

The onappinstalled event handler IDL attribute handles "appinstalled" events.

onbeforeinstallprompt attribute

The onbeforeinstallprompt event handler IDL attribute handles "beforeinstallprompt" events.