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.
In order to implement this API, the user agent MUST support [[[appmanifest]]].
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.
The following steps are added to the extension point in the steps for processing a manifest:
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:
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:
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.
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:
`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.
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=].
|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=].
Switching on |client mode|, run the following substeps:
An appropriate selection strategy would be to pick the one that was most recently in focus.
The steps to process unconsumed launch params given a {{LaunchQueue}} |queue| are as follows:
[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"}}.
callback LaunchConsumer = any (LaunchParams params);
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.
The {{LaunchQueue/setConsumer(consumer)}} method steps are:
{{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.
This specification has no known accessibility 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|: