This specification defines an API that allows web applications to configure the behaviour of app launches with respect to already open app instances. This API aims to cater to the needs of single instance web apps e.g. music players.

Prerequisites

In order to implement this API, the user agent MUST support [[[appmanifest]]].

Usage Example

A music player app wants to direct app shortcut launches to an existing window without interrupting music playback. This music app would add a [=manifest/launch_handler=] entry to the [[[appmanifest]]], as shown:

      {
        "name": "Music Player",
        "shortcuts": [{
          "name": "Now Playing",
          "url": "/"
        }, {
          "name": "Library",
          "url": "/library"
        }, {
          "name": "Favorites",
          "url": "/favorites"
        }, {
          "name": "Discover",
          "url": "/discover"
        }],
        "launch_handler": {
          "client_mode": "focus-existing"
        }
      }
      

The [=manifest/client_mode=] parameter set to [=client mode/focus-existing=] causes app launches to bring existing app instances (if any) into focus without navigating them away from their current document.

A {{LaunchParams}} will be enqueued on the {{Window/launchQueue}} where the music player can read the {{LaunchParams/targetURL}} in its {{LaunchConsumer}} and handle it in script e.g.:

        window.launchQueue.setConsumer((launchParams) => {
          const url = launchParams.targetURL;
          // If the URL is to one of the app sections, updates the app view to
          // that section without interrupting currently playing music.
          if (maybeFocusAppSection(url)) {
            return;
          }
          // Could not handle the launch in-place, just navigate the page
          // (interrupts any playing music).
          location.href = url;
        });
      

A user, already using the music player app to listen to music, activating the "Library" app shortcut will trigger an app launch to /library which gets routed to the existing app instance, enqueued in the page's {{Window/launchQueue}} which, through the assigned {{LaunchConsumer}}, brings the library section of the music player into focus without affecting the current music playback.

Extension to the Web App Manifest

The following steps are added to the extension point in the steps for processing a manifest:

  1. Run the steps for [=processing the launch_handler member=] given [=ordered map=] |json:ordered map| and [=ordered map=] |manifest:ordered map|.

[=manifest/launch_handler=] member

The `launch_handler` is a dictionary containing configurations for how web app launches should behave.

[=manifest/launch_handler=] is a dictionary despite [=manifest/client_mode=] being the only member. This is to give room for more members to be added should other types of behaviors be needed in the future.

The steps for processing the launch_handler member, given [=ordered map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, are as follows:

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

[=manifest/client_mode=] member

The `client_mode` member of the [=manifest/launch_handler=] is a [=string=] or list of [=strings=] that specify one or more [=client mode targets=]. A [=client mode target=] declares a particular client selection and navigation behaviour to use for web apps launches.

User agents MAY support only a subset of the [=client mode targets=] depending on the constraints of the platform (e.g. mobile devices may not support multiple app instances simultaneously).

The client mode targets are as follows:

auto
The user agent's default launch routing behaviour is used.

The user agent is expected to decide what works best for the platform. e.g., on mobile devices that only support single app instances the user agent may use `navigate-existing`, while on desktop devices that support multiple windows the user agent may use `navigate-new` to avoid data loss.

navigate-new
A new web app client is created to load the launch's target URL.
navigate-existing
If an existing web app client is open it is brought to focus and navigated to the launch's target URL. If there are no existing web app clients the [=client mode/navigate-new=] behaviour is used instead.
focus-existing
If an existing web app client is open it is brought to focus but not navigated to the launch's target URL, instead the target URL is communicated via {{LaunchParams}} . If there are no existing web app clients the [=client mode/navigate-new=] behaviour is used instead.

It is necessary for the page to have a {{LaunchConsumer}} set on {{Window/launchQueue}} to receive the launch's {{LaunchParams}} and do something with it. If no action is taken by the page the user experience of the launch is likely going to appear broken.

To process the `client_mode` member, given [=ordered map=] |json_launch_handler:ordered map|, [=ordered map=] |manifest_launch_handler:ordered map|, run the following:

  1. If |json_launch_handler|["client_mode"] does not [=map/exist=], return.
  2. If the type of |json_launch_handler|["client_mode"] is [=list=]:
    1. [=list/For each=] |entry| of |json_launch_handler|["client_mode"]:
      1. If the type of |entry| is not [=string=], continue.
      2. If |entry| is supported by the user agent, set |manifest_launch_handler|["client_mode"] to |entry| and return.
  3. If |json_launch_handler|["client_mode"] is [=string=] and supported by the user agent, set |manifest_launch_handler|["client_mode"] to |json_launch_handler|["client_mode"] and return.
  4. Set |manifest_launch_handler|["client_mode"] to [=client mode/auto=].

`client_mode` accepts a list of strings to allow sites to specify fallback [=client mode targets=] to use if the preferred [=client mode target=] is not supported by the user agent or platform. The first supported [=client mode target=] entry in the list gets used.

Handling Web App Launches

Launching a Web App with Handling

This specification replaces the existing algorithm to [=launch a web application=] to include the behavior of [=manifest/launch_handler=] by replacing it with the steps to [=launch a web application with handling=].

The steps to launch a web application with handling are given by the following algorithm. The algorithm takes a [=Document/processed manifest=] |manifest:processed manifest|, an optional [=URL=] or {{LaunchParams}} |params|, an optional [=POST resource=] |POST resource| and returns an [=application context=].

  1. If |params| is not given, set |params| to |manifest|.[=manifest/start_url=].
  2. If |params| is a [=URL=], set |params| to a new {{LaunchParams}} with {{LaunchParams/targetURL}} set to |params|.
  3. Assert: |params|.{{LaunchParams/targetURL}} is [=manifest/within scope=] of |manifest|.
  4. Set |application context| to the result of running the steps to [=prepare an application context=] passing |manifest|, |params| and |POST resource|.
  5. Append |params| to the [=unconsumed launch params=] of the |application context|'s document's {{Window/launchQueue}}.
  6. Run the steps to [=process unconsumed launch params=] on the |application context|'s [=navigable/active document=]'s {{Window/launchQueue}}.

    |application context| may be an existing instance with an [=assigned launch consumer=] hence it is necessary to process the newly appended {{LaunchParams}}.

The steps to prepare an application context are given by the following algorithm. The algorithm takes a [=Document/processed manifest=] |manifest:processed manifest|, a {{LaunchParams}} |launch params|, an optional [=POST resource=] |POST resource| and returns an [=application context=].

  1. Let [=client mode target=] |client_mode| be |manifest|.[=manifest/launch_handler=].[=manifest/client_mode=].
  2. If |client_mode| is [=client mode/auto=], set |client_mode| to either [=client mode/navigate-new=] or [=client mode/navigate-existing=] according to the user agent's decision for which is most appropriate.
  3. Switching on |client mode|, run the following substeps:

    [=client mode/navigate-new=]
    1. Return the result of running the steps to [=create a new application context=] passing |manifest|, |launch params|.{{LaunchParams/targetURL}} and |POST resource|.
    [=client mode/navigate-existing=] or [=client mode/focus-existing=]
    1. If there is no [=application context=] that has |manifest| [=applied=]:
      1. Return the result of running the steps to [=create a new application context=] passing |manifest|, |launch params|.{{LaunchParams/targetURL}} and |POST resource|.
    2. Let |application context| be an [=application context=] that has |manifest| [=applied=], the user agent selects the most appropriate one if there are multiple.

      An appropriate selection strategy would be to pick the one that was most recently in focus.

    3. If |client mode| is [=client mode/focus-existing=] and |application context|'s current session history entry's URL is [=manifest/within scope=] of |manifest|:
      1. Bring |application context|'s viewport into focus.
      2. Return |application context|.
    4. [=Navigate=] |application context| to |launch params|.{{LaunchParams/targetURL}} passing |POST resource|.
    5. Return |application context|.

Processing [=unconsumed launch params=]

The steps to process unconsumed launch params given a {{LaunchQueue}} |queue| are as follows:

  1. If the [=assigned launch consumer=] |consumer| is set on |queue|:
    1. [=list/For each=] |launch_params:LaunchParams| of the |queue|'s [=unconsumed launch params=]:
      1. Invoke |consumer| with |launch_params|.
    2. Set |queue|'s [=unconsumed launch params=] to the empty list.

Script Interfaces to App Launches

LaunchParams interface

          [Exposed=Window] interface LaunchParams {
            readonly attribute DOMString? targetURL;
            readonly attribute FrozenArray<FileSystemHandle> files;
          };
        

{{LaunchParams/targetURL}} represents the [=URL=] of the launch which MUST be [=manifest/within scope=] of the application.

For every |file handle:FileSystemHandle| in {{LaunchParams/files}}, querying the file system permission with {{FileSystemPermissionDescriptor/mode}} set to {{FileSystemPermissionMode/"readwrite"}} MUST return {{PermissionState/"granted"}}.

LaunchConsumer function

          callback LaunchConsumer = any (LaunchParams params);
        

LaunchQueue interface

          partial interface Window {
            readonly attribute LaunchQueue launchQueue;
          };

          [Exposed=Window] interface LaunchQueue {
            undefined setConsumer(LaunchConsumer consumer);
          };
        

{{LaunchQueue}} has an unconsumed launch params which is a [=list=] of {{LaunchParams}} that is initially empty.

{{LaunchQueue}} has an assigned launch consumer which is an optional {{LaunchConsumer}} that is initially null.

setConsumer method

The {{LaunchQueue/setConsumer(consumer)}} method steps are:

  1. Set the [=assigned launch consumer=] to |consumer|.
  2. Run the steps to [=process unconsumed launch params=].

{{LaunchParams}} are passed to the document via a {{LaunchQueue}} instead of via events to avoid a race condition between a launch event firing and page scripts attaching the event listener. In contrast the {{LaunchQueue}} buffers all enqueued {{LaunchParams}} until a {{LaunchConsumer}} has been set.

Accessibility

This specification has no known accessibility considerations.

Privacy and Security Considerations

Implementations should take care when [=launching a web application with handling=] for launches where [=manifest/client_mode=] is [=client mode/focus-existing=]. These launches MUST NOT leak URLs outside of the [=manifest/navigation scope=]. This applies in both directions given a [=Document/processed manifest=] |manifest|: