├── .gitignore ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── archived └── translations-explainer.md ├── borderless-explainer.md ├── display_mode-client-hint.md ├── images ├── borderless-app-with-AWC.png ├── clumsy-app-with-wco.png ├── current-chromium-dialogs.png ├── out-of-scope-navigation.png ├── out-of-scope-ux.png ├── privacy-indicators-in-shelf.png └── scope-privacy-info.png ├── index.html ├── note_taking-explainer.md ├── predictable-app-updating-security-privacy-questionnaire.md ├── predictable-app-updating.md ├── scope_extensions-explainer.md ├── scope_extensions-security-privacy-questionnaire.md ├── tabbed-mode-explainer.md ├── tidyconfig.txt ├── user-preferences-explainer.md └── w3c.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.html", 3 | "type": "respec" 4 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the W3C Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive contributions, 5 | you must join the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request comment. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors 2 | under the 3 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 4 | 5 | Contributions to Specifications are made under the 6 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 7 | 8 | Contributions to Test Suites are made under the 9 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Specification 'manifest-incubations' 3 | 4 | Specification link: https://wicg.github.io/manifest-incubations/index.html 5 | 6 | # Explainers 7 | 8 | - [Scope Extensions for Web Apps](scope_extensions-explainer.md) 9 | 10 | # Background 11 | 12 | There are a number of [Web App Manifest](https://www.w3.org/TR/appmanifest/) features that are either not mature enough to go into the main W3C spec, or that do not belong there because they do not have multiple independent implementations. 13 | 14 | - [`BeforeInstallPrompt`](https://github.com/w3c/manifest/pull/836) 15 | - [`display_override`](https://github.com/w3c/manifest/pull/932) (unless that PR can land in W3C Manifest) 16 | - [Tabbed mode](https://github.com/w3c/manifest/issues/737) 17 | - [Protocol handlers](https://github.com/w3c/manifest/issues/846) 18 | - [Declarative link capturing](https://github.com/WICG/sw-launch/blob/master/declarative_link_capturing.md) 19 | 20 | Rather than creating a separate WICG repo for each of these (given that there are often interdependencies between them), we are creating a "manifest-incubations" spec with all of them in a single document. That means readers only need to keep track of two documents, instead of ~10. 21 | 22 | Other related incubations that _do_ have their own WICG repos: 23 | 24 | - [URL handlers](https://github.com/WICG/pwa-url-handler/) 25 | - [File handlers](https://github.com/WICG/file-handling/) 26 | - [Window controls overlay](https://github.com/WICG/window-controls-overlay/) 27 | 28 | Despite this, it may make sense for their spec text to live inside `manifest-incubations` since they may have dependencies on other things here (e.g., `display_override`). 29 | 30 | ## Contact 31 | 32 | * Daniel Murphy \ 33 | * Matt Giuca \ 34 | -------------------------------------------------------------------------------- /archived/translations-explainer.md: -------------------------------------------------------------------------------- 1 | # Web App Translations 2 | 3 | **This document is obsolete. The manifest editors have chosen to implement this using a different approach. You can track the progress in [w3c/manifest#1101](https://github.com/WICG/manifest-incubations/pull/103).** 4 | 5 | --- 6 | 7 | Authors: [Aaron Gustafson](https://github.com/aarongustafson), Louise Brett (loubrett@google.com), Glen Robertson (glenrob@chromium.org) 8 | 9 | TODO: This explainer needs to be updated to match the [Web Manifest Overrides](https://github.com/w3c/manifest/issues/1045) proposal. 10 | 11 | ## Overview 12 | 13 | This document proposes a new `translations` manifest member that enables web apps to provide alternate values for manifest members in specific languages. 14 | 15 | ## Problem 16 | 17 | Currently the Web App Manifest has no direct support for localization so fields can only be provided in a single language. There are two recommended ways to translate the manifest suggested in [non-normative text in the spec](https://www.w3.org/TR/appmanifest/#internationalization): 18 | 19 | * Using a different manifest URL for each language and having HTML point to the correct one. 20 | * Dynamically serving a localized manifest from the same URL. 21 | 22 | There are several problems with these approaches: 23 | 24 | * For the first option having a different manifest URL can cause some user agents to consider it a different app. 25 | * The second option requires dynamic logic on the server side. 26 | * For both of these options the app will not update immediately when the language is changed. 27 | * For both options there is no way for a crawler to index all supported languages. 28 | 29 | ## Proposal 30 | 31 | Add a new dictionary manifest entry `translations`, mapping locale strings to a [`ManifestOverride` object](#manifestoverride-object). 32 | 33 | 34 | ### `ManifestOverride` Object 35 | 36 | A `ManifestOverride` is a generic object that contains a subset of redefined manifest properties appropriate to the context in which the `ManifestOverride` is being used. The context (e.g., `translations`) will define which properties may be redefined. Any properties not allowed within the context will be ignored. 37 | 38 | If the condition governing a `ManifestOverride` is met (e.g., the locale string matches the user agent language), the override is active. When active, 39 | 40 | * Redefined string properties will override the initial value set in the root of the Manifest. 41 | * Redefined array items 42 | * **Will be overridden in the order they are authored.** When redefining objects (e.g., `ShortcutItem`, `ImageResource`), authors will only be able to redefine specific properties of that object. In order to ensure all overrides are applied correctly, the order must match the original array (i.e., each `ShortcutItem` must be redefined in order, as must their `icons`, if they also require re-definition). 43 | * **Should be equal in number to the array being overridden**. If there is a mismatch in the number of items in either array, any excess items will be ignored. This is only an issue if the original array has more items than the override array, because any excess items within the original array will not be re-defined. 44 | 45 |

In the context of the `translations` member, acceptable keys for the `ManifestOverride` include:

46 | 47 | * `dir` 48 | * `name` 49 | * `short_name` 50 | * `description` 51 | * `icons` 52 | * `src` 53 | * `type` 54 | * `screenshots` 55 | * `src` 56 | * `type` 57 | * `label` 58 | * `shortcuts` 59 | * `name` 60 | * `short_name` 61 | * `description` 62 | * `icons` 63 | * `src` 64 | * `type` 65 | 66 | ### Simple `translations` Example 67 | 68 | When the user agent language matches a language code key in the `translations` object, those fields can be used instead of the top-level fields. If none of the provided languages match, the top-level fields can be used. 69 | 70 | ```json 71 | { 72 | "lang": "en", 73 | "name": "Good dog", 74 | "description": "An app for dogs", 75 | "translations": { 76 | "fr": { 77 | "name": "Bon chien", 78 | "description": "Une application pour chiens", 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | In the above example, people who have their language set to French would install a web app named "Bon chien," whereas people who have their language set to anything other than French would install a web app named "Good dog." 85 | 86 | ### Complex `translations` Example 87 | 88 | For array members (e.g., `icons`, `screenshots`, `shortcuts`), only a subset of each item’s properties are open to redefinition. For example, a `ShortcutItem`’s `name` may be translated, but its `url` cannot be changed. [A complete list of translatable properties can be found above](#translatable-members). Any attempts to redefine properties other than those allowed will be ignored. 89 | 90 | 91 | ```json 92 | "shortcuts": [ 93 | { 94 | "name": "Pet Me", 95 | "url": "/pet-me" 96 | }, 97 | { 98 | "name": "Feed Me", 99 | "url": "/feed-me" 100 | } 101 | ], 102 | "translations": { 103 | "fr": { 104 | "shortcuts": [ 105 | { 106 | "name": "Caressez-moi" 107 | }, 108 | { 109 | "name": "Nourrissez-moi" 110 | } 111 | ] 112 | } 113 | }, 114 | ``` 115 | 116 | To ensure translations for these complex constructs do not get out of sync with their counterparts in the original arrays, it’s imperative that developers pay close attention to both the number and order of array members. Failure to do so, creates an opportunity for any/all of the following: 117 | 118 | * The first and second `ShortcutItem`s are swapped, but the corresponding translations are not. When this happens, the wrong translations are applied and users will be incredibly confused. 119 | * A new `ShortcutItem` is appended to the array, without corresponding translation(s). When this happens, the last shortcut would remain untranslated and appear in the default language of the Manifest. 120 | * A new `ShortcutItem` is prepended to the array, without corresponding translation(s). When this happens, the translations get out of sync, with the original translation(s) of the first `ShortcutItem` being applied to the new item, and the final `ShortcutItem` would not be translated. Users will be incredibly confused. 121 | 122 | 123 | ## Possible extensions 124 | 125 | In addition to the language string mapping to an object containing the translations as proposed, it could map to a string that defines the location of a separate JSON file containing the translations. 126 | 127 | 128 | manifest.json: 129 | ```json 130 | { 131 | "name": "Good dog", 132 | "description": "An app for dogs", 133 | "icons": [], 134 | "screenshots": [], 135 | "lang": "en", 136 | "translations": { 137 | "fr": "manifest.fr.json" 138 | } 139 | } 140 | ``` 141 | 142 | manifest.fr.json: 143 | ```json 144 | { 145 | "name": "Bon chien", 146 | "description": "Une application pour chiens", 147 | "icons": [], 148 | "screenshots": [] 149 | } 150 | ``` 151 | 152 | 153 | Since the translations field could become quite large and difficult to manage in the single file, this option reduces the size of the manifest and allows user agents to only download the languages they need. It is not proposed as the initial implementation due to the complexity and overhead of downloading a separate file for every language. 154 | 155 | ## Security and Privacy Considerations 156 | 157 | Allowing apps to specify different names and icons introduces a potential spoofing concern as apps could pretend to be a different app once the language has changed. User agent developers should be aware of this and consider measures to prevent spoofing. For example, they might make it obvious to the user what has changed for each app when the language changes or show the alternatives at install time. 158 | 159 | ## Alternatives Considered 160 | 161 | Some alternatives were considered on the [original issue thread](https://github.com/w3c/manifest/issues/676): 162 | 163 | 1. Have the translations field point to a single separate file which contains all of the translations. This has the advantage of keeping the manifest file small. However this adds the complexity of having another file to download without the benefit of only downloading the necessary languages (which is possible with the extension option above). 164 | 2. Have the translations field keyed by manifest member rather than by language: 165 | 166 | ```json 167 | { 168 | "name": "Good dog", 169 | "description": "An app for dogs", 170 | "lang": "en", 171 | "translations": { 172 | "name": { 173 | "fr": "Bon chien" 174 | }, 175 | "description": { 176 | "fr": "Une application pour chiens" 177 | }, 178 | "icons": [] 179 | } 180 | ``` 181 | 182 | This approach is less readable for complex fields such as shortcuts, for little/no benefit over the proposed option. 183 | -------------------------------------------------------------------------------- /borderless-explainer.md: -------------------------------------------------------------------------------- 1 | # Borderless Explainer 2 | 3 | **Status**: draft, **Last updated**: 03.05.2023 4 | 5 | Related documents: 6 | 7 | - [window-controls-overlay explainer](https://github.com/WICG/window-controls-overlay/blob/main/explainer.md) 8 | - [additional-windowing-controls (AWC) explainer](https://github.com/ivansandrk/additional-windowing-controls/blob/main/awc-explainer.md) 9 | - [ChromeOS app-details](https://crbug.com/1225871) (Googlers-only) 10 | - [go/cros-privacy-indicators-design](http://go/cros-privacy-indicators-design) 11 | (Googlers-only) 12 | 13 | ## Table of contents 14 | 15 | - [Borderless Explainer](#borderless-explainer) 16 | - [Table of contents](#table-of-contents) 17 | - [Introduction](#introduction) 18 | - [Example of a remote-app in VDI context with window-controls-overlay](#example-of-a-remote-app-in-vdi-context-with-window-controls-overlay) 19 | - [Goals](#goals) 20 | - [Non-goals](#non-goals) 21 | - [Proposal](#proposal) 22 | - [1. Setting the `display_override` to borderless](#1-setting-the-display_override-to-borderless) 23 | - [2. Enabling draggable regions](#2-enabling-draggable-regions) 24 | - [3. CSS display-mode media query for borderless](#3-css-display-mode-media-query-for-borderless) 25 | - [4. Leveraging Window Management permission for borderless](#4-leveraging-window-management-permission-for-borderless) 26 | - [Enabling Window Management](#enabling-window-management) 27 | - [Disabling Window Management](#disabling-window-management) 28 | - [5. Isolated Web App check](#5-isolated-web-app-check) 29 | - [Displaying app’s origin](#displaying-apps-origin) 30 | - [Privacy indicators](#privacy-indicators) 31 | - [Other notable details](#other-notable-details) 32 | - [“Settings and more” three-dot button](#settings-and-more-three-dot-button) 33 | - [Demo](#demo) 34 | - [manifest.json](#manifestjson) 35 | - [index.html](#indexhtml) 36 | - [style.css](#stylecss) 37 | - [Considered Alternatives](#considered-alternatives) 38 | - [Having a separate permission for borderless](#having-a-separate-permission-for-borderless) 39 | - [WCO extension](#wco-extension) 40 | - [Reasonings for discarding](#reasonings-for-discarding) 41 | - [New JS APIs](#new-js-apis) 42 | - [Reasonings for discarding](#reasonings-for-discarding-1) 43 | - [Security Considerations](#security-considerations) 44 | - [Spoofing risks](#spoofing-risks) 45 | - [Out-of-scope Navigation](#out-of-scope-navigation) 46 | - [Opening a popup from a borderless PWA](#opening-a-popup-from-a-borderless-pwa) 47 | - [Iframes](#iframes) 48 | 49 | ## Introduction 50 | 51 | Currently all the possible app display and 52 | [display_override](https://developer.mozilla.org/en-US/docs/Web/Manifest/display_override#values) 53 | modes rely on apps having at least some format of a title bar - something 54 | between the full Chrome title bar and currently most minimized 55 | [`window-controls-overlay`](https://developer.mozilla.org/en-US/docs/Web/API/Window_Controls_Overlay_API) (WCO). 56 | Despite WCO having some same qualities to what we are trying to achieve, it is 57 | still not offering enough flexibility for some use-cases. 58 | 59 | Some example use-cases for borderless (with no host-native title bar) could be: 60 | 61 | 1. Apps that want to fully customize their title bar to still have the same 62 | functionality, but with their own style, like 63 | [Steam](https://store.steampowered.com/). 64 | 2. Apps that want to completely remove the title bar and provide no controls 65 | (which can be seen sometimes in native apps e.g. for dialogs like splash 66 | screens, or for drop-down menus). Some of these want to be completely 67 | non-draggable and without any window controls. For example for a drop-down 68 | menu, there would not be any draggable area and clicking anywhere would call 69 | the API to close the drop-down menu window. 70 | 3. [VDI](https://www.softwaretestinghelp.com/best-vdi-software/#What_Is_Virtual_Desktop_Infrastructure) 71 | (Virtual Desktop Infrastructure) providers that want to mirror a remote app 72 | drawing a remote-OS-native title bar and we want to avoid drawing a second 73 | host-native title bar over the top of it or above it. 74 | 75 | To enable such use-cases, this explainer will explain how the title bar will be 76 | completely removed and so-called borderless mode enabled. This way the title bar 77 | area is replaced with web content and so giving the developers full control on 78 | how the title bar would look like. 79 | 80 | ## Example of a remote-app in VDI context with window-controls-overlay 81 | 82 | When app is streamed through VDI, an enforced `window-controls-overlay` title 83 | bar could still look clumsy. This is because it's a remote title bar that we 84 | have no control over. 85 | 86 | ![A clumsy VDI app with WCO](./images/clumsy-app-with-wco.png) 87 | 88 | ## Goals 89 | 90 | - Enable full control over the appearance of the title bar area 91 | - Ensure draggable regions work with borderless mode 92 | 93 | ## Non-goals 94 | 95 | - Implementation of 96 | [additional windowing controls](https://github.com/ivansandrk/additional-windowing-controls/blob/main/awc-explainer.md), 97 | which is another closely-related project 98 | - Making borderless mode dynamic (with e.g. JavaScript APIs or something else) 99 | - Changing display mode in app settings (e.g. from borderless to standalone) 100 | - Support for mobileOSs as there would be very little distinction from full-screen mode 101 | - Support for non-IWAs (Isolated Web Apps) 102 | 103 | ## Proposal 104 | 105 | The solution proposed consists of the following parts: 106 | 107 | 1. A new `display_override` option `borderless` for the web app manifest. 108 | 2. Enabling draggable regions when in borderless mode. 109 | 3. CSS display-mode media query for borderless. 110 | 4. Leveraging Window Management API permission (_earlier “Multi-screen Windows 111 | Placement” API permission_) for borderless mode. 112 | 5. Check that the app is Isolated Web App. 113 | 114 | ### 1. Setting the `display_override` to borderless 115 | 116 | To provide the maximum addressable area for web content, the User Agent (UA) 117 | will create a frameless window removing all UI, leaving only resizing of the 118 | window from its borders. The removed window controls will be enabled using AWC 119 | and HTML/JavaScript/CSS (see non-goals and 120 | [additional-windowing-controls explainer](<[http://go/additional-windowing-controls](https://github.com/ivansandrk/additional-windowing-controls/blob/main/awc-explainer.md)>)). 121 | 122 | Example apps in borderless mode could look e.g. like below but the appearance 123 | would eventually fully depend on what the developer would implement. 124 | 125 | ![Example borderless PWA](./images/borderless-app-with-AWC.png) 126 | 127 | The desire to remove the title bar will be declared within the web app manifest 128 | by setting the `display_override` to `borderless`. The `display_override` value 129 | will be ignored on unsupported OSs and it will act as a `standalone` window. 130 | This is because Isolated Web Apps default are not supported to open in `browser` 131 | display mode. 132 | 133 | ``` 134 | { 135 | "display_override": [ "borderless" ] 136 | } 137 | ``` 138 | 139 | ### 2. Enabling draggable regions 140 | 141 | When the app enters the borderless mode, app-defined 142 | [draggable regions](https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#defining-draggable-regions-in-web-content) 143 | will be enabled. Currently on many browsers, this is activated on elements with 144 | `-webkit-app-region: drag;` there is an ongoing 145 | [standards effort](https://github.com/w3c/csswg-drafts/issues/7017) to rename 146 | this to `app-region`. 147 | 148 | ### 3. CSS display-mode media query for borderless 149 | 150 | Similarly to other display modes, borderless should be queryable with @media. 151 | 152 | Example media query with borderless: 153 | 154 | ``` 155 | @media (display-mode: borderless) { 156 | .any-css-class-name { 157 | margin: 5px; 158 | } 159 | } 160 | ``` 161 | 162 | See [documentation of @media/display-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode). 163 | 164 | ### 4. Leveraging Window Management permission for borderless 165 | 166 | In order for the borderless mode to activate, the app needs to have Window 167 | Management permission granted. The permission prompt can be triggered using 168 | JavaScript. 169 | 170 | For the **managed** context, permission prompts can be by-passed with a policy 171 | set by an admin and later on edited in the app’s settings. For the **unmanaged** 172 | context, the Window management API permission must be granted. 173 | 174 | In case the Window management permission is not provided, the app will act like 175 | a `standalone` app as Isolated Web Apps do not support being opened in `browser` 176 | mode. 177 | 178 | #### Enabling Window Management 179 | 180 | Granting the window management API permission can be done with the following 181 | JavaScript: 182 | 183 | ``` 184 | const screenDetails = await window.getScreenDetails(); 185 | ``` 186 | 187 | That will show the permission prompt to enable Window Management API permission 188 | which will also enable borderless capability in case the permission is given. 189 | 190 | Later on, the state of the permission can be queried with the following script: 191 | 192 | ``` 193 | navigator.permissions.query({name:'window-management'}) 194 | .then((status) => { 195 | // Do what you need with the permission state. 196 | console.log(status.state) 197 | }); 198 | ``` 199 | 200 | If the user fails to grant the permission (e.g. clicks Block by accident), they 201 | can also enable the permission from the app's settings. 202 | 203 | #### Disabling Window Management 204 | 205 | To disable borderless mode, the user can disable the Window Management 206 | permission from the app's settings. 207 | 208 | ### 5. Isolated Web App check 209 | 210 | Borderless mode is only available for 211 | [Isolated Web Apps (IWAs)](https://github.com/WICG/isolated-web-apps). 212 | 213 | ### Displaying app’s origin 214 | 215 | Since without the title bar and the 3-dot menu, the app’s origin won’t be visible 216 | anymore, it should be shown somewhere else depending on each OS. E.g. for 217 | ChromeOS it is intended to be displayed in App Settings. 218 | 219 | Displaying the origin is a security requirement for web apps. However Isolated 220 | Web Apps are shifting away from origins and towards app names. One way to still 221 | be able to see the apps origin is via the developer console with 222 | [`document.location.href`](https://developer.mozilla.org/en-US/docs/Web/API/Location/href). 223 | 224 | ### Privacy indicators 225 | 226 | On some OSs removing the title bar might also remove the area for some critical 227 | privacy indicators, e.g. for camera and microphone access. This is again 228 | OS-dependant on where those indicators should be shown instead. One option is to 229 | move them to the shelf. 230 | 231 | Example on ChromeOS: 232 | ![Example privacy indicator on ChromeOS](./images/privacy-indicators-in-shelf.png) 233 | 234 | ## Other notable details 235 | 236 | ### “Settings and more” three-dot button 237 | 238 | Some implementations of PWAs, e.g. Chrome and Edge, show a three-dot menu 239 | button. This gives users access to capabilities like for example extensions, 240 | copy URL, open in browser, zoom, print, find, cast, cut, copy, paste etc 241 | depending on the browser. 242 | 243 | User agents in borderless mode will want to find some other place to put these, 244 | or developers need to understand that they won't have all those options. Note 245 | that other platforms (like Android) have already solved this by moving some of 246 | the settings into the OS notifications tray, so this is not unprecedented. Also 247 | many of them are still available using keyboard shorcuts. 248 | 249 | Another way to get the three-dots menu working would be that it could be part of 250 | the AWC feature, but that discussion should rather be part of the AWC feature. 251 | This could also be something considered to implement later on if seen necessary. 252 | 253 | ## Demo 254 | 255 | Demo app: 256 | [webpack.swbn](https://github.com/sonkkeli/borderless/blob/main/webpack.swbn) 257 | 258 | Demo app’s full code: 259 | [github.com/sonkkeli/borderless/demo-app/](https://github.com/sonkkeli/borderless/demo-app/) 260 | 261 | The implementation details presented here are the ones considered most important 262 | to be able to create **a simple MVP using borderless mode**. There can be more 263 | details and functionality in the demo app. Also the details for creating 264 | Isolated Web Apps are still under development and keep changing so one should 265 | find the IWA related implementation details from IWA related documents. 266 | 267 | ### manifest.json 268 | 269 | ``` 270 | { 271 | "name": "Borderless title bar example", 272 | "display": "standalone", 273 | "display_override": ["borderless"] 274 | } 275 | ``` 276 | 277 | ### index.html 278 | 279 | ``` 280 | 281 | 282 | Borderless title bar example 283 | 284 | 285 | 290 | 291 | 292 | 293 |
294 |
295 |
296 |
297 |

Hello world!

298 |
299 | 300 | 301 | ``` 302 | 303 | ### style.css 304 | 305 | The draggable region for the (web content) title bar is set using 306 | `-webkit-app-region: drag;`. 307 | 308 | ``` 309 | @media (display-mode: borderless) { 310 | body { 311 | background: blue; 312 | } 313 | } 314 | 315 | #titlebar { 316 | display: block; 317 | position: fixed; 318 | background: #254053; 319 | color: #FFFFFF; 320 | 321 | left: 0; 322 | top: 0; 323 | width: 100%; 324 | height: 33px; 325 | touch-action: none; 326 | } 327 | 328 | #titlebar #drag-region { 329 | width: 100%; 330 | height: 100%; 331 | -webkit-app-region: drag; 332 | } 333 | 334 | #main { 335 | margin-top: 33px; 336 | padding: 20px; 337 | } 338 | ``` 339 | 340 | ## Considered Alternatives 341 | 342 | ### Having a separate permission for borderless 343 | 344 | In order to be able to close the borderless mode, one needs to have the AWC 345 | permission. Then again AWC on its own knows nothing about multiple screens, so 346 | you will not be able to move a window on another screen. If there are multiple 347 | windows on multiple screens, one needs to allow cross-screen movement, 348 | otherwise, the user experience is strange. So AWC needs to know about other 349 | screens and their resolution. In the end, it seems like AWC would be able to 350 | create a new window on any screen with arbitrary size and/or move an existing 351 | window there. This is possible with the 352 | [Window Management permission](https://github.com/w3c/window-management/blob/main/EXPLAINER.md). 353 | 354 | If all (borderless & AWC) would be separate permissions, their management in 355 | SiteSettings would be extremely complicated and bug-prone, because only 356 | enabling some of them might lead to a broken user experience. Also one would 357 | need to explain to the users that they cannot disable some of them and keep the 358 | others. 359 | 360 | ### WCO extension 361 | 362 | Extending WCO by using the existing WCO toggle to enter borderless mode and 363 | something new to exit. 364 | 365 | Suggestions for exiting: 366 | 367 | 1. Right click on the app’s icon → “Show title bar” option there 368 | 2. Hover over the side/top of the app and show settings menu: When moving the 369 | mouse to the very far right or top of the screen, some settings bar would 370 | open up and one would be able to disable the borderless mode. 371 | 3. Holding down ESC → App permissions / settings 372 | 4. Message on re-focus of an app 373 | 374 | #### Reasonings for discarding 375 | 376 | In general exiting differently from entering would be fairly inconsistent and 377 | wouldn’t reutilize the existing capabilities much more than the chosen solution. 378 | The chosen approach was the one preferred by the security & privacy team members 379 | regarding those security aspects combined with the amount of engineering work 380 | required. 381 | 382 | ### New JS APIs 383 | 384 | Entering and existing borderless mode using JS APIs. On top of the manifest’s 385 | display_override `borderless` value and user permission, these APIs would enable 386 | manual toggling of the borderless mode. Calling the APIs in JS could look 387 | something like this: `window.requestBorderless()` and `window.exitBorderless()` 388 | 389 | #### Reasonings for discarding 390 | 391 | This could still be considered as an extension on top of the currently planned 392 | solution if such features would be considered as useful. Currently there’s no 393 | use-case for needing these. 394 | 395 | ## Security Considerations 396 | 397 | ### Spoofing risks 398 | 399 | Giving sites partial control of the title bar leaves room for developers to 400 | spoof content in what was previously a trusted, UA-controlled region. To 401 | mitigate the threat of spoofing, the feature is (at least for now) only targeted 402 | to be available for Isolated Web Apps and users will have to opt-in to the 403 | feature via a window management permission prompt or via admins’ policies. The 404 | title bar can be returned via App settings by revoking the window-management 405 | permission. 406 | 407 | ### Out-of-scope Navigation 408 | 409 | An existing security feature for installed web apps is an indicator of when a 410 | user has left the declared scope of the app. When a user navigates out of scope, 411 | a black bar appears between the title bar and the web content, and it includes 412 | the following information: 413 | 414 | - A close button to allow users to easily navigate back into scope 415 | - A security icon which opens the security info popup when clicked 416 | - The origin and title of the site 417 | - In borderless mode, if a user navigates out-of-scope the web app will revert 418 | to standalone display mode (with a standalone title bar). When the user 419 | navigates back into scope, the standalone title bar will be removed again and 420 | the borderless mode enabled. 421 | 422 | Example sketch of an out-of-scope navigation and reverting to standalone title 423 | bar with custom tab bar stating the current origin: 424 | 425 | ![Example of out of scope navigation layout](./images/out-of-scope-navigation.png) 426 | 427 | ### Opening a popup from a borderless PWA 428 | 429 | - Opening a popup to any other origin → The popup should NOT be in borderless 430 | mode (security risk if the following pop-up app would be malicious). 431 | - Opening a popup to itself (same origin) → The popup opens in borderless mode, 432 | because the app has already got the permission to run in borderless mode. 433 | 434 | ### Iframes 435 | 436 | - Display modes cannot be enabled on embedded pages. 437 | 438 | ## Accessibility 439 | 440 | ### Risks 441 | - Non-draggable windows 442 | - Non-closable windows 443 | - Non-implemented windowing controls 444 | 445 | ### Existing mitigations 446 | 447 | **IWAs**: The main mitigation is that borderless mode will only be available for 448 | high-trust environments (IWAs). Web content itself allows the creation of 449 | accessible websites. We would expect our trusted partners (which will be the 450 | first target group to leverage IWAs) to take advantage of those existing 451 | accessibility capabilities to build accessibile apps. 452 | 453 | **Closing**: Any malicious borderless app can be closed / uninstalled from by 454 | right-clicking the app's icon or another platform-specific place for app 455 | settings. 456 | 457 | **Draggability**: This is actually a wanted feature. 458 | 459 | ### WIP "mitigation" 460 | 461 | Related [AWC](https://github.com/ivansandrk/additional-windowing-controls/blob/main/awc-explainer.md) 462 | feature, when appropriately used will provide the means for building borderless 463 | IWAs, which are as accessible, as the non-borderless counter-parts would be. 464 | 465 | ### Future mitigation ideas, if the scope would increase 466 | 467 | Further steps to mitigate the accessibility concerns should be taken, in case 468 | borderless mode would be concidered to be used outside of IWAs / when IWAs 469 | would become a thing of the open web. Some examples of possible mitigations 470 | could be e.g., 471 | - Limiting borderless mode to a set of appIDs specified by a policy 472 | (enterprice-only). 473 | - Relying on malicious apps being blocked from a potential future app store. 474 | - Making it easier to revoke the permission to be able to escape borderless mode 475 | more easily. This would then be a platform-specific decision on how this would 476 | work. 477 | -------------------------------------------------------------------------------- /display_mode-client-hint.md: -------------------------------------------------------------------------------- 1 | # Client Hint for Display Mode 2 | 3 | Author: [Aaron Gustafson](https://github.com/aarongustafson) 4 | 5 | PR: https://github.com/w3c/manifest/pull/977 6 | 7 | ## Overview 8 | 9 | Developers often need to know details about how their web app is being rendered in order to make content-negotiation decisions. Though this information is accessible via JavaScript, when sent as part of a Request, servers are empowered to do the content-negotiation earlier in the process of setting up the web app, before JavaScript execution takes place. 10 | 11 | ## Proposal 12 | 13 | To provide `display_mode` information, this document suggests adding a `Sec-CH-Display-Mode` Client Hints Token. The value for the `Sec-CH-Display-Mode` header must be a valid `display_mode` value that represents the current display mode of the web application, reflective of both the authored `display_mode` and `display_override` values. 14 | 15 | ### Example 16 | 17 | ```http 18 | Sec-CH-Display-Mode: standalone 19 | ``` 20 | 21 | ## Security and Privacy Considerations 22 | 23 | There are minimal security and privacy concerns with this proposal. This will allow sites to know the current display_mode being used for the PWA, however this information does not constitute PII and it is already exposed through CSS and JavaScript. 24 | 25 | ## Alternatives considered 26 | 27 | Alternative header names were also considered. 28 | 29 | ## Open questions 30 | 31 | 1. -------------------------------------------------------------------------------- /images/borderless-app-with-AWC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/borderless-app-with-AWC.png -------------------------------------------------------------------------------- /images/clumsy-app-with-wco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/clumsy-app-with-wco.png -------------------------------------------------------------------------------- /images/current-chromium-dialogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/current-chromium-dialogs.png -------------------------------------------------------------------------------- /images/out-of-scope-navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/out-of-scope-navigation.png -------------------------------------------------------------------------------- /images/out-of-scope-ux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/out-of-scope-ux.png -------------------------------------------------------------------------------- /images/privacy-indicators-in-shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/privacy-indicators-in-shelf.png -------------------------------------------------------------------------------- /images/scope-privacy-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/manifest-incubations/edc0979f341b7464256e8dac105db77562fc0a5a/images/scope-privacy-info.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manifest Incubations 7 | 8 | 10 | 35 | 36 | 37 |
38 |

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

46 |
47 |
48 |

49 | This is an unofficial proposal. 50 |

51 |
52 |
53 |

54 | display_override member 56 |

57 |

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

65 |

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

78 |

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

84 |
    85 |
  1. [=list/For each=] |candidate_display_mode:DisplayModeType| of 86 | |manifest:ordered map|.[=manifest/display_override=] member: 87 |
      88 |
    1. If display 89 | modes list contains |candidate_display_mode:DisplayModeType|, 90 | return that |candidate_display_mode:DisplayModeType| 91 |
    2. 92 |
    3. If |candidate_display_mode:DisplayModeType| is [=display 93 | mode/window-controls-overlay=] and the user agent supports this, 94 | then return that |candidate_display_mode:DisplayModeType|. 95 |
    4. 96 |
    5. If |candidate_display_mode:DisplayModeType| is [=display 97 | mode/tabbed=] and the user agent supports this, then return that 98 | |candidate_display_mode:DisplayModeType|. 99 |
    6. 100 |
    101 |
  2. 102 |
103 |

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

111 |
112 |

113 | Concepts 114 |

115 |
    116 |
  • 117 | Window controls: 118 | interface elements that the operating system uses consistently 119 | across applications to enable the user to perform certain actions 120 | to control the application. Common actions in the [=window 121 | controls=] include minimize, maximize/restore, and close buttons. 122 |
  • 123 |
124 |
125 |
126 |

127 | Display mode extensions 128 |

129 |

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

134 |
135 |
136 | borderless 137 |
138 |
139 | The web application does not have any host-native title bar or 140 | [=window controls=] visible and with the web contents extended to 141 | the whole title bar area. The app can specify [=draggable region=]s 142 | in the web contents to create a customized title bar. The user 143 | agent may change the title bar state depending on various security 144 | considerations, like an out-of-scope navigation. 145 |
146 |
147 | [=display mode/window-controls-overlay=] 148 |
149 |
150 | tabbed 151 |
152 |
153 | The web application can have multiple [=application contexts=] 154 | combined in a single operating-system-level window. For example, 155 | this could mean the user agent displays a tab strip UI to allow the 156 | user to switch between the application contexts. 157 |
158 |
159 |
160 |

161 | [=manifest/display_override=] usage example 162 |

163 |

164 | The following shows a [=manifest=] that prefers the 165 | minimal-ui display mode over 167 | standalone, but if minimal-ui isn't 168 | supported, falls back to standalone as opposed to 169 | browser. 170 |

171 |
 172 |             {
 173 |               "name": "Recipe Zone",
 174 |               "description": "All of the recipes!",
 175 |               "icons": [{
 176 |                 "src": "icon/hd_hi",
 177 |                 "sizes": "128x128"
 178 |               }],
 179 |               "start_url": "/index.html",
 180 |               "display_override": ["minimal-ui"],
 181 |               "display": "standalone",
 182 |               "theme_color": "yellow",
 183 |               "background_color": "red"
 184 |             }
 185 |           
186 |
187 |
188 |
189 |

190 | Defining draggable 191 | regions 192 |

193 |

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

199 |

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

204 |
    205 |
  • To enable dragging an element, shall be set to `drag`. 206 |
  • 207 |
  • To disable dragging an element, shall be set to `no-drag`. 208 |
  • 209 |
210 |
211 |
212 |
213 |

214 | Extensions to processing the manifest 215 |

216 |

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

225 |
    226 |
  1. [=Process the `tab_strip` member=], passing |json|, |manifest| and 227 | |manifest URL|. 228 |
  2. 229 |
  3. [=Process the `note_taking` member=], passing |json|, |manifest| 230 | and |manifest URL|. 231 |
  4. 232 |
  5. [=Process the `protocol_handlers` member=], passing |json| and 233 | |manifest|. 234 |
  6. 235 |
  7. [=Process the `file_handlers` member=], passing |json|, |manifest| 236 | and |manifest URL|. 237 |
  8. 238 |
  9. [=Process the `related_applications` member=], passing |json| and 239 | |manifest|. 240 |
  10. 241 |
  11. [=Process the `scope_extensions` member=], passing |json| and 242 | |manifest|. 243 |
  12. 244 |
245 |
246 |
247 |

248 | tab_strip member 249 |

250 |

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

257 |
    258 |
  • [=tab_strip/home_tab=] 259 |
  • 260 |
  • [=tab_strip/new_tab_button=] 261 |
  • 262 |
263 |
264 |

265 | home_tab member 266 |

267 |

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

273 |
    274 |
  • [=home_tab/scope_patterns=] 275 |
  • 276 |
277 |

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

282 | 288 |

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

294 |

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

301 |

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

306 |

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

310 |
    311 |
  • the application [=has a home tab=], and 312 |
  • 313 |
  • |url| is [=manifest/within scope=] of the manifest, and 314 |
  • 315 |
  • at least one of the following: 316 |
      317 |
    • |url| [=URL/equals=] the [=start URL=], with 318 | [=URL/equals/exclude fragments=] set to true, or 319 |
    • 320 |
    • Applying [=URL pattern/match=] given any element of the 321 | [=scope_patterns=] member of the [=home_tab=] member of the 322 | [=tab_strip=] member of the [=Document/processed manifest=] and 323 | |url| returns a {{URLPatternResult}}. 324 |
    • 325 |
    326 |
  • 327 |
328 |

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

332 | 355 |
356 |

357 | Every window has a home tab 358 |

359 |

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

367 |
368 |
369 |

370 | Navigations concerning the home tab scope 371 |

372 |

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

377 |
    378 |
  1. Let [=top-level traversable=] |tab:toplevel traversable| be the 379 | result of choosing a navigable with a target of 380 | "_blank" and noopener true. 381 |
  2. 382 |
  3. Instead of [=navigating=] the home-tab traversable, 383 | [=navigate=] |tab| with the same parameters. 384 |
  4. 385 |
  5. [=applied|Apply=] the current [=application manifest=] to 386 | |tab|'s [=top-level browsing context=]. 387 |
  6. 388 |
  7. The user agent SHOULD place |tab| in the same window as the 389 | home-tab navigable. 390 |
  8. 391 |
  9. Focus |tab|. 392 |
  10. 393 |
394 |

395 |

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

402 |
    403 |
  1. Let |hometab:toplevel traversable| be the [=top-level 404 | traversable=] of the [=home tab context=] associated with the 405 | current window. 406 |
  2. 407 |
  3. Instead of [=navigating=] the top-level traversable, 408 | [=navigate=] |hometab| with the same parameters. 409 |
  4. 410 |
  5. Focus |hometab|. 411 |
  6. 412 |
413 | 422 |
423 |
424 |

425 | Home tab invariants 426 |

427 |

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

432 |
    433 |
  • every application window has exactly one [=home tab context=], 434 | and 435 |
  • 436 |
  • every [=home tab context=]'s active document's [=Document/URL=] 437 | is [=within home tab scope=] (unless the document's URL has changed 438 | since it was created), and 439 |
  • 440 |
  • every non-home-tab [=application context=]'s active document's 441 | [=Document/URL=] is [=outside of home tab scope=] (unless the 442 | document's URL has changed since it was created). 443 |
  • 444 |
445 |

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

453 |

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

464 |
465 |
466 |
467 |

468 | new_tab_button member 469 |

470 |

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

476 |
    477 |
  • [=new_tab_button/url=] 478 |
  • 479 |
480 |

481 | The url member 482 | is a string that represents a URL relative to the [=manifest URL=] 483 | that is [=manifest/within scope=] of a [=Document/processed 484 | manifest=]. 485 |

486 |

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

494 | 509 |

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

513 |
    514 |
  1. [=Create a new application context=] in the current window and 515 | focus it. 516 |
  2. 517 |
  3. Navigate it to the value of the [=new_tab_button/url=] member of 518 | [=new_tab_button=]. 519 |
  4. 520 |
521 |
522 |
523 |

524 | Processing the `tab_strip` member 525 |

526 |

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

533 |
    534 |
  1. Set |manifest|["tab_strip"] to a new [=ordered map=]. 535 |
  2. 536 |
  3. If |json|["tab_strip"] does not exist or |json|["tab_strip"] is 537 | not an [=ordered map=]: 538 |
      539 |
    1. Set |manifest|["tab_strip"]["new_tab_button"]["url"] to 540 | |manifest|["start_url"]. 541 |
    2. 542 |
    3. Return. 543 |
    4. 544 |
    545 |
  4. 546 |
  5. [=Process the `home_tab` member=] passing |json|["tab_strip"], 547 | |manifest|["tab_strip"], and |manifest URL|. 548 |
  6. 549 |
  7. [=Process the `new_tab_button` member=] passing 550 | |json|["tab_strip"], |manifest|["tab_strip"], |manifest URL|, and 551 | |manifest|["start_url"]. 552 |
  8. 553 |
554 |
555 |

556 | Processing the `home_tab` member 557 |

558 |

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

564 |
    565 |
  1. If |json tab strip|["home_tab"] does not exist or |json tab 566 | strip|["home_tab"] not an [=ordered map=], return. 567 |
  2. 568 |
  3. Let |home tab:ordered map| be a new [=ordered map=]. 569 |
  4. 570 |
  5. [=Process the `scope_patterns` member=] passing |json tab 571 | strip|["home_tab"]["scope_patterns"], |home tab| and |manifest 572 | URL|. 573 |
  6. 574 |
  7. Set |manifest tab strip|["home_tab"] to |home tab|. 575 |
  8. 576 |
577 |
578 |
579 |

580 | Processing the `new_tab_button` member 581 |

582 |

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

588 |
    589 |
  1. Set |manifest tab strip|["new_tab_button"]["url"] to |start 590 | URL|. 591 |
  2. 592 |
  3. If |json tab strip|["new_tab_button"] does not exist or |json 593 | tab strip|["new_tab_button"] is not an [=ordered map=], return. 594 |
  4. 595 |
  5. Let |url:URL| be the result of [=URL Parser|parsing=] |json tab 596 | strip|["new_tab_button"]["url"] with |manifest URL| as the base 597 | URL. 598 |
  6. 599 |
  7. If |url| is failure, return. 600 |
  8. 601 |
  9. If |url| is not [=URL/within scope=] of |manifest URL|, return. 602 |
  10. 603 |
  11. Set |manifest tab strip|["new_tab_button"]["url"] to |url|. 604 |
  12. 605 |
606 |
607 |
608 |

609 | Processing the `scope_patterns` member 610 |

611 |

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

616 |
    617 |
  1. Let |processed scope patterns:list| be a new [=list=]. 618 |
  2. 619 |
  3. Set |manifest home tab|["scope_patterns"] to |processed scope 620 | patterns|. 621 |
  4. 622 |
  5. If |json home tab|["scope_patterns"] doesn't exist or |json 623 | home tab|["scope_patterns"] is not a [=list=], return. 624 |
  6. 625 |
  7. For each |entry:URLPatternInit| of |json home 626 | tab|["scope_patterns"]: 627 |
      628 |
    1. Let |pattern:URL pattern| be the result of [=build a URL 629 | pattern from an infra value|building a URL pattern from an 630 | infra value=] |entry| given |manifest URL|. If this process 631 | throws or returns null, continue. 632 |
    2. 633 |
    3. Append |pattern| to |processed scope patterns|. 634 |
    4. 635 |
    636 |
  8. 637 |
638 |

639 |
640 |
641 |
642 |

643 | Usage Example 644 |

645 |
 646 |         {
 647 |           "name": "Tabbed App Example",
 648 |           "start_url": "/",
 649 |           "display": "standalone",
 650 |           "display_override": ["tabbed"],
 651 |           "tab_strip": {
 652 |             "home_tab": {
 653 |               "scope_patterns": [
 654 |                 {"pathname": "/"},
 655 |                 {"pathname": "/index.html"}
 656 |               ]
 657 |             },
 658 |             "new_tab_button": {
 659 |               "url": "/create"
 660 |             }
 661 |           }
 662 |         }
 663 |         
664 |

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

671 |

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

680 |
681 |
682 |
683 |

684 | `share_target` member 685 |

686 |

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

692 |
693 |
694 |

695 | note_taking member 696 |

697 |

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

703 |
    704 |
  • [=note_taking/new_note_url=] 705 |
  • 706 |
707 |

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

712 |
713 |

714 | new_note_url 715 | member 716 |

717 |

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

724 |

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

730 | 737 |
738 |
739 |

740 | Usage Example 741 |

742 |

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

745 |
 746 |           {
 747 |             "name": "My Note Taking App",
 748 |             "description": "You can take notes!",
 749 |             "icons": [{
 750 |               "src": "icon/hd_hi",
 751 |               "sizes": "128x128"
 752 |             }],
 753 |             "start_url": "/index.html",
 754 |             "display": "standalone",
 755 |             "note_taking": {
 756 |               "new_note_url": "/new_note.html"
 757 |             }
 758 |           }
 759 |         
760 |
761 |
762 |

763 | Processing the `note_taking` member 764 |

765 |

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

772 |
    773 |
  1. If |json|["note_taking"] does not [=map/exist=], return. 774 |
  2. 775 |
  3. If the type of |json|["note_taking"] is not [=ordered map=], 776 | return. 777 |
  4. 778 |
  5. Set |manifest|["note_taking"] to a new [=ordered map=]. 779 |
  6. 780 |
  7. [=process the `new_note_url` member=] passing 781 | |json|["note_taking"], |manifest|["note_taking"], and |manifest URL|. 782 |
  8. 783 |
784 |
785 |
786 |

787 | Processing the `new_note_url` member 788 |

789 |

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

795 |
    796 |
  1. If |json_note_taking|["new_note_url"] does not [=map/exist=], 797 | return. 798 |
  2. 799 |
  3. If the type of |json_note_taking|["new_note_url"] is not 800 | [=string=], return. 801 |
  4. 802 |
  5. Let |new_note_url:URL| be the result of [=URL Parser|parsing=] 803 | |json_note_taking|["new_note_url"] with |manifest URL| as the base 804 | URL. 805 |
  6. 806 |
  7. If |new_note_url| is failure, return. 807 |
  8. 808 |
  9. If |new_note_url| is not [=manifest/within scope=] of |manifest 809 | URL|, return. 810 |
  10. 811 |
  11. Set manifest_note_taking["new_note_url"] to |new_note_url|. 812 |
  12. 813 |
814 |
815 |
816 |

817 | Launching the `new_note_url` 818 |

819 |

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

824 |
    825 |
  1. If |manifest|["note_taking"] does not [=map/exist=], return. 826 |
  2. 827 |
  3. If |manifest|["note_taking"]["new_note_url"] does not 828 | [=map/exist=], return. 829 |
  4. 830 |
  5. If the type of |manifest|["note_taking"]["new_note_url"] is not 831 | [=URL=], return. 832 |
  6. 833 |
  7. Run the steps to [=launch a web application=] setting |manifest| 834 | to |manifest| and |target URL| to 835 | |manifest|["note_taking"]["new_note_url"]. 836 |
  8. 837 |
838 |

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

843 |
844 |
845 |
846 |

847 | `protocol_handlers` member 848 |

849 |

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

855 |

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

859 |
    860 |
  • If there are multiple registered handlers for a protocol, the OS 861 | allows the user to select which app should open it, and also allows the 862 | user to set a default app. 863 |
  • 864 |
  • Clicking on a registered protocol will launch the registered 865 | application. If this is the web app, then execute the [=invoke a 866 | protocol handler=] steps defined in [=HTML=], where the user agent 867 | SHOULD navigate to [=url=] and the appropriate browsing context is set 868 | to a new top level browsing context. 869 |
  • 870 |
871 | 876 |
877 |

878 | Processing the `protocol_handlers` member 879 |

880 |

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

884 |
    885 |
  1. Let |processedProtocolHandlers| be a new [=list=] of 886 | |json:JSON|["|protocol_handlers|"]. 887 |
  2. 888 |
  3. Set manifest["|protocol_handlers|"] to 889 | |processedProtocolHandlers|. 890 |
  4. 891 |
  5. [=list/For each=] |protocol_handler| (protocol handler 892 | description): 893 |
      894 |
    1. If |protocol_handler|["protocol"] or 895 | |protocol_handler|["url"] is undefined, [=iteration/continue=]. 896 |
    2. 897 |
    3. Let (|normalizedProtocol:string|, |normalizedUrl:URL|) be the 898 | result of running [=normalize protocol handler parameters=] with 899 | |protocol_handler|["protocol"], | protocol_handler|["url"] using 900 | |manifest URL| as the base URL, and [=this=]'s relevant 901 | [=environment settings object=]. If the result is failure, 902 | [=iteration/continue=]. 903 |
    4. 904 |
    5. If |normalizedUrl| is not [=manifest/within scope=] of 905 | |manifest|, [=iteration/continue=]. 906 |
    6. 907 |
    7. If |processedProtocolHandlers| [=list/contains=] the 908 | |normalizedUrl|, [=iteration/continue=]. 909 |
    8. 910 |
    9. [=List/Append=] |protocol_handler| to 911 | |processedProtocolHandlers|. 912 |
    10. 913 |
    914 |
  6. 915 |
  7. [=list/For each=] |processedProtocolHandlers|, the user agent 916 | SHOULD [=register a protocol handler=]. 917 |
  8. 918 |
919 |

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

927 | 961 |
962 |
963 |

964 | Protocol handler items 965 |

966 |

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

972 |
    973 |
  • [=protocol handler description/protocol=] 974 |
  • 975 |
  • [=protocol handler description/url=] 976 |
  • 977 |
978 |

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

984 |

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

994 |
995 |

996 | `protocol` member 997 |

998 |

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

1005 |

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

1011 |
1012 |
1013 |

1014 | `url` member 1015 |

1016 |

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

1023 |

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

1029 |
1030 |
1031 |

1032 | Handling a protocol launch 1033 |

1034 |

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

1042 |

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

1047 |
1048 |
1049 |
1050 |

1051 | Privacy consideration: Default protocol handler 1052 |

1053 |

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

1060 |
    1061 |
  • Requiring explicit user consent before executing the process to 1062 | [=invoke a protocol handler=]. 1063 |
  • 1064 |
  • Removing the web application's OS registration as a protocol 1065 | handling entity, either in response to the above dialog or by user 1066 | action. 1067 |
  • 1068 |
  • Confirming protocol handler registrations on [=installation=]. 1069 |
  • 1070 |
1071 |

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

1076 |
1077 |
1078 |
1079 |

1080 | `file_handlers` member 1081 |

1082 |

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

1088 |

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

1092 |

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

1097 |
    1098 |
  1. Let |processedFileHandlers:list| be a new [=list=]. 1099 |
  2. 1100 |
  3. Set |manifest|["file_handlers"] to |processedFileHandlers|. 1101 |
  4. 1102 |
  5. If |json|["file_handlers"] doesn't [=map/exist=] or 1103 | |json|["file_handlers"] is not a [=list=], return. 1104 |
  6. 1105 |
  7. [=list/For each=] |entry:ordered map| of |json|["file_handlers"]: 1106 |
      1107 |
    1. Let |file_handler:ordered map| be [=process a file handler 1108 | item=] with |entry|, [=manifest/start_url=], [=manifest/scope=], 1109 | and |manifest_url|. 1110 |
    2. 1111 |
    3. If |file_handler| is failure, [=iteration/continue=]. 1112 |
    4. 1113 |
    5. [=list/Append=] |file_handler| to |processedFileHandlers|. 1114 |
    6. 1115 |
    1116 |
  8. 1117 |
1118 |

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

1122 |
1123 |

1124 | File Handler Items 1125 |

1126 |

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

1131 |
    1132 |
  • [=file handler/action=] 1133 |
  • 1134 |
  • [=file handler/name=] 1135 |
  • 1136 |
  • [=file handler/accept=] 1137 |
  • 1138 |
  • [=file handler/icons=] 1139 |
  • 1140 |
1141 |

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

1145 |

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

1155 |
1156 |

1157 | `action` member 1158 |

1159 |

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

1169 |
1170 |
1171 |

1172 | `name` member 1173 |

1174 |

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

1181 | 1190 |
1191 |
1192 |

1193 | `icons` member 1194 |

1195 |

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

1202 | 1212 |
1213 |
1214 |

1215 | `accept` member 1216 |

1217 |

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

1222 |

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

1226 |

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

1232 |

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

1238 | 1245 |
1246 |
1247 |

1248 | `launch_type` member 1249 |

1250 |

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

1258 |

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

1267 | 1282 |
1283 |
1284 |
1285 |

1286 | Processing file handler items 1287 |

1288 |

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

1293 |
    1294 |
  1. Return failure if any of the following is true: 1295 |
      1296 |
    • |item|["action"] doesn't [=map/exist=] or is not a 1297 | [=string=]. 1298 |
    • 1299 |
    • |item|["accept"] doesn't [=map/exist=]. 1300 |
    • 1301 |
    • |item|["accept"] is not a [=dictionary=]. 1302 |
    • 1303 |
    • |item|["accept"] [=map/is empty=]. 1304 |
    • 1305 |
    1306 |
  2. 1307 |
  3. Let |url:URL| be the result of [=URL Parser|parsing=] 1308 | |item|["action"] with |manifest_url URL| as the base URL. 1309 |
  4. 1310 |
  5. If |url| is failure, return failure. 1311 |
  6. 1312 |
  7. If |url| is not [=manifest/within scope=] of |scope|, return 1313 | failure. 1314 |
  8. 1315 |
  9. Let |launch_type:string| be a new [=string=] initialized to 1316 | "single-client". 1317 |
  10. 1318 |
  11. If |item|["launch_type"] [=map/exists=] and is 1319 | "multiple-clients", set |launch_type| to |item|["launch_type"]. 1320 |
  12. 1321 |
  13. Let |accept:ordered map| be a new [=ordered map=]. 1322 |
  14. 1323 |
  15. [=map/for each=] |mime_type_string:string| → |extensions| of 1324 | |item|["accept"] 1325 |
      1326 |
    1. If |extensions| is not a [=list=], [=iteration/continue=]. 1327 |
    2. 1328 |
    3. If |extensions| is [=list/empty=], [=iteration/continue=]. 1329 |
    4. 1330 |
    5. If |extensions| [=list/contains=] items that are not 1331 | [=string=]s, [=iteration/continue=]. 1332 |
    6. 1333 |
    7. If |extensions| [=list/contains=] strings that do not begin 1334 | with the character `.`, [=iteration/continue=]. 1335 |
    8. 1336 |
    9. If |extensions| [=list/contains=] strings that are greater 1337 | than 16 characters long, [=iteration/continue=]. 1338 |
    10. 1339 |
    11. Let |mime_type_parsed:mime type| be the result of running the 1340 | steps of [=parse a mime type=] on |mime_type_string|. 1341 |
    12. 1342 |
    13. If |mime_type_parsed:mime type| is failure, 1343 | [=iteration/continue=]. 1344 |
    14. 1345 |
    15. If |mime_type_parsed/type| is not listed as a top-level type 1346 | in [[IANA-MEDIA-TYPES]], [=iteration/continue=]. 1347 |
    16. 1348 |
    17. Set |accept|[|mime_type_string|] to |extensions|. 1349 |
    18. 1350 |
    1351 |
  16. 1352 |
  17. If |accept:ordered map| is empty, return failure. 1353 |
  18. 1354 |
  19. Let |file_handler:ordered map| be |ordered map| «[ "action" → 1355 | |url|, "name" → |item|["name"], "launch_type" → |launch_type|, 1356 | "accept" → |accept| ]». 1357 |
  20. 1358 |
  21. 1359 | Process 1360 | image resources passing |item|["icons"], |file_handler|, 1361 | |manifest URL|, and "icons". 1362 |
  22. 1363 |
  23. Return |file_handler|. 1364 |
  24. 1365 |
1366 |
1367 |
1368 |

1369 | Execute a file handler launch 1370 |

1371 |

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

1377 |
    1378 |
  1. Let |file_handlers:list| be |manifest|["file_handlers"]. 1379 |
  2. 1380 |
  3. If |file_handlers:list| is null, return. 1381 |
  4. 1382 |
  5. Let |launches:ordered map| be an [=ordered map=]. 1383 |
  6. 1384 |
  7. [=list/for each=] |filename:string| of |files| 1385 |
      1386 |
    1. [=list/for each=] |file_handler:ordered_map| of 1387 | |file_handlers:list|: 1388 |
        1389 |
      1. [=map/for each=] |mime_type_string:string| → 1390 | |extensions:list| of |file_handler|["accept"] 1391 |
          1392 |
        1. [=list/for each=] |extension:string| of |extensions|: 1393 |
            1394 |
          1. If |filename| does not end in |extension|, 1395 | [=iteration/continue=]. 1396 |
          2. 1397 |
          3. If |launches|[|file_handler|] [=map/exists=], 1398 | [=list/append=] |filename| to 1399 | |launches|[|file_handler|]. 1400 |
          4. 1401 |
          5. Else, set |launches|[|file_handler|] to a 1402 | [=list=] with the single element |filename|. 1403 |
          6. 1404 |
          7. [=iteration/Continue=] to next element of 1405 | |files|. 1406 |
          8. 1407 |
          1408 |
        2. 1409 |
        1410 |
      2. 1411 |
      1412 |
    2. 1413 |
    1414 |
  8. 1415 |
  9. [=map/for each=] |file_handler| → |files:list| of |launches| 1416 |
      1417 |
    1. If |file_handler|["launch_type"] is equal to 1418 | "multiple-clients" 1419 |
        1420 |
      1. [=list/for each=] |file| of |files| 1421 |
          1422 |
        1. Let |params:LaunchParams| be a new {{LaunchParams}} 1423 | with {{LaunchParams/files}} set to a {{FrozenArray}} with 1424 | a single element that is a {{FileSystemHandle}} 1425 | corresponding to |file|. 1426 |
        2. 1427 |
        3. [=Launch a web application with handling=], passing 1428 | |manifest| and |params|. 1429 |
        4. 1430 |
        1431 |
      2. 1432 |
      1433 |
    2. 1434 |
    3. Else, 1435 |
        1436 |
      1. Let |params:LaunchParams| be a new {{LaunchParams}} with 1437 | {{LaunchParams/files}} set to a {{FrozenArray}} of 1438 | {{FileSystemHandle}}s that correspond to the file paths named 1439 | by |files|. 1440 |
      2. 1441 |
      3. [=Launch a web application with handling=], passing 1442 | |manifest| and |params|. 1443 |
      4. 1444 |
      1445 |
    4. 1446 |
    1447 |
  10. 1448 |
1449 |
1450 |
1451 |

1452 | Registering file handlers 1453 |

1454 |

1455 | A user agent SHOULD register file handlers with the host 1456 | operating system, consistent with: 1457 |

1458 |
    1459 |
  • The app is exposed in appropriate OS surfaces such as the native 1460 | file browser and other surfaces that display lists of apps that can 1461 | handle a given file or file type. 1462 |
  • 1463 |
  • The app is a candidate for becoming the system's default handler 1464 | for associated file types. 1465 |
  • 1466 |
1467 |

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

1472 |
1473 |
1474 |

1475 | Privacy consideration: Default file handler. 1476 |

1477 |

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

1488 |
1489 |
1490 |

1491 | Privacy consideration: Name and icon. 1492 |

1493 |

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

1500 |
1501 |
1502 |

1503 | Example manifest with file handlers 1504 |

1505 |

1506 | In the following example, the web application has 3 different file 1507 | handlers. 1508 |

1509 | 1547 |
1548 |
1549 |
1550 |

1551 | `related_applications` member 1552 |

1553 |

1554 | A related application is an application accessible to the 1555 | underlying application platform that has a relationship with the web 1556 | application. 1557 |

1558 |

1559 | The [=manifest's=] related_applications member lists related 1561 | applications and serves as an indication of such a relationship 1562 | between web application and related applications. This 1563 | relationship is unidirectional and unless a listed application claims 1564 | the same relationship, the user agent MUST NOT assume a 1565 | bi-directional endorsement. 1566 |

1567 |

1568 | Example of usages of the `related_applications` could be a crawler that 1569 | would use that information to gather more information about the web 1570 | application or a browser that could suggest a listed application as an 1571 | alternative if the user wants to install the web application. 1572 |

1573 |

1574 | To process the `related_applications` member, given 1575 | [=ordered map=] |json:ordered map| and [=ordered map=] 1576 | |manifest:ordered map|: 1577 |

1578 |
    1579 |
  1. Let |relatedApplications:list| be a new [=list=]. 1580 |
  2. 1581 |
  3. Set |manifest|["related_applications"] to |relatedApplications|. 1582 |
  4. 1583 |
  5. If |json|["related_applications"] doesn't [=map/exist=] or 1584 | |json|["related_applications"] is not a [=list=], return. 1585 |
  6. 1586 |
  7. [=list/For each=] app of |json|["related_applications"]: 1587 |
      1588 |
    1. If neither app["id"] nor app["url"] are 1589 | missing: 1590 |
        1591 |
      1. Set app["url"] to the result of running 1592 | process the `url` member of an application given 1593 | app["url"]. 1594 |
      2. 1595 |
      3. [=list/append=] app to 1596 | relatedApplications. 1597 |
      4. 1598 |
      1599 |
    2. 1600 |
    1601 |
  8. 1602 |
  9. Set relatedApplications. 1603 |
  10. 1604 |
1605 |
1606 |
1607 |

1608 | `prefer_related_applications` member 1609 |

1610 |

1611 | The [=manifest's=] `prefer_related_applications` member is a 1613 | [=boolean=] that is used as a hint for the user agent to say that 1614 | related applications should be preferred over the web 1615 | application. If the `prefer_related_applications` member is set to 1616 | `true`, and the user agent wants to suggest to install the web 1617 | application, the user agent might want to suggest installing one of the 1618 | related applications instead. 1619 |

1620 |
1621 |
1622 |

1623 | `scope_extensions` member 1624 |

1625 |

1626 | The [=manifest's=] 1627 | scope_extensions member represents a list of an 1628 | application's desired [=scope extensions=]. 1629 |

1630 |

1631 | A scope extension extends the [=manifest/navigation scope of 1632 | a manifest=] by describing additional [=URLs=] that should be treated as 1633 | being [=within extended scope=]. 1634 |

1635 |

1636 | The user agent MUST [=process the scope_extensions member=] and 1637 | [=validate scope extensions=] before it can [=apply extended scope=] 1638 | to allow additional [=URLs=] to be [=within extended scope=]. 1639 |

1640 |

1641 | A [=URL=] |target:URL| is within extended scope of a 1642 | |manifest:processed manifest| if the |target| is [=manifest/within 1643 | scope=] of the manifest or [=matches a validated scope extension=]. 1644 |

1645 |

1646 | A [=URL=] |target:URL| matches a validated scope extension if 1647 | the following algorithm returns `true`, given [=URL=] |target:URL| and 1648 | [=list=] |validated_scope_extensions:list|: 1649 |

1650 |
    1651 |
  1. [=list/For each=] |entry:ordered map| of 1652 | |validated_scope_extensions:list|: 1653 |
      1654 |
    1. 1655 | If |entry| is not an [=ordered map=], or |entry|["scope"] does not 1656 | [=map/exist=] or is not a [=URL=], continue.
    2. 1657 |
    3. Let [=URL=] |resolved_scope:URL| be |entry:ordered 1658 | map|["scope"].
    4. 1659 |
    5. If |target| is [=URL/within scope=] of |resolved_scope|, return 1660 | `true`.
    6. 1661 |
    1662 |
  2. Return `false`.
  3. 1663 |
1664 | 1669 |
1670 |

1671 | Usage Example 1672 |

1673 |

1674 | The following example shows a [=manifest=] for an application which 1675 | uses the `scope_extensions` member. 1676 |

1677 |
1679 |           {
1680 |             "id": "https://example.com/app",
1681 |             "name": "My App",
1682 |             "icons": [{
1683 |               "src": "icon/hd_hi",
1684 |               "sizes": "128x128"
1685 |             }],
1686 |             "start_url": "/app/index.html",
1687 |             "scope": "/app",
1688 |             "display": "standalone",
1689 |             "scope_extensions": [
1690 |               { "type": "origin", "origin": "https://example.co.uk" },
1691 |               { "type": "origin", "origin": "https://help.example.com" }]
1692 |           }
1693 |         
1694 |

1695 | The following shows 2 [=web-app-origin-association=] files which can 1696 | be downloaded from the `.well-known` path of the [=origins=] listed 1697 | in the `scope_extensions` member. 1698 |

1699 |
1701 |           {
1702 |             "https://example.com/app": {
1703 |               "scope": "/app"   
1704 |             }
1705 |           }
1706 |         
1707 |
1709 |           {
1710 |             "https://example.com/app": {
1711 |               "scope": "/"   
1712 |             }
1713 |           }
1714 |         
1715 |

1716 | The navigation scope of this app consists of URLs that are 1717 | [=URL/within scope=] of any of these [=URLs=]: 1718 | `https://example.com/app`, `https://example.co.uk/app`, and 1719 | `https://help.example.com`. 1720 |

1721 |
1722 |
1723 |

Processing the `scope_extensions` member

1724 |

1725 | To process the `scope_extensions` member, given [=ordered 1726 | map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, and 1727 | [=URL=] |manifest_id:URL|: 1728 |

1729 |
    1730 |
  1. Let |processedScopeExtensions:list| be a new [=list=]. 1731 |
  2. 1732 |
  3. Set |manifest|["scope_extensions"] to |processedScopeExtensions|. 1733 |
  4. 1734 |
  5. If |json|["scope_extensions"] does not [=map/exist=] or is not a 1735 | [=list=], return. 1736 |
  6. 1737 |
  7. [=list/For each=] |entry:ordered map| of |json|["scope_extensions"]: 1738 |
      1739 |
    1. If |entry|["type"] or |entry|["origin"] do not [=map/exist=], 1740 | [=iteration/continue=]. 1741 |
    2. 1742 |
    3. If |entry|["type"] is not "origin", [=iteration/continue=]. 1743 |
    4. 1744 |
    5. If |entry|["origin"] is not a [=string=], 1745 | [=iteration/continue=]. 1746 |
    6. 1747 |
    7. Let [=URL=] |origin_url:URL| be the result of [=URL 1748 | Parser|parsing=] |entry|["origin"]. 1749 |
    8. 1750 |
    9. If |origin_url| is a valid |URL| with [=URL/scheme=] "https", 1751 | set |entry|["origin"] to the [=URL/origin=] of |origin_url|. 1752 |
    10. Else [=iteration/continue=].
    11. 1753 |
    12. Validate |entry| using the algorithm from [=validate scope 1754 | extensions=], given [=ordered map=] |entry:ordered map| and 1755 | [=URL=] |manifest_id:URL|. 1756 |
    13. 1757 |
    14. If the previous step returned an error, [=iteration/continue=]. 1758 |
    15. 1759 |
    16. 1760 | Else, set |entry|["scope"] to the returned [=URL=]. 1761 |
    17. 1762 |
    18. [=list/Append=] |entry| to |processedScopeExtensions|. 1763 |
    19. 1764 |
    1765 |
  8. 1766 |
1767 | 1772 |
1773 |
1774 |

The web-app-origin-association file

1775 |

A web-app-origin-association file is a JSON file that can 1776 | be used to [=validate scope extensions=]. It confirm an association 1777 | between the origin it is in with one or more web applications. It 1778 | identifies apps uniquely by referencing manifest [=manifest/ids=]. 1779 |

1780 |

1781 | Given [=origin=] |origin:origin|, a [=web-app-origin-association=] 1782 | file is expected to be downloadable from 1783 | [origin]/.well-known/web-app-origin-association. 1784 |

1785 |
1786 |
1787 |

Validating scope extensions

1788 |

1789 | To validate scope extensions, given [=ordered map=] 1790 | |extension:ordered map| and [=URL=] |manifest_id:URL|: 1791 |

1792 |
    1793 |
  1. Let [=URL=] |origin_url:URL| be the [=URL=] of the [=URL/origin=] 1794 | |extension|["origin"].
  2. 1795 |
  3. Let [=URL=] |download_url:URL| be the result of [=URL 1796 | Parser|parsing=] "/.well-known/web-app-origin-association" using 1797 | |origin_url| as the base [=URL=]. 1798 |
  4. 1799 |
  5. Let [=ordered map=] |json:ordered map| be the result of a [=fetch=] 1800 | given |download_url|. 1801 |
  6. 1802 |
  7. If the [=fetch=] failed, return an error. 1803 |
  8. 1804 |
  9. If |json|[|manifest_id|] does not [=map/exist=] or is not an 1805 | [=ordered map=], return an error.
  10. 1806 |
  11. Let [=string=] |scope:string| be "/".
  12. 1807 |
  13. If |json|[|manifest_id|]["scope"] [=map/exists=] and is a 1808 | [=string=], set |scope| to |json|[|manifest_id|]["scope"]. 1809 |
  14. 1810 |
  15. Let [=URL=] |resolved_scope:URL| be the result of [=URL 1811 | Parser|parsing=] |scope| with |extension|["origin"] as the base 1812 | [=URL=]. 1813 |
  16. 1814 |
  17. Return |resolved_scope|.
  18. 1815 |
1816 |
1817 |
1818 |

Applying scope extensions

1819 |

The user agent MAY choose to apply apply extended scope in 1820 | some scenarios and [=manifest/scope=] in others. 1821 |

1822 |

1823 | If the [=application context=]'s [=navigable/active document=]'s [=URL=] 1824 | is not [=manifest/within scope=] but is [=within extended scope=], the 1825 | user agent SHOULD provide UI that allows the user to determine the 1826 | [=URL=] or at least its [=origin=], including whether it is served over 1827 | a secure connection. This UI SHOULD differ from any UI used when the 1828 | [=URL=] is not [=manifest/within scope=] of the [=application 1829 | context=]'s [=Document/processed manifest=], in order to make it obvious 1830 | that the user is still navigating the intended contents of the 1831 | application but also keep the user informed of the privacy and security 1832 | implications of navigating to a different [=origin=]. 1833 |

1834 |
1835 |
1836 |
1837 |

1838 | External application resource 1839 |

1840 |

1841 | Each external 1842 | application resource represents an application related to the web 1843 | application. 1844 |

1845 |

1846 | An [=external application resource=] can have the following members, 1847 | some of which are required to be a [=valid external application 1848 | resource=]: 1849 |

1850 |
    1851 |
  • [=external application resource/fingerprints=] member 1852 |
  • 1853 |
  • [=external application resource/id=] member 1854 |
  • 1855 |
  • [=external application resource/min_version=] member 1856 |
  • 1857 |
  • [=external application resource/platform=] member 1858 |
  • 1859 |
  • [=external application resource/url=] member 1860 |
  • 1861 |
1862 |

1863 | A valid external application resource MUST have [=external 1864 | application resource/platform=] member, and either an [=external 1865 | application resource/url=] or an [=external application resource/id=] 1866 | member (or both). 1867 |

1868 | 1900 |
1901 |

1902 | `platform` member 1903 |

1904 |

1905 | The platform member 1907 | represents the [=platform=] this [=external application resource=] is 1908 | associated with. A platform represents a 1909 | software distribution ecosystem or possibly an operating system. This 1910 | specification does not define the particular values for the 1911 | platform member. 1912 |

1913 | 1918 |
1919 |
1920 |

1921 | `url` member 1922 |

1923 |

1924 | An [=external application resource's=] url member is the 1926 | URL where the application can be found. 1927 |

1928 |

1929 | To process the `url` member of an application: 1930 |

1931 |
    1932 |
  1. If application URL is missing, return null. 1933 |
  2. 1934 |
  3. Otherwise, [=URL Parser|parse=] application URL and if 1935 | the result is not failure, return the result. Otherwise return null. 1936 |
  4. 1937 |
1938 |
1939 |
1940 |

1941 | `id` member 1942 |

1943 |

1944 | An [=external application resource's=] id member represents the 1946 | id which is used to represent the application on the platform. 1947 |

1948 |
1949 |
1950 |

1951 | `min_version` member 1952 |

1953 |

1954 | An [=external application resource's=] min_version member 1956 | represents the minimum version of the application that is considered 1957 | related to this web app. This version is a string with 1958 | platform-specific syntax and semantics. 1959 |

1960 |
1961 |
1962 |

1963 | `fingerprints` member 1964 |

1965 |

1966 | An [=external application resource's=] fingerprints member 1968 | represents an [=list=] of [=fingerprints=]. 1969 |

1970 |

1971 | A fingerprint represents a 1972 | set of cryptographic fingerprints used for verifying the application. 1973 | A fingerprint has the following two members: type and value. Each of these are strings, but 1976 | their syntax and semantics are platform-defined. 1977 |

1978 |
1979 |
1980 |
1981 |

1982 | Installation prompts 1983 |

1984 |

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

1987 |
    1988 |
  • An end-user can manually 1989 | trigger the installation process through the user agent's 1990 | UI, directly invoking the steps 1991 | to present an install prompt. 1992 |
  • 1993 |
  • The installation process can occur through an automated 1994 | install prompt: that is, a UI that the user agent presents to the 1995 | user when, for instance, there are sufficient installability 1996 | signals to warrant installation of the web application. 1997 |
  • 1998 |
  • The installation process can occur through a site-triggered 1999 | install prompt: the site can programmatically request that the 2000 | user agent present an install prompt to the user. The user agent MAY 2001 | restrict the availability of this feature to cases where, for instance, 2002 | there are sufficient signals to warrant [=installed web 2003 | application|installation=] of the web application. 2004 |
  • 2005 |
2006 |

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

2010 |

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

2017 |

2018 | To present 2020 | an install prompt: 2021 |

2022 |
    2023 |
  1. Show some user-agent-specific UI, asking the user whether to 2024 | proceed with installing the app. The result of this choice 2025 | is either {{AppBannerPromptOutcome/"accepted"}} or 2026 | {{AppBannerPromptOutcome/"dismissed"}}. 2027 |
  2. 2028 |
  3. Return result, and in parallel: 2029 |
      2030 |
    1. If result is {{AppBannerPromptOutcome/"accepted"}}, 2031 | run the steps to install the web application. 2032 |
    2. 2033 |
    2034 |
  4. 2035 |
2036 |

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

2040 |
    2041 |
  1. Wait until the {{Document}} of the top-level browsing 2042 | context is completely 2043 | loaded. 2044 |
  2. 2045 |
  3. If there is already an install prompt being presented or if 2047 | the steps to install the web application are currently being 2048 | executed, then abort this step. 2049 |
  4. 2050 |
  5. 2051 | Queue a task on the application life-cycle task source 2052 | to do the following: 2053 |
      2054 |
    1. Let |mayShowPrompt| be the result of [=fire an event=] named 2055 | `"beforeinstallprompt"` at the [=top-level browsing context=]'s 2056 | [=relevant global object=] using the {{BeforeInstallPromptEvent}} 2057 | interface, with steps to initialize the {{Event/cancelable}} 2058 | attribute to `true`. 2059 |
    2. 2060 |
    3. If |mayShowPrompt| is true, then the user agent MAY, in 2061 | parallel, request to present an install prompt with 2062 | |event|. 2063 |
    4. 2064 |
    2065 |
  6. 2066 |
2067 |
2068 |
2069 |

2070 | Installable web applications 2071 |

2072 |
2073 |

2074 | Installation process 2075 |

2076 |

2077 | The steps to install the web application are given by the 2078 | following algorithm: 2079 |

2080 |
    2081 |
  1. Let manifest be the manifest of an installable 2082 | document. 2083 |
  2. 2084 |
  3. Perform an unspecified sequence of actions to attempt to register 2085 | the web application in the user's operating system (e.g., create 2086 | shortcuts that launch the web application, register the application 2087 | in the system uninstall menu, etc.). If the installation fails (which 2088 | can be for any reason, for example, the OS denying permission to the 2089 | user agent to add an icon to the home screen of the device), abort 2090 | these steps. 2091 |
  4. 2092 |
  5. 2093 | Queue a task on the application life-cycle task 2094 | source to fire an event named `"appinstalled"` at the 2095 | [=top-level browsing context=]'s [=relevant global object=] for 2096 | which the installation took place. 2097 |
  6. 2098 |
2099 |
2100 |
2101 |

2102 | Installability signals 2103 |

2104 |

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

2115 |

2116 | Examples of possible installability signals for a web 2117 | application that a user agent might implement: 2118 |

2119 |
    2120 |
  • has a [=application manifest=] with an application name and a 2121 | suitable icon. 2122 |
  • 2123 |
  • is served over a secure network connection, but also functions 2124 | without a network connection. 2125 |
  • 2126 |
  • has a sensible content security policy. 2127 |
  • 2128 |
  • is able to responsively adapt to display on a variety of screen 2129 | sizes, catering for both mobile and desktop. 2130 |
  • 2131 |
  • shows a high degree of end-user engagement over an extended 2132 | period of time. 2133 |
  • 2134 |
  • has been explicitly marked by the user as one that they value and 2135 | trust (e.g., by bookmarking or "starring" it). 2136 |
  • 2137 |
2138 |

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

2144 |
2145 |
2146 |
2147 |

2148 | Installation Events 2149 |

2150 |

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

2154 |
2155 |

2156 | BeforeInstallPromptEvent Interface 2157 |

2158 |
2159 | The beforeinstallprompt event is somewhat misnamed, as it does 2160 | not necessarily signal that a manual installation will follow 2161 | (depending on the user agent, it might just be giving the site the 2162 | ability to trigger an install prompt). It is so named for historical 2163 | reasons. 2164 |
2165 |
2166 |           [Exposed=Window]
2167 |           interface BeforeInstallPromptEvent : Event {
2168 |             constructor(DOMString type, optional EventInit eventInitDict = {});
2169 |             Promise<PromptResponseObject> prompt();
2170 |           };
2171 | 
2172 |           dictionary PromptResponseObject {
2173 |             AppBannerPromptOutcome userChoice;
2174 |           };
2175 | 
2176 |           enum AppBannerPromptOutcome {
2177 |             "accepted",
2178 |             "dismissed"
2179 |           };
2180 |         
2181 |

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

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

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

2203 |

2204 | An instance of a {{BeforeInstallPromptEvent}} has the following 2205 | internal slots: 2206 |

2207 |
2208 |
2209 | [[\didPrompt]] 2210 |
2211 |
2212 | A boolean, initially `false`. Represents whether this event was 2213 | used to present an install prompt to the end-user. 2214 |
2215 |
2216 | [[\userResponsePromise]] 2217 |
2218 |
2219 | A promise that represents the outcome of presenting an install 2220 | prompt. 2221 |
2222 |
2223 |
2224 |

2225 | prompt() method 2226 |

2227 |

2228 | The prompt method, when called, runs the following 2229 | steps: 2230 |

2231 |
    2232 |
  1. Let |userResponsePromise| be 2233 | {{BeforeInstallPromptEvent/[[userResponsePromise]]}}. 2234 |
  2. 2235 |
  3. If |userResponsePromise| is pending: 2236 |
      2237 |
    1. If [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} is 2238 | `true`, terminate this algorithm. 2239 |
    2. 2240 |
    3. If this event's {{Event/isTrusted}} attribute is `false`, 2241 | reject |userResponsePromise| with {{"NotAllowedError"}} and 2242 | terminate this algorithm. 2243 |
    4. 2244 |
    5. Set [=this=].{{BeforeInstallPromptEvent/[[didPrompt]]}} to 2245 | `true`. 2246 |
    6. 2247 |
    7. 2248 | In parallel, request to present an install 2249 | prompt with [=this=]. Wait, possibly indefinitely, for 2250 | the end-user to make a choice. 2251 |
    8. 2252 |
    2253 |
  4. 2254 |
  5. Return |userResponsePromise|. 2255 |
  6. 2256 |
2257 |

2258 | To request to present an install prompt 2259 | with {{BeforeInstallPromptEvent}} event: 2260 |

2261 |
    2262 |
  1. 2263 | Present an install prompt and let |outcome| be the result. 2264 |
  2. 2265 |
  3. Let |response| be a newly created {{PromptResponseObject}}, 2266 | initializing its {{PromptResponseObject/userChoice}} to |outcome|. 2267 |
  4. 2268 |
  5. [=Resolve=] 2269 | |event|.{{BeforeInstallPromptEvent/[[userResponsePromise]]}} with 2270 | |response|. 2271 |
  6. 2272 |
2273 |
2274 |
2275 |

2276 | Usage example 2277 |

2278 |

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

2286 |
2288 |               window.addEventListener("beforeinstallprompt", event => {
2289 |                 // Suppress automatic prompting.
2290 |                 event.preventDefault();
2291 | 
2292 |                 // Show the (disabled-by-default) install button. This button
2293 |                 // resolves the installButtonClicked promise when clicked.
2294 |                 installButton.disabled = false;
2295 | 
2296 |                 // Wait for the user to click the button.
2297 |                 installButton.addEventListener("click", async e => {
2298 |                   // The prompt() method can only be used once.
2299 |                   installButton.disabled = true;
2300 | 
2301 |                   // Show the prompt.
2302 |                   const userChoice = await event.prompt();
2303 |                   console.info(`user choice was: ${userChoice}`);
2304 |                 });
2305 |               });
2306 |             
2307 |
2308 |
2309 |

2310 | AppBannerPromptOutcome enum 2311 |

2312 |

2313 | The AppBannerPromptOutcome enum's values represent the 2314 | outcomes from presenting an install prompt. 2315 |

2316 |
2317 |
2318 | "accepted": 2319 |
2320 |
2321 | The end-user indicated that they would like the user agent to 2322 | install the web application. 2323 |
2324 |
2325 | "dismissed": 2326 |
2327 |
2328 | The end-user dismissed the install prompt. 2329 |
2330 |
2331 |
2332 |
2333 |
2334 |

2335 | Extensions to the `Window` object 2336 |

2337 |
2338 |           partial interface Window {
2339 |             attribute EventHandler onappinstalled;
2340 |             attribute EventHandler onbeforeinstallprompt;
2341 |           };
2342 |         
2343 |
2344 |

2345 | onappinstalled attribute 2346 |

2347 |

2348 | The onappinstalled event handler IDL attribute 2349 | handles "appinstalled" events. 2350 |

2351 |
2352 |
2353 |

2354 | onbeforeinstallprompt attribute 2355 |

2356 |

2357 | The onbeforeinstallprompt event handler IDL 2358 | attribute handles "beforeinstallprompt" events. 2360 |

2361 |
2362 |
2363 |
2364 |
2365 | 2366 | 2367 | -------------------------------------------------------------------------------- /note_taking-explainer.md: -------------------------------------------------------------------------------- 1 | # Note Taking Web Apps 2 | 3 | Author: Glen Robertson (glenrob@chromium.org) 4 | 5 | ## Overview 6 | 7 | Note-taking apps have special integrations in some User Agents and Operating 8 | Systems as a convenient way for the user to take a quick note. Some examples: 9 | 10 | * ["Quick Note" keyboard shortcut on Windows]( 11 | https://support.microsoft.com/en-us/office/create-quick-notes-0f126c7d-1e62-483a-b027-9c31c78dad99) 12 | * ["Note" action center button on Windows]( 13 | https://www.windowscentral.com/how-change-note-button-action-open-other-note-taking-apps-windows-10) 14 | * ["New note" stylus tools button on CrOS]( 15 | https://support.google.com/chromebook/answer/7073299) 16 | * ["Take a note" in Google Assistant on Android]( 17 | https://support.google.com/assistant/answer/9053424) 18 | * ["New Note" touch bar button or "Create a note" with Siri on OSX]( 19 | https://support.apple.com/en-au/guide/notes/not9474646a9/mac) 20 | 21 | However, these integrations currently work only for native apps. Such 22 | integrations could be supported for web apps too, if they had a way to identify 23 | themselves as note-taking apps and a URL to launch for taking new note. It 24 | wouldn't cover all of the use-cases linked above (eg. voice assistant actually 25 | adding items to the note) but it covers the core & common flow of adding a new 26 | note. 27 | 28 | This explainer proposes a way for web apps to: 29 | * declare themselves as note-taking apps 30 | * declare a URL to launch for taking a new note 31 | 32 | which will allow UAs and OSs to provide convenient integrations with note-taking 33 | web apps, if they choose to do so. 34 | 35 | ### Background 36 | 37 | Other web app APIs set a precedent of using a URL declared in the web app 38 | manifest to advertise capabilities and facilitate integrations with the OS: 39 | 40 | * [Web Share Target](https://w3c.github.io/web-share-target/) 41 | * [File Handling](https://github.com/WICG/file-handling/blob/main/explainer.md) 42 | * [Protocol Handlers](https://web.dev/url-protocol-handler/) 43 | 44 | ## Proposal 45 | 46 | This explainer proposes a new dictionary manifest entry `note_taking` with an 47 | entry `new_note_url` to specify a URL, within app scope, to launch for taking a 48 | new note. 49 | 50 | ``` 51 | { 52 | "name": "My note taking app", 53 | "start_url": "/index.html", 54 | ... other required manifest fields ... 55 | "note_taking": { "new_note_url": "/new_note.html" }, 56 | } 57 | ``` 58 | 59 | The presence of the `note_taking` member signals intent to act as a note-taking 60 | app, and `new_note_url` is a simple note-taking app capability. The url is 61 | launched in the same way as the `start_url`, so is affected by the app's other 62 | properties, such as 63 | [display mode](https://www.w3.org/TR/appmanifest/#display-member) and 64 | [launch handlers]( 65 | https://github.com/WICG/sw-launch/blob/main/launch_handler.md). 66 | 67 | The user agent can choose (or let the user choose) what to do with web apps 68 | identified as note-taking apps. For example, it may surface the apps in its own 69 | UI or integrate those apps with some OS-level functionality for taking notes. If 70 | multiple note-taking apps are identified, it could integrate them all or choose 71 | (or let the user choose) some subset to integrate. 72 | 73 | By using a dictionary we can easily add other note-taking app functionality or 74 | extend the current functionality, and it keeps the manifest organised with 75 | related functionality together. It also means note-taking can be specified 76 | largely orthogonally to other manifest specification changes, which helps to 77 | keep the manifest specification modular and manageable. 78 | 79 | By launching the `new_note_url` using the same launch mechanism as 80 | the `start_url` apps can use a [launch handler]( 81 | https://github.com/WICG/sw-launch/blob/main/launch_handler.md) to 82 | determine what happens when the URL is launched. For example, the app could 83 | choose to receive the launch as an event in an existing app instance, instead of 84 | opening a new instance of the app, and the app could handle the event by showing 85 | a new note in the existing UI. This allows apps a lot of flexibility in 86 | customising their behaviour in response to a launch, without complicating this 87 | API. 88 | 89 | ### Future Considerations 90 | 91 | If the need arises in the future to add more note-taking functionality, this can 92 | easily be added to the `note_taking` member. For example (**not 93 | currently proposed**), if we want to be able to populate a note with contents 94 | from another app or add items to a list from voice input, we could add: 95 | 96 | ``` 97 | { 98 | ... other manifest fields ... 99 | "note_taking": { 100 | "new_note_with_contents": { 101 | "url": "/POST_url/add_note.html", 102 | "params": { 103 | "title": "name", 104 | "text": "description", 105 | "url": "link" 106 | } 107 | }, 108 | "add_item_to_note": { 109 | "action": "/POST_url/add_item_to_note.html", 110 | "params": { 111 | "existing_note_id": "id", 112 | "item_text": "description", 113 | } 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | ## Alternatives Considered 120 | 121 | A common theme of alternatives is to make a generalised interface for web apps 122 | to advertise their capabilities. Ideally this would let us solve a whole class 123 | of problems instead of just one case. For example, a similar mechanism to " 124 | create a new note" could be used to "create new calendar entry" or "add a 125 | contact". 126 | 127 | ### Extend the `Shortcuts` Member 128 | 129 | If we used the existing [Shortcuts manifest member]( 130 | https://www.w3.org/TR/appmanifest/#shortcuts-member), we would need to add a way 131 | for the user agent to identify shortcuts as intended to be used for specific 132 | purposes/tasks (eg. a new optional "purpose" field on each shortcut entry, with 133 | a defined set of values like "new-note", "new-contact", etc). Some of these 134 | future tasks might need additional parameters/context, which would further 135 | extend and complicate the `Shortcuts` member. Additionally, Shortcuts are 136 | intended to be displayed to the user, while many tasks/capabilities should not 137 | be displayed in a shortcuts menu, eg. any parameterised or contextual tasks, 138 | non-task capabilities like [lock screen](https://github.com/WICG/lock-screen) 139 | support. It seems like extending `Shortcuts` this far would be significantly 140 | changing the semantics of the member. 141 | 142 | ### A General Capability Member 143 | 144 | We also considered using a more general-purpose integrations field, where each 145 | individual integration has a particular schema of fields. For example: 146 | 147 | ``` 148 | "integrations": [ 149 | { "task": "new-note", "url": } 150 | { "task": "add-to-existing-note", "url": , } 151 | { "task": "lock-screen", "url": } 152 | { "task": "new-contact", "url": , } 153 | ] 154 | ``` 155 | 156 | Or: 157 | 158 | ``` 159 | "integrations": { 160 | "note-taking": { 161 | "create": { 162 | "action": "/new_note.html", 163 | }, 164 | "add-item": { 165 | "action": "/add_item_to_note.html", 166 | "params": { 167 | "title": "name", 168 | "text": "description", 169 | "url": "link" 170 | } 171 | } 172 | } 173 | } 174 | ``` 175 | 176 | However, these individual integrations likely need to be specified individually 177 | anyway, so we get little benefit from grouping like this. In fact there might be 178 | a drawback to grouping disparate features and tying their specification together 179 | rather than having separate tiny specifications. This "integrations" member is 180 | really serving the same purpose as the root manifest object. 181 | 182 | ### An Event 183 | 184 | Another option would have been to have a note-taking app register an event 185 | handler for a "new note" event. It could potentially have a dictionary of 186 | parameters to allow a high degree of flexibility and added functionality in the 187 | future. 188 | 189 | However, such flexibility actually reduces the ability for the API to declare 190 | capabilities. Unlike an event registration, the proposed New Note URL is known 191 | as soon as the manifest is parsed, and relatively fixed, so it is clear to the 192 | UA when a given app starts/stops self-identifying as a note-taking app capable 193 | of the "new note" action. By contrast, an event handler might only be registered 194 | in certain conditions and provides no assurance of being registered again in the 195 | future. If new functionality is added to the New Note URL in the future, it 196 | could be added with explicit parameters or another URL declared in the manifest, 197 | which again acts as a declaration of capabilities. By contrast for an event with 198 | a dictionary of parameters, the UA cannot know whether the app is capable of 199 | handling new parameters. For example, if adding a text parameter to a "new note" 200 | event's parameters, older apps may ignore this extra parameter and lose some 201 | input text. 202 | 203 | An event also complicates launching. Opening a URL is a natural way to launch a 204 | web app from outside the app (the UA or OS), whereas an event requires that the 205 | app is first launched or is already running. An event allows more customisation 206 | of the app's launch behaviour, but similar customisation is possible with launch 207 | URLs using [launch handlers]( 208 | https://github.com/WICG/sw-launch/blob/main/launch_handler.md). 209 | 210 | ## Security and Privacy Considerations 211 | 212 | This proposal would simply allow a web app to declare an additional URL (within 213 | scope), which the user and/or user agent may choose to navigate to. So there are 214 | minimal security & privacy effects: if the `new_note_url` is launched, then the 215 | site can know that the URL was loaded, so it could be used for fingerprinting in 216 | exactly the same way as the manifest `start_url` already allows. Launching 217 | the `new_note_url` when creating a new note is identical to loading the URL 218 | normally. 219 | -------------------------------------------------------------------------------- /predictable-app-updating-security-privacy-questionnaire.md: -------------------------------------------------------------------------------- 1 | # Self-Review Questionnaire: Security and Privacy 2 | 3 | Security and Privacy questionnaire for [`update-token`](https://github.com/Dp-Goog/manifest-incubations/blob/gh-pages/predictable-app-updating.md) 4 | 5 | ## What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary? 6 | 7 | User agents will be able to know if a manifest update has been applied on an installed PWA or not. 8 | 9 | ## Do features in your specification expose the minimum amount of information necessary to enable their intended uses? 10 | 11 | Yes. 12 | 13 | ## How do the features in your specification deal with personal information, personally-identifiable information (PII), or information derived from them? 14 | 15 | There is no information of this type being handled by the feature. 16 | 17 | ## How do the features in your specification deal with sensitive information? 18 | 19 | User agents will have the option of showing a UX dialog in case security sensitive fields of the app (`name`, `short_name` and `icons`) change. 20 | 21 | ## Do the features in your specification introduce new state for an origin that persists across browsing sessions? 22 | 23 | No. 24 | 25 | ## Do the features in your specification expose information about the underlying platform to origins? 26 | 27 | No. 28 | 29 | ## Does this specification allow an origin to send data to the underlying platform? 30 | 31 | Yes, this will send the information that a site has changed its manifest, and the user agent can choose to apply that update if they want. 32 | 33 | ## Do features in this specification enable access to device sensors? 34 | 35 | No. 36 | 37 | ## Do features in this specification enable new script execution/loading mechanisms? 38 | 39 | No. 40 | 41 | ## Do features in this specification allow an origin to access other devices? 42 | 43 | No. 44 | 45 | ## Do features in this specification allow an origin some measure of control over a user agent’s native UI? 46 | 47 | Yes. User agents can create their own native UI based on whether the field in the manifest has a different value from what's stored or not. 48 | 49 | ## What temporary identifiers do the features in this specification create or expose to the web? 50 | 51 | N/A. 52 | 53 | ## How does this specification distinguish between behavior in first-party and third-party contexts? 54 | 55 | No, it does not make such distinction. 56 | 57 | ## How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode? 58 | 59 | It will not be available in Private Browsing/Incognito mode. 60 | 61 | ## Does this specification have both "Security Considerations" and "Privacy Considerations" sections? 62 | 63 | There is no specification written yet. 64 | 65 | ## Do features in your specification enable origins to downgrade default security protections? 66 | 67 | No. 68 | 69 | ## How does your feature handle non-"fully active" documents? 70 | 71 | This feature does not interact with non-"fully active" documents. 72 | 73 | ## What should this questionnaire have asked? 74 | 75 | N/A. 76 | -------------------------------------------------------------------------------- /predictable-app-updating.md: -------------------------------------------------------------------------------- 1 | # Predictable Web App Updating \- Explainer 2 | 3 | Author: Dibyajyoti Pal (dibyapal@chromium.org) 4 | 5 | # **Introduction** 6 | 7 | This explainer proposes a consistent way for developers to identify when to fully update their PWA in a safe and resourceful manner that is flexible enough to be adapted by different user agents. This involves modifying the [manifest updating spec](https://www.w3.org/TR/appmanifest/#updating) to make update detection more deterministic. The proposal attempts to do so in a way that: 8 | 9 | 1. Ensures lesser usage of network resources by the user agent. 10 | 2. Prevent user confusion by showing the update UX less often. 11 | 12 | Doing so helps further bridge the gap between PWAs and native apps. 13 | 14 | # **Background** 15 | 16 | [PWAs](https://web.dev/explore/progressive-web-apps) are app experiences built on the web, and like native apps, they support updating themselves. Developers have the option of changing any field in the manifest (including the [security sensitive fields](https://www.w3.org/TR/appmanifest/#dfn-security-sensitive-members)), and the user agent can then apply the changes in a way as defined in the [manifest updating spec](https://www.w3.org/TR/appmanifest/#updating). For example, changes to the security sensitive manifest properties **require** the user agent to explicitly get user permission, which is why Chromium shows an update dialog to the user. This helps prevent [phishing risks](#phishing). 17 | 18 | Updates on PWAs are important because they allow: 19 | - Rebranding via icon and name changes. 20 | - Icon changes via changing icon urls in the manifest. 21 | - Minor visual changes in the icon even if the url has stayed the same (due to dynamic re-encoding by CDNs). 22 | 23 | For all these use-cases however, the **detection** of when an update should happen is not clearly defined in the spec, leading to [problems](#chromium-problems). The next session attempts to dive into these problems, and [propose](#proposal) a solution that all user agents can implement without running into the same problems. 24 | 25 | # [**Chromium PWA update detection, and its problems**](#chromium-problems) 26 | 27 | Currently, detecting that a PWA needs an update goes like this: 28 | 29 | - On page load of an url within the [scope of an installed PWA](https://www.w3.org/TR/appmanifest/#scope-member), a manifest update is triggered. 30 | - The manifest, and the resources defined in it, are downloaded and compared with the installed PWA and its resources. 31 | - If there is a difference, an update happens. 32 | - If the difference is in security sensitive fields, like the name, icon or short name, the user agent shows a UX notifying the user that an update is supposed to happen. 33 | - The UX shows the differences between the old and the new sensitive fields, and asks the user to either accept the changes or uninstall the app. 34 | 35 | ![Update dialogs](./images/current-chromium-dialogs.png) 36 | 37 | ## Problem: Developers have no control over when the update dialog may show up 38 | 39 | The dialog shows up whenever Chromium sees the new manifest & detects changes. Developers have to accept that every change to security sensitive members could trigger this. They cannot, for example, make a number of incremental changes, and then trigger one update dialog at the end once they are all done. 40 | 41 | ## Problem: Update check wastes bandwidth, requiring a throttle 42 | 43 | Network resources are wasted by performing icon downloads over and over again just to see if an update is needed. Chromium's current implementation is forced to mitigate this problem by introducing a [throttle](https://web.dev/articles/manifest-updates) to reduce wasted downloads for non-updates. 44 | 45 | This also causes confusion among developers testing manifest updates for their sites, as updates are throttled to once per day. This required addition of a new [flag](https://web.dev/articles/manifest-updates#cr-desktop-test) to bypass the throttle. 46 | 47 | ## Problem: Basic icon diff triggers an update dialog too often 48 | 49 | Sometimes, icons would change in use-cases that wouldn’t really require an intrusive UX dialog to be shown to the user. Some use-cases where that would happen are: 50 | 51 | - Developers making minor changes to icons which would not be visibly noticeable. 52 | - CDNs dynamically re-encoding icons. 53 | 54 | Both these would trigger manifest updates, with the end user seeing no “visible” difference in the update UX, and would be confused about why this was popping up and interrupting their workflow. This led the behavior to be treated as spammy. 55 | 56 | Chrome on Android solved it with a stop gap where PWA updates were automatic if the visual difference between the downloaded icon and the local icon was less than 10%. 57 | 58 | # **Goals** 59 | 60 | The current manifest update process gets the job done, but if it could be [specified properly](https://github.com/w3c/manifest/issues/384) to know when that should happen, it would certainly help developers. As such, the goals to solve are: 61 | 62 | * [**Developer control**](#devc): Support developers to allow them to update their installed experiences when they want to, and keep functional behavior like [file handling](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/file_handlers) consistent across app updates. 63 | * [**Preventing unnecessary user interruption**](#useint): Users should not see an update dialog more than necessary to confirm security-sensitive changes. 64 | * [**Consistency**](#consistency): Provide a consistent way to detect when a manifest update should happen, which can be implemented across all user agents. 65 | * [**User agent flexibility**](#uaf): It should be possible for user agents to use their judgement to block updates for known bad sites, allow known trusted apps to update without UX, or allow tiny visual changes to icons without requiring UX. 66 | * [**Reduce network traffic**](#traffic): Unnecessary network traffic should be minimized. 67 | 68 | # [**Proposal: Perform non-security-sensitive field updates silently, icon updates only when icon url changes, and security-sensitive field updates are optional (or user-agent mediated)**](#proposal) 69 | 70 | The proposal is to: 71 | - Allow the updating of non-security sensitive members silently. 72 | - Allow updating security sensitive members like name with an UX shown by the user agent. 73 | - For icons, only consider an icon updated for a given size if the [icon url in the manifest](https://www.w3.org/TR/appmanifest/#icons-member) has changed, similar to [Cache-Control:Immutable](https://caniuse.com/mdn-http_headers_cache-control_immutable) behavior. 74 | 75 | The algorithm will treat a url as 'updated' or 'changed' by looking at the previously seen manifest's specified icon for each size and purpose. This means that a developer who specifies the same icon url, but for a different size, is technically eligible for a re-download by the user agent, as the user agent may cache icons by downloading them at the sizes specified by the developer. This behavior keeps the implementation & expectations simple. 76 | 77 | ## Pre-requisites: 78 | 79 | - An app corresponding to the manifest has already been installed. 80 | 81 | ## Future Considerations 82 | 83 | ### Javascript API for developers 84 | 85 | Introducing a javascript API similar to [`beforeinstallprompt`](https://wicg.github.io/manifest-incubations/#beforeinstallpromptevent-interface) would provide developers with more fine grained control over when to trigger an update for their API (or even put UX) on their site to trigger the update process. For our proposal, let's call it `appupdateprompt`. 86 | 87 | ``` 88 | let deferredUpdatePrompt; 89 | window.addEventListener('appupdateprompt', (e) => { 90 | e.preventDefault(); 91 | // Save the event to trigger it later. 92 | deferredUpdatePrompt = e; 93 | // Show customized update prompt for PWA. 94 | showAppUpdateDialog(); 95 | }); 96 | ``` 97 | 98 | The developer can then choose to tie it to an update button on their UX: 99 | 100 | ``` 101 | // Gather the data from custom update UI 102 | updateButton.addEventListener('click', async () => { 103 | deferredPrompt.prompt(); 104 | // Find out whether the user confirmed the installation or not 105 | const { outcome } = await deferredPrompt.outcome; 106 | // Act on the user choice, as if the outcome is either `accepted` or `dismissed` 107 | if (outcome === 'accepted') { 108 | console.log('Update UX accepted'); 109 | } else if (outcome === 'dismissed') { 110 | console.log('Update UX dismissed'); 111 | } 112 | }); 113 | ``` 114 | 115 | This would be an ultimate solution for developer control, because it gives the developer "full" control over the manifest update process. However, allowing the security sensitive members to be mediated by the user agent allows the update process to be less blocking, so the main developer complaint of not wanting blocking dialogs for users is mitigated. 116 | 117 | # **How does this solve the problem?** 118 | 119 | Let’s review the goals again to see how this proposal meets them: 120 | 121 | > Goal: [`Consistency`](#consistency), [`User Agent Flexibility`](#uaf) 122 | 123 | Since the change in an icon url is the **`signal`** that a manifest update can be triggered, this allows user agents to implement their own behavior around it. This will be [speced in the manifest](https://www.w3.org/TR/appmanifest/#updating) for consistency across multiple user agents. 124 | 125 | > Goal: [`Developer Control`](#devc), [`Preventing unnecessary user interruption.`](#useint) 126 | 127 | The users should only see the dialog when the developer wants them to. To do so, the developer only has to change the icon urls. The manifest fields that determine the functionality of the app (like [`file_handlers`](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/file_handlers) or [`launch_handlers`](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/launch_handler)) update silently, so multiple functional copies of the app are not available everywhere. 128 | 129 | > Goal: [`Reduce network traffic`](#traffic) 130 | 131 | The most network heavy traffic is downloading icons, which will only happen when the developer wants them to, and not up to once per day every time the app is accessed. 132 | 133 | ## Cons 134 | 135 | The proposal is not backwards compatible for certain platforms where developers rely on icon bits changing while the url still stays the same for triggering a manifest update. However, there is a workaround for end users in this case, which is to uninstall and re-install the site as a PWA. 136 | 137 | # **Alternatives considered** 138 | 139 | ## [Use a `version` field to trigger manifest updates](#version) 140 | 141 | Add the concept of versioning to the web app manifest, so that the developer can change "versions" of their installed experiences to trigger updates. This has been proposed earlier in the manifest spec multiple times (like [manifest#1036](https://github.com/w3c/manifest/issues/1036) and [manifest#1157](https://github.com/w3c/manifest/issues/1157)). 142 | 143 | Pros: 144 | - This would also help support this [FR about changelogs](https://github.com/w3c/manifest-app-info/issues/1). 145 | 146 | Cons: 147 | - The term is highly overloaded, and it is [hard to get consensus on the data type](https://github.com/w3c/manifest/issues/446#issuecomment-904359501) and what its purpose is. Compared to that, `update_token` is highly confined to a specific use-case, and is simpler to reason about. 148 | - Leads to complications around supporting a lot of things like [version downgrades](https://github.com/w3c/manifest/issues/446#issuecomment-905725612), upgrades as well as [ overall interpretation of the field](https://github.com/w3c/manifest/issues/1036). 149 | 150 | ## Use an `update_token` field, only for manifest updates 151 | 152 | This is kind of similar to the above suggestion of using a [version field](#version), with the only difference being that the field is highly confined to be of a single data type like string, and has a single purpose, which is to signal that the manifest needs updating. 153 | 154 | Cons: 155 | - This requires an extra image diff algorithm to determine if icon bits have changed if the url is still the same during the update process. 156 | - There is still a chance that developers might make a mistake while setting the `update_token` or removing it, leading to unwanted updates. 157 | - There is also a potential foot-gun here of different app functionalities in the wild without the developer knowing if the token is not updated by them, or if they gradually change the manifest. 158 | 159 | ## Allow end users to ignore updates 160 | 161 | End users can turn off “updates” for their installed app from the settings page of their app. 162 | 163 | Cons: 164 | 165 | - Users don’t get new functionalities specified by the developer, like `file_handlers`. 166 | - Developer complexity is too high to “support” old versions of the same PWAs, leading to incompatible feature sets across different “versions” of the manifest used by users. 167 | - User agents would have to build a custom UI and logic trigger updates when the user is ready, or to save that a user has ignored one. Otherwise the only solution for the end user would be to uninstall and then reinstall. 168 | - Old application configurations are often connected with security vulnerabilities. 169 | 170 | ## Only update on manifest\_url change 171 | 172 | While this solution is simple to implement, it breaks existing update behavior, and is also cumbersome for developers, who would have to update where they serve their manifest from every time a change is made. 173 | 174 | # **Notes** 175 | 176 | - Since [any website is an installable application](https://www.w3.org/TR/appmanifest/#installable-web-applications), if a user agent allows installation of a site that never specified an explicit manifest, update can still occur if the manifest ids matches. 177 | - The user agent can perform the following tasks if they want: 178 | - Notify the end user that the app has been updated, like native apps do. 179 | - Provide a warning string in the console for developers if they change icons but forget to add/update the `update_token`. 180 | 181 | ## [Phishing](#phishing) 182 | 183 | A malicious actor can set up an innocent site to come across as non-malicious, and once the user installs it, they can trick the user by acting as a different site by updating itself silently. Some examples of how this can happen: 184 | - **Buyout**: Where a malicious actor buys a non-malicious site (e.g. Wordle) and updates itself to mimic a bank app. 185 | - **Bait-and-switch**: A site for a simple use-case (like an innocent calculator app) updates itself to mimic a bank app. 186 | 187 | This is currently handled on Chromium by: 188 | - Showing a dialog if the security sensitive fields have changed. 189 | - Using [Safe Browsing](https://support.google.com/chrome/answer/9890866?hl=en&co=GENIE.Platform%3DAndroid) on Chrome. 190 | -------------------------------------------------------------------------------- /scope_extensions-explainer.md: -------------------------------------------------------------------------------- 1 | # Scope Extensions for Web App Manifest 2 | 3 | ## Participate 4 | 5 | * [Github issues](https://github.com/WICG/manifest-incubations/issues?q=is%3Aissue+is%3Aopen+label%3Ascope-extensions) 6 | 7 | ## Introduction 8 | 9 | This explainer proposes a new `scope_extensions` [Web Application 10 | Manifest](https://www.w3.org/TR/appmanifest/) member that extends the concept of 11 | [app scope](https://www.w3.org/TR/appmanifest/#understanding-scope). 12 | 13 | The scope of an installed web app ["restricts the URLs to which the manifest is 14 | applied and provides a means to 'deep link' into a web application from other 15 | applications."](https://www.w3.org/TR/appmanifest/#abstract) User agents apply 16 | metadata in the manifest to documents [within 17 | scope](https://www.w3.org/TR/appmanifest/#dfn-within-scope) and often apply 18 | differential UX treatments to make it obvious when an app window navigates to 19 | documents outside of scope. 20 | 21 | Currently, the scope of an app can only include URLs from a single [web 22 | origin](https://datatracker.ietf.org/doc/rfc6454/). Web developers who are 23 | interested in creating a web app with contents located in multiple different web 24 | origins find themselves unable to do so. This leads to a poor user experience 25 | when viewing documents that are part of the app but are technically 26 | out-of-scope. Users may be confused by browser UI marking the page as 27 | out-of-scope, and some desired app-like features such as deep linking may not 28 | function at all for out-of-scope documents. 29 | 30 | Apps that wish to define a scope spanning multiple different origins could do so 31 | using the `scope_extensions` manifest field detailed below. 32 | 33 | ## What apps could benefit? 34 | 35 | For each example below, developers have expressed interest in publishing a 36 | single installable app instead of one per origin with distinct [manifest 37 | ids](https://www.w3.org/TR/appmanifest/#id-member). 38 | 39 | ### TLD locales 40 | 41 | An app can have content from multiple Top Level Domains representing different 42 | locales. Each site is usually structured identically but could have differences 43 | in policies, displayed language, etc. Example: 44 | * https://www.starbucks.fr, https://www.starbucks.de, https://www.starbucks.ca, ... 45 | 46 | ### Shortened/Vanity URLs 47 | 48 | An app can share shortened/vanity URLs and want those URLs to be captured to app 49 | windows when navigated. Example: 50 | * https://youtu.be/dQw4w9WgXcQ is the shortened URL for 51 | https://www.youtube.com/watch?v=dQw4w9WgXcQ. 52 | 53 | ### Sub-domain locales 54 | 55 | An app can also use different sub-domains to represent different locales. 56 | Example: 57 | * https://en.wikipedia.org, https://fr.wikipedia.org, ... 58 | 59 | ### Sub-domains that represent different user groups (non-goal) 60 | Some sites are structured to use sub-domains to represent different groups or 61 | organizations but may want to publish a single installable app instead of one 62 | for each. Examples: 63 | * https://chromium.slack.org, https://foo.slack.org, https://bar.slack.org 64 | * https://foo.zoom.us, https://bar.zoom.us, ... 65 | 66 | Addressing this case is considered a non-goal for this explainer because it is 67 | not possible to enumerate and validate all of the matching sub-domains at 68 | install time. This work is left to a future explainer. 69 | 70 | ## Key User Scenarios 71 | 72 | ### Out-of-scope UX 73 | 74 | In Chromium browsers, top-level app window navigation to an out-of-scope URL 75 | creates a notification bar - this notification bar informs the user that they 76 | have navigated outside of app scope or to a different origin altogether and 77 | provides a control to return to the `start_url`. This UI bar protects the user 78 | by providing useful information about the change in browsing context. It is not 79 | dismissable without navigating the page back to the `start_url`. Other browser 80 | implementations may include similar behavior. 81 | 82 |
83 | 84 |
Example: installed web app window with out-of-scope UI bar
85 |
86 | 87 | The app window shown above has navigated to a url (`https://airhorner.com`) 88 | outside of its scope (`https://diek.us/pwinter/`). The white bar above the web 89 | contents informs the user of this difference. This feature is important as it 90 | aims to keep the user aware of the content's security context. 91 | 92 | A developer may intentionally want to include documents from different origins 93 | in app scope. In this case, an obtrusive out-of-scope bar is not necessary and 94 | could confuse users. 95 | 96 | ### Out-of-scope navigations leave the app window 97 | 98 | When a page in an app window navigates to an out-of-scope URL, some browser 99 | implementations may handle it by opening a new tab or browsing context in a 100 | regular browser window. This can appear jarring to users if they are unfamiliar 101 | with how scope works and the content appears to still be part of the app. 102 | Developers can use `scope_extensions` to ensure appropriate navigations do not 103 | leave the app window. 104 | 105 | ### Navigation Capture (a.k.a. In-browser Link Capture) 106 | 107 | Navigation capturing is a behavior that captures suitable browser window page 108 | navigations and opening them an app window when the target URL matches the scope 109 | of an installed app. This behavior is common on mobile platforms and is [in 110 | development](https://issues.chromium.org/issues/40058251) for desktop platforms 111 | in Chromium. Navigation capturing relies on the scope of an app to decide if a 112 | navigation is entering or leaving an app. Developers can use `scope_extensions` 113 | to ensure appropriate browser navigations open in an app window. 114 | 115 | Navigation capturing can be combined with 116 | [`launch_handlers`](https://developer.mozilla.org/en-US/docs/Web/Manifest/launch_handler) 117 | to create a tailored app navigation experience. 118 | 119 | ### Other manifest features 120 | As app scope is used as the boundary for the application of manifest metadata 121 | such as 122 | [`theme_color`](https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color), 123 | documents intended to be part of the app but are excluded from app scope cannot 124 | take part and are not presented in a consistent manner. [Window Controls 125 | Overlay](https://developer.mozilla.org/en-US/docs/Web/API/Window_Controls_Overlay_API) 126 | is another example - the overlay is not applied to the titlebar when the main 127 | document in the app window is out-of-scope. 128 | 129 | ## Goals 130 | 131 | - Allow developers to define an app scope using a list of origins known at 132 | install time. 133 | 134 | - Allow developers to specify a scoping path for each listed origin similar to 135 | how the manifest `scope` field works. 136 | 137 | - Allow web apps to capture user navigations to sites they are affiliated with. 138 | E.g. "Productivity Suite" app capturing links navigations to 139 | presentations.productivity.com. 140 | 141 | ## Non-goals 142 | 143 | - Allow developers to define an app scope that includes an unknown number of 144 | origins at install time, such as could be done using URL pattern matching or 145 | by including all origins of an entire site. 146 | 147 | - Allow developers to define scope in a single origin using more complicated URL 148 | filters, lists of filters, etc. 149 | 150 | - Allow developers to exclude URLs from app scope. 151 | 152 | ## Proposed solution 153 | 154 | To extend app scope, a developer will: 155 | 156 | 1. Add a `scope_extensions` section to the web app manifest which lists one or 157 | more extensions. 158 | 159 | 1. Host a `.well-known/web-app-origin-association` file at each additional 160 | origin. This file establishes a two-way handshake between a unique app and the 161 | origin owner. It also contains any fine-grain URL filters needed to control 162 | scope. 163 | 164 | ### Manifest 165 | 166 | Example manifest located at `https://example.com/manifest.webmanifest`: 167 | ```json 168 | { 169 | "id": "https://example.com/app", 170 | "name": "My App", 171 | "display": "standalone", 172 | "start_url": "/app/index.html", 173 | "scope": "/app", 174 | "scope_extensions": [ 175 | { "type": "origin", "origin": "https://example.co.uk" }, 176 | { "type": "origin", "origin": "https://help.example.com" } 177 | ] 178 | } 179 | ``` 180 | The "Example" app has a regular scope of `http://example.com/app` and is 181 | extending its app scope to the origins `https://example.co.uk` and 182 | `https://help.example.com`. 183 | 184 | * Each entry in `scope_extensions` must contain both `type` and `origin` string 185 | fields. 186 | * `type` must be `"origin"`. Other types could be added in the future. One 187 | future addition could be a `site` type which includes a dynamic number of 188 | sub-domain origins (stated above as a non-goal). 189 | * `origin` must a valid URL. The URL is converted to an 190 | [origin](https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-tuple). 191 | 192 | ### Association file 193 | 194 | A `web-app-origin-association` file must be served from `https:///.well-known/web-app-origin-association`. An app is allowed to extend its 196 | scope to this origin if its manifest id is found in this file. 197 | 198 | Example association file located at 199 | `https://example.co.uk/.well-known/web-app-origin-association`: 200 | 201 | ```json 202 | { 203 | "https://example.com/app": { 204 | "scope": "/app" // Evaluates to https://example.co.uk/app 205 | } 206 | } 207 | ``` 208 | 209 | * Each dictionary key must be a validly formatted [manifest 210 | id](https://w3c.github.io/manifest/#id-member). 211 | * Each dictionary value must be an object. 212 | * Each dictionary value can optionally contain a `scope` string. If not 213 | provided, `scope` defaults to `/`. 214 | * This `scope` configures the extension scope each identified app is allowed to 215 | utilize. 216 | * This `scope` works the same way as the 217 | [`scope`](https://www.w3.org/TR/appmanifest/#scope-member) member in the 218 | manifest and is relative to this origin. 219 | 220 | ## Security Considerations 221 | 222 | ### Link capturing from another origin 223 | 224 | If an origin A adds a web app B to its `web-app-origin-association` file, A is 225 | implicitly authorizing app B to intercept navigations to URLs in A. This implies 226 | that app B can potentially spoof origin A and therefore it is advised that 227 | origin A and web app B should be owned by the same entity. 228 | 229 | User agents may perform link capturing for user navigations within a web app's 230 | extended scope and launch the web app instead of performing the navigation. 231 | 232 | The [launch handler][launch-handler] proposal enables sites to reroute app 233 | launches into existing web app contexts. 234 | 235 | The combination of link capturing, launch handler and scope extensions leads to 236 | the following attack vector: 237 | 1. User installs the "TestApp" web app from app.com. 238 | 1. TestApp's scope includes site.com with valid origin association. 239 | 1. TestApp sets its `launch_handler` to 240 | ``` 241 | { 242 | "client_mode": "focus-existing" 243 | } 244 | ``` 245 | 1. User clicks on a link to site.com. 246 | 1. Navigation is captured by an existing TestApp window that is brought into 247 | focus and has a LaunchParam is enqueued. 248 | 1. *TestApp is now aware that the user is navigating to site.com and could 249 | perform a fake navigation with the intention of duping the user into thinking 250 | they're on site.com.* 251 | 252 | ## Future work under consideration 253 | 254 | 1. More [fine-grained scoping 255 | mechanisms](https://github.com/w3c/manifest/issues/996) such as 256 | include/exclude lists or [URL patterns](https://wicg.github.io/urlpattern/). 257 | These mechanisms could be reused in 3 different places: in the association 258 | file, in `scope_extensions` in the manifest, at the top level in the manifest. 259 | 260 | 1. Change the constraint on manifest URLs that are bound by scope (except for 261 | `start_url`) to instead be bound by the extended scope. Validation of the 262 | associated origins is not required for these URLs to be part of a valid 263 | manifest. Prior to validation the URLs must be treated as if they were not 264 | specified. 265 | 266 | 1. Add an `"authorize"` field to `web-app-origin-association` e.g.: 267 | ```json 268 | { 269 | "web_apps": [{ 270 | "web_app_identity": "https://example.org", 271 | "authorize": ["intercept-links"] 272 | }] 273 | } 274 | ``` 275 | This opt-in serves as a signal of trust from the associated origin to allow 276 | the web app to [capture navigations][link-capturing-from-another-origin] into 277 | the associated origin. 278 | 279 | ### Extended Scope Permissions 280 | 281 | When an application uses `scope_extensions` to expand its scope, **each 282 | additional origin's site permissions remain the same**. Expanding scopes does 283 | not imply any change in permissions. 284 | 285 | ### Additional security UX 286 | 287 | For added security, app windows may want to implement more visible UI that 288 | displays the current URL being served, as well the privacy and permission 289 | information. 290 | 291 |
292 | 293 |
Domain, privacy, and permissions info in the app menu.
294 |
295 | 296 | In the implementation shown above, the domain, privacy, and permission 297 | information for the current origin can be found in the app menu. The 298 | discoverability of this information has room for improvement. 299 | 300 | ## Related Proposals 301 | 302 | ### url_handlers 303 | 304 | The `scope_extensions` proposal is a replacement for part of the 305 | [`url_handlers`](https://github.com/WICG/pwa-url-handler/blob/main/explainer.md) 306 | proposal with the following changes: 307 | - Re-orient the goal to be focused just on expanding the set of origins/URLs in 308 | the web app's scope. Remove the goal of registering web apps as URL handlers 309 | in the user's operating system. That behaviour will be covered by individual 310 | browsers optionally offering users the choice to capture link navigations as 311 | web app launches. 312 | - Rename the new manifest field from `url_handlers` to `scope_extensions` to 313 | reflect the change in goals. 314 | - Move the association file from "/web-app-origin-association.json" to 315 | "/.well-known/web-app-origin-association". This better conforms with 316 | [RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615). 317 | - Change the association file entries to be keyed on the [web app 318 | identifier](manifest-identity) rather than the web app's manifest URL (the 319 | former having been added to the Manifest spec in the interim). 320 | - Replace `"paths"` with `scope` in the association file entries. 321 | - Add an "authorize" field to the association file entries for the associated 322 | origin to provide explicit opt-in signals for security sensitive 323 | capabilities. 324 | 325 | ### Others 326 | 327 | * [launch-handler](https://github.com/WICG/sw-launch/blob/main/launch_handler.md) 328 | * [manifest-identity](https://w3c.github.io/manifest/#dfn-identity) 329 | -------------------------------------------------------------------------------- /scope_extensions-security-privacy-questionnaire.md: -------------------------------------------------------------------------------- 1 | # Self-Review Questionnaire: Security and Privacy 2 | 3 | Security and Privacy questionnaire for [`scope_extensions`](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md) 4 | 5 | ## What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary? 6 | 7 | If an URL in extended scope is captured by an app that has `launch_handler: { client_mode: "focus-existing" }` set in its manifest, the URL is made visible to the app through `Window.launchQueue` instead of causing a top level navigation. Without `scope_extensions`, this URL must be from the same origin as the app scope. With `scope_extensions`, this exposed URL can be from a different origin within extended scope. 8 | 9 | ## Do features in your specification expose the minimum amount of information necessary to enable their intended uses? 10 | 11 | Yes. 12 | 13 | ## How do the features in your specification deal with personal information, personally-identifiable information (PII), or information derived from them? 14 | 15 | There is no information of this type being handled by the feature. 16 | 17 | ## How do the features in your specification deal with sensitive information? 18 | 19 | There is no sensitive information handled by the feature. 20 | 21 | ## Do the features in your specification introduce new state for an origin that persists across browsing sessions? 22 | 23 | No, this feature does not introduce new states tied to an origin. The validation that an origin is within an extended scope is done at the same cadence as manifest updates. A validated status of an origin might persist across sessions. 24 | 25 | ## Do the features in your specification expose information about the underlying platform to origins? 26 | 27 | No. 28 | 29 | ## Does this specification allow an origin to send data to the underlying platform? 30 | 31 | No. 32 | 33 | ## Do features in this specification enable access to device sensors? 34 | 35 | No. 36 | 37 | ## Do features in this specification enable new script execution/loading mechanisms? 38 | 39 | No. 40 | 41 | ## Do features in this specification allow an origin to access other devices? 42 | 43 | No. 44 | 45 | ## Do features in this specification allow an origin some measure of control over a user agent’s native UI? 46 | 47 | Yes. The feature will affect the UA's out-of-scope banner on installed web applications. This UI will still appear if the PWA navigates to a page that is not in the (extended) scope. 48 | 49 | ## What temporary identifiers do the features in this specification create or expose to the web? 50 | 51 | None. 52 | 53 | ## How does this specification distinguish between behavior in first-party and third-party contexts? 54 | 55 | No, it does not make such distinction. 56 | 57 | ## How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode? 58 | 59 | It will not be available in Private Browsing/Incognito mode. 60 | 61 | ## Does this specification have both "Security Considerations" and "Privacy Considerations" sections? 62 | 63 | There is no specification written yet. (The [explainer](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md) does have Security considerations.) 64 | 65 | ## Do features in your specification enable origins to downgrade default security protections? 66 | 67 | This feature makes a distinction between origins in an app's extended scope versus unrelated origins. Origins in the extended scope will not trigger the out-of-scope banner while unrelated origins will. All cross-origin navigation will be higlighted by origin text appearing in the window's title bar. 68 | 69 | ## How does your feature handle non-"fully active" documents? 70 | 71 | This feature does not interact with non-"fully active" documents. 72 | 73 | ## What should this questionnaire have asked? 74 | 75 | N/A. 76 | -------------------------------------------------------------------------------- /tabbed-mode-explainer.md: -------------------------------------------------------------------------------- 1 | # Tabbed Mode 2 | 3 | Author: Louise Brett (loubrett@google.com) 4 | 5 | ## Overview 6 | 7 | Currently PWAs in a standalone window can only have one page open at a time. Some apps expect users to have many pages open at once. Tabbed mode adds a tab strip to standalone web apps that allows multiple tabs to be open at once. 8 | 9 | This document describes a new display mode `tabbed` and a new manifest member `tab_strip` which lets apps enable the tab strip and customize it. 10 | 11 | ## Proposal 12 | 13 | Add a new display override option `tabbed` which behaves similarly to `standalone`, but with an added tab strip. 14 | 15 | ```json 16 | "display_override": ["tabbed"] 17 | ``` 18 | 19 | Per the design of `display_override`, sites cannot request the `tabbed` display mode in the `display` member. This is because it would be backwards-incompatible with user agents that do not support the tabbed display mode. By forcing it into the `display_override` member, the site can specify an explicit fallback chain, ending with the declared `display` mode. (This means sites can decide whether unsupported browsers should fall back to `standalone` or `browser`, depending on what is more important to the site: tabs, or a standalone window.) 20 | 21 | Add a new manifest field `tab_strip` which allows apps to customize the tab strip. The `tab_strip` field will only be used when the display mode is `tabbed`. 22 | 23 | ``` 24 | "tab_strip": { 25 | "home_tab": { 26 | "scope_patterns": [{"pathname": "..."}] 27 | }, 28 | "new_tab_button": { 29 | "url": , 30 | }, 31 | }, 32 | ``` 33 | 34 | The home tab is a pinned tab that, if enabled for an app, should be present in all app windows. If the `home_tab` field is unset, then the app will not have a home tab. 35 | 36 | The `home_tab.scope_patterns` field allows the app to set a list of [URLPatterns](https://wicg.github.io/urlpattern/#urlpattern) to define the scope of the home tab. This "home tab scope" carves the URL space of the application scope into two parts: "within home tab scope" and "outside of home tab scope". The home tab scope affects navigation in two important ways: 37 | 38 | 1. From within the home tab, a navigation to a URL outside of the home tab scope will open a new tab and perform the navigation there. 39 | 2. From outside the home tab, a navigation to a URL within the home tab scope will focus the home tab and perform the navigation there. 40 | 41 | This "capturing" behaviour behaves a bit like `target=_blank` navigations, but applies to all navigations including regular `target=_self` links. Therefore, it explicitly breaks the normal HTML navigation flow, and is therefore something developers need to explicitly opt in to and be aware of. (As an example, a page may navigate itself to another URL, expecting its state to be destroyed, but due to the above home tab logic, the page may in fact stay open indefinitely.) 42 | 43 | The `scope_patterns` member will be resolved against the manifest URL, and patterns must be within the app scope. If there is a home tab, the app's `start_url` will automatically be included in the home tab scope (this is necessary to ensure when a window is created there is a URL to navigate the home tab to). If `home_tab` is present but `scope_patterns` is absent, then there will be a home tab, but it will only include the `start_url`. 44 | 45 | The new tab button should open a new tab at a URL that is within the scope of the app, but outside of the home tab scope. The app may choose to set a custom URL to be opened. If the new tab URL is within the scope of the home tab, the new tab button will be hidden. If this URL is not specified or is out of scope, it will default to the `start_url` (which means the button will, by definition, be hidden if there is a home tab). 46 | 47 | If the `tab_strip` field is unset, it will default to the following object: 48 | ``` 49 | "tab_strip": { 50 | "new_tab_button": { 51 | "url": , 52 | }, 53 | }, 54 | ``` 55 | 56 | User agents can decide how to handle dragging these tabs around to create new windows or combine with browser tabs. 57 | 58 | Apps can detect whether they have the tab strip enabled by checking the display mode with a media query. 59 | 60 | ```css 61 | @media (display-mode: tabbed) { 62 | /* CSS to apply in tabbed application mode. */ 63 | } 64 | ``` 65 | 66 | ```js 67 | const tabbedApplicationModeEnabled = window.matchMedia('(display-mode: tabbed)').matches; 68 | ``` 69 | 70 | ## Use cases 71 | 72 | Tabbed mode is useful for any site where it is common to have more than one instance open at a time. Without tabbed mode, this would mean opening multiple windows for a single app which quickly becomes difficult to manage. 73 | 74 | This can also be used instead of sites creating their own HTML-based tab strip. 75 | 76 | The pinned home tab is suited for any site with a well defined home page that can be used to navigate the site. For example, productivity apps that have a home page with a gallery of current documents and a way to create a new documents. The home tab can be used as a menu to open existing files, all of which would then open in their own tab. These apps may also use a custom URL for the new tab button to quickly create a new document. 77 | 78 | A use case for having the new tab button hidden is when the home tab is present and that page is sufficient to navigate the app. For example a news site where articles are opened from the home tab. 79 | 80 | ## Possible extensions 81 | 82 | The proposed format makes it easy to add more fields to the `tab_strip` member in the future. 83 | 84 | The `home_tab` field could have a `url` field, if an app wanted the home tab to open at a different URL to the `start_url` and an `icons` field to customize the icon displayed on the home tab. 85 | 86 | The `new_tab_button` could have an `icons` field to customize the icon shown on the new tab button. 87 | 88 | ``` 89 | "tab_strip": { 90 | "home_tab": { 91 | "url": , 92 | "icons": [...], 93 | }, 94 | "new_tab_button": { 95 | "icons": [...], 96 | }, 97 | }, 98 | ``` 99 | 100 | ### Combining with other display modes 101 | 102 | Some apps may want to use a combination of display modes together. An example of this is using [window controls overlay](https://wicg.github.io/window-controls-overlay/) with tabbed mode. 103 | 104 | There is a temptation to design a maximally flexible system where sites can request any combination of display mode (e.g. `"window-controls-overlay, tabbed"`). However, this would require every browser to explicitly design, support and test all permutations of all display modes, making the addition of each additional display mode prohibitively expensive. 105 | 106 | Instead, it was decided that *if* certain combinations of display modes were wanted in the future, we would explicitly design and implement those combinations; e.g., we could add a `window-controls-overlay-tabbed` display mode if there was enough support for it. This is less flexible, but scales in a controllable way. This is discussed in detail in the [Display Mode Override Proposal](https://github.com/WICG/display-override/blob/main/explainer.md#custom-display-mode-names-with-display-modifiers-style-specification). 107 | 108 | ## Interaction with Launch Handler API 109 | 110 | The [Launch Handler API](https://wicg.github.io/web-app-launch/) lets sites redirect app launches into existing app windows to prevent duplicate windows being opened. When a tabbed app sets `"client_mode": "navigate-new"`, app launches will open a new tab in an existing app window. 111 | 112 | ## Security and Privacy 113 | 114 | There are minimal security and privacy concerns with this proposal. Navigating out-of-scope will show the same out-of-scope UI that is used in standalone apps. This UI should only be shown on the out-of-scope tab. 115 | -------------------------------------------------------------------------------- /tidyconfig.txt: -------------------------------------------------------------------------------- 1 | char-encoding: utf8 2 | indent: yes 3 | indent-spaces: 2 4 | wrap: 80 5 | tidy-mark: no 6 | custom-tags: yes -------------------------------------------------------------------------------- /user-preferences-explainer.md: -------------------------------------------------------------------------------- 1 | # User Preferences Explainer 2 | 3 | Authors: [Aaron Gustafson](https://github.com/aarongustafson), Louise Brett (loubrett@google.com) 4 | 5 | ## Overview 6 | 7 | Currently in the web app manifest a `theme_color` and `background_color` can be defined. However there is no way to change these based on dark mode or [other user preferences](#possible-extensions). 8 | 9 | This document proposes a new `user_preferences` manifest member that enables web apps to provide alternate values for manifest members given specific user preferences. The structure of this `user_preferences` field matches the structure of the proposed [translations](https://github.com/WICG/manifest-incubations/blob/gh-pages/translations-explainer.md) member. 10 | 11 | This new member is inspired by the [CSS media queries user preferences](https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences). The keys are simple strings but they are derived from the CSS media query syntax. 12 | 13 | ## Proposal 14 | 15 | Add a new dictionary manifest entry `user_preferences`, mapping preference strings to a [`ManifestOverride` object](#manifestoverride-object). 16 | 17 | Initially, the only valid key of the `user_preferences` member is `color_scheme`, which can have the keys `dark` and `light`. This could be expanded in the future to include other [user preference media features](#possible-extensions). 18 | 19 | In the previous version, the valid keys of the `user_preferences` member were `color_scheme_dark` and `color_scheme_light`. 20 | 21 | ### `ManifestOverride` Object 22 | 23 | The `ManifestOverride` is a generic object that contains a subset of redefined manifest properties appropriate to the context (e.g. `user_preferences`) in which the `ManifestOverride` is being used. The properties that may be redefined in the `ManifestOverride` object depend on the context (e.g. `user_preferences`). Any properties not allowed within the context will be ignored. The redefined fields override the values set in the root of the manifest. 24 | 25 | For more detail on the `ManifestOverride` object, see [translations](https://github.com/WICG/manifest-incubations/blob/gh-pages/translations-explainer.md). 26 | 27 | For the `user_preferences` member, the acceptable keys for the `ManifestOverride` include: 28 | 29 | * `theme_color` 30 | * `background_color` 31 | * `icons` 32 | * `src` 33 | * `type` 34 | * `shortcuts` 35 | * `icons` 36 | * `src` 37 | * `type` 38 | 39 | For `user_preferences` which the host OS supports, implementers should make the relevant overrides available to the OS. For example, if an OS supports dark mode and an app has specified icon overrides for dark mode, the implementer should download these icons in addition to the icons they already download. 40 | 41 | SVG icons natively support `user_preferences` through CSS. Therefore SVG manifest icons (if supported) should also be rendered under all the OS supported `user_preferences`. 42 | 43 | ### Example 44 | 45 | ```json 46 | { 47 | "user_preferences": { 48 | "color_scheme": { 49 | "dark": { 50 | "theme_color": "#000", 51 | "background_color": "#000" 52 | }, 53 | "light": { 54 | "theme_color": "#fff", 55 | "background_color": "#fff" 56 | } 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | When a user has dark mode enabled, the fields redefined for `color_scheme.dark` will be used instead of the top level fields. 63 | 64 | Example of previous version: 65 | ```json 66 | { 67 | "user_preferences": { 68 | "color_scheme_dark": { 69 | "theme_color": "#000", 70 | "background_color": "#000" 71 | }, 72 | "color_scheme_light": { 73 | "theme_color": "#fff", 74 | "background_color": "#fff" 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | ## Possible extensions 81 | 82 | In addition to the `color_scheme` preference, other CSS [user preference media features](https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences) could be added as valid keys. These include: 83 | 84 | * Prefers reduced motion 85 | * Prefers reduced transparency 86 | * Prefers contrast 87 | * Forced colors 88 | * Prefers reduced data 89 | 90 | ## Security and Privacy Considerations 91 | 92 | There are minimal security and privacy concerns with this proposal. This will allow sites to know what user preferences are set and use this for fingerprinting, however this information is already exposed through CSS. 93 | 94 | ## Alternatives considered 95 | 96 | It would also be possible to use the CSS media query syntax for the keys (e.g. `(prefers-color-scheme: dark)`) and parse this as CSS instead of using fixed keys as proposed. However, using the CSS parser adds a lot of complexity which we would like to avoid. Before a web app can launch, the CSS parser would need to be run to analyze the media query. This would also allow media queries which don’t make sense for this proposal, such as window size. 97 | 98 | ## Open questions 99 | 100 | How does this interact with `translations` for fields that can be overridden by either `user_preferences` or `translations`, such as icons? 101 | 102 | For sites that have an in app dark mode setting, how can they communicate that the selected theme is different from the system theme? 103 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [80485] 3 | , "contacts": ["marcoscaceres"] 4 | , "repo-type": "cg-report" 5 | } 6 | --------------------------------------------------------------------------------