Comprehensive Developer Design Spec for Web Install API Enhancements
Detailed overview of the Web Install API including goals, technical implementation, JS API changes, permissions, UX, and testing for installing web apps across origins.
Comprehensive Developer Design Spec for Web Install API Enhancements
1.
Web Install API
DevDesign Spec
Author: Amanda Baker, Diego Gonzalez, Kristin Lee, Lia Hiscock
Spec status: In Progress
Last updated: Jun 18, 2025
Links: Explainer | Chrome Status | CR bug | UX changes review doc
7
Feature Overview
Introduction
Goals
Non-goals
Concepts
Interfaces and Interactions
JS API changes
navigator.install()
Web App Manifest changes
Technical Implementation
Web Install Permission
navigator.install()
Blink
Mojo
Surfacing install result to Blink
Chrome - WebInstallServiceImpl
Current document installs - FetchManifestAndInstallCommand
Background document installs - WebInstallFromUrlCommand (new!)
Background document, check for app already installed
WebAppFilter for background document
WebAppLaunchDialog
[WIP] – Post OriginTrials – Background document install – additional functionality
[WIP] – Post OriginTrials – Code hygiene – command refactoring
UX changes
Install Dialog
Open App Dialog
Incognito/Guest Mode
Security
Privacy
Functional and Unit Testing
Test Approach
2.
Test Cases
Automated TestCases
Manual Test Cases
Telemetry
Open Questions
Appendix
Past API Discussions
MLInstallabilityPromoter & background install dialog considerations
Future Work (WIP)
Rejected Alternatives
Manifest Addition: install_sources
Allowing/Blocking Install Sources
navigator.getInstalledApps()
Efficiency Improvement for Same-origin Installs
Web install permission
Persistent omnibox status icon (intent picker)
Custom text for blocked permission omnibox pill
Archive - Permissions Strings
Permissions Strings Review Approvals
Permissions String Review Notes
Original dev spec permissions content
Feature Overview
Introduction
Current paradigms of application discovery involve a user going to an application
repository to search and install apps. These repositories are generally curated and have
a level of trust associated by the user. They play a vital role in allowing a user to acquire
high quality apps. Application repositories are valuable because they help developers
acquire users, which incentivises developers to create better apps on the web platform.
While this is the general acquisition flow on many platforms, the web does not have the
ability to replicate this scenario because it can not install applications. This makes it
impossible for a web app (repository, catalog, store) to install and distribute other
applications.
The Web Install API addresses this shortcoming in the platform. It allows a web site
to install a web app, either same or cross origin. This functionality allows the
3.
creation of webbased catalogs that can install PWAs directly from the web and
into multiple platforms.
Goals
● Enable installation of web apps.
● Allow the web app to report back to the installation origin with the outcome of the
installation.
● Enable UAs to suppress potential installation-prompt spam.
● Allow acquisition details (a CampaignId for instance) to be captured at all install time and
retrieved with the Acquisition Info API. (with the Acquisition Info API).
Non-goals
● Installing “DIY” apps.
● Installing web content that does not pass the installability criteria (see "Requirements"
sections).
● Change the way the UA currently prompts for installation of a web app.
● Associate ratings and reviews with the installed app (see Ratings and Reviews API
explainer).
● Process payments for installation of PWAs (see Payment Request API).
● List purchased/installed goods from a store (see Digital Goods API).
● Implement getInstalledApps() so an origin can request the list of manifest_ids associated
with apps installed by that origin (see getInstalledApps in Appendix)
Concepts
● Current document installation: the install API is invoked to install the exact page that
the user is currently on. This is very similar to the existing onbeforeinstallprompt
behavior.
● Background document installation: the install API is invoked to install a different page
from the one that the API is called from.
● install_url: url of the intended page to install as an app. Any url within the scope of
the manifest can be an install_url if it links to the manifest. An install_url that only
contains relevant content for installation is useful for an install API. See [PWA]
Unvalidated URL Installation: Problems, Workarounds, Solutions for more info.
● Crafted app: a web app that includes a manifest with a minimum set of fields: name,
start_url, icons, and a valid display mode.
● DIY app: web apps that do not provide a manifest or do not include the minimum
required manifest fields to qualify as a crafted app.
4.
Interfaces and Interactions
JSAPI changes
navigator.install()
Web Install APIs will extend the window.navigator object, with 3 distinct signatures. See the
explainer for motivations for the 3 signatures:
MSEdgeExplainers/WebInstall/explainer.md at main · MicrosoftEdge/MSEdgeExplainers
Signature Info Requirements
navigator.install(
<install_url>,
<manifest_id>)
Installs a document, given by
install_url, in the background.
The document at install_url must link
to a manifest file,
AND
manifest_id must match the computed id
after parsing the manifest.
navigator.install(
<install_url>)
Installs a document, given by
install_url, in the background
The document at install_url must link
to a manifest file,
AND
The manifest file must have an id field
defined.
navigator.install() Installs the current document. The current document must link to a
manifest file,
AND
The manifest file must have an id field
defined.
All 3 signatures also optionally take a <params> object intended to future-proof the API if
additional data were required with the call. If Acquisition Info API is supported, then <params>
will include a referral-info field which will accept an object with arbitrary information as
required by the calling installation domain.
note #1: the 1 and 2 parameter signatures can technically be used to install the current document (when
install_url == calling url). However, for simplicity and consistency, it will still install in the background.
Web App Manifest changes
No changes to manifest. Previous discussions included a list of install_sources from which
an app could be installed, but this has been moved to future considerations. See Appendix for
details.
5.
Technical Implementation
Web InstallPermission
A new permissions policy kWebAppInstallation will be added that allows an origin to ask to
install web apps. Details of how to create a new permission in code can be found here. See
Web Install UX review - Google Docs for screenshots of the permissions-related UX additions
and flows.
Permission will never be required if we’re installing the current document. Generally, this
means –
We will show the permission prompt for all background document installs (1 or 2
parameter API signatures), unless the user has already granted permission to the
origin.
We will not show the permission prompt for any current document installs (0
parameter API signature).
Edge case: if the developer uses the 1 or 2 parameter signature to install the current document (in the
background), we will not prompt for permission, as this is still installing the current document.
See Privacy & Security considerations in the Background Doc explainer for more details.
navigator.install()
An installation source (ie. any website) can request to install the web contents at an installation
target using navigator.install([<install_url>[, <manifest_id>]]),
6.
Blink
The JS APIswill be implemented under //t/b/r/modules/web_install, extending
Navigator as NavigatorWebInstall. install() can only be called in response to user
activation in the main frame, and should be rejected if called from any other context. It will also
be rejected if the calling origin has previously been denied the kWebAppInstallation
permission.
7.
We also doillegal argument validation to check for bad input (empty string, undefined, null) and
invalid install_urls, If this succeeds, we attempt to resolve the manifest id. A failure in any
of these checks results in a V8ThrowException::CreateTypeError being returned inside
the rejected promise.
If these checks are satisfied, then app installation proceeds via
blink::mojom::WebInstallService.
Mojo
The Blink APIs will communicate with //chrome via a WebInstallService mojo interface.
To reduce code duplication, we have collapsed the 3 Blink signatures into 1 Install()
function. It takes 1 argument, InstallOptions, with 3 valid states which correspond to the 3
API signatures.
struct InstallOptions {
url.mojom.Url install_url;
url.mojom.Url? manifest_id;
};
Install(InstallOptions? options)
=> (WebInstallServiceResult result, url.mojom.Url
manifest_id);
0 parameter 1 parameter 2 parameter
InstallOptions opts;
Install(opts);
InstallOptions opts =
{ foo.com };
Install(opts);
InstallOptions opts =
{ foo.com, some_id };
Install(opts);
Surfacing install result to Blink
Install() returns a WebInstallServiceResult and a Url. To protect user privacy, there
are only 3 possible results – kSuccess, kAbortError, kDataError (see explainer). Url is
empty in both error cases.
Chrome - WebInstallServiceImpl
The blink::mojom::WebInstallService will be implemented in
//c/b/web_applications as WebInstallServiceImpl. This is where we reconcile the 3
blink signatures with the 2 possible browser install paths – current or background document.
(We know which of the 3 blink signatures was called based on the state of InstallOptions.)
Current – 0 parameter signature
8.
Background –1 or 2 parameter signatures
Current document installs - FetchManifestAndInstallCommand
1. If InstallOptions is null, we initiate a current document install.
2. Use the WebAppTabHelper to check if the current document is already installed.
a. If installed, use the AppId of the current document to get an AppLock on the
registrar, and use the IntentPickerTabHelper to show the launch dialog.
b. Update IntentPickerTabHelper to optionally take a callback that always
runs asynchronously. The callback runs with true if the user clicked “open” in the
dialog and the withapp launches and reparents. If true, kSuccess and the
app’s manifest_id will be returned, otherwise kAbortError and an empty
string will be returned.
3. If not already installed, fetch the current document’s manifest using the
WebAppDataRetriever.
4. Check that the manifest contained a developer-specified id, via the has_custom_id
field of manifest.mojom.
a. Abort and report a DataError to Blink if there was none.
5. Use the WebAppUIManager to show the install dialog.
a. If the dialog is accepted and installation succeeds, return Success and the
installed app’s manifest_id. Otherwise, report AbortError and return an
empty string.
Background document installs - WebInstallFromUrlCommand (new!)
1. If InstallOptions is non-null, we initiate a background document install.
2. If not already granted, prompt the user for permission to allow the current site to install
web apps.
a. If denied, report a kAbortError and return.
3. Verify that the current web contents does not already have an install happening.
a. If an install is already happening, report a kAbortError and return.
4. Verify whether the background document is already installed. See Background
document, check for app already installed for how install state is determined.
a. If already installed, show a new WebAppLaunchDialog.
b. If the user clicks “open” in the dialog, resolve the promise with kSuccess. If the
dialog closes for any other reason, kAbortError.
c. Queue a LaunchWebAppCommand to launch the app.
5. Create an MLInstallOperationTracker and register the install on the current web
contents.
9.
a. See MLInstallabilityPromoter& background install dialog considerations for more
info.
6. Schedule the WebInstallFromUrlCommand, passing it the Tracker and current web
contents. This leverages the shared_web_contents to fetch and parse the manifest.
a. 1 parameter signature – validate that the fetched manifest has_custom_id
b. 2 parameter signature – validate that the fetched manifest’s id matches what was
passed in to navigator.install.
i. If either of these checks fail, Abort and report a kDataError.
ii. Also abort and report a kDataError for the following error scenarios:
1. No manifest present.
2. Manifest has no icons.
3. Loading install_url failed or timed out.
4. Install_url redirects which are currently not supported.
Future task for evaluating support for redirects:
crbug.com/471021583.
c. Prompt the user to accept or cancel installation via a modified version of the
existing install dialog, either WebApp[Simple/Detailed]InstallDialog.
i. If accepted, finalize the install and launch the app.
ii. If declined, Abort with AbortError.
d. If the install is successful, kSuccess and the app’s manifest_id will be
returned, otherwise kAbortError and an empty string.
7. For successful installs, we store the invoking URL for each newly installed web app, via
an installed_by field in web_app.h, for use by the safebrowsing team later. See
Security section for motivation.
Background document, check for app already installed
install_url and optionally manifest_id are the only pieces of information available to
determine whether a background document is already installed. (manifest_id is only
available if the 2 parameter API signature was called). FindBestAppWithUrlInScope (code)
is the primary registrar function for getting an AppId from an arbitrary URL, but it introduces
issues with nested apps.
For example – mail.google.com and mail.google.com/chat are 2 different apps, each with
their own manifest and scope. If only mail.google.com is installed, and the user calls
navigator.install(“mail.google.com/chat”), this will match and launch
mail.google.com.
Given this, we will prioritize manifest_id for app lookups –
1. If manifest_id is present, GenerateAppIdFromManifestId and check if the
generated AppId is in the registrar via AppMatches*. If a match is found, return the
generated AppId. Otherwise return nullopt.
10.
2. If nomanifest_id, then fall back to FindBestAppWithUrlInScope* with the
install_url.
*Both of these lookups are performed via unsafe registrar access. This is OK, as the
actual app launch queues a command which handles acquiring a lock, verifying the app
is still in the registrar, and launching. This avoids holding a lock the entire time the dialog
is open, as that has a deadlock potential if the user leaves the dialog open and tries to
uninstall the app from the OS.
Note!
This approach does not guarantee that the 1 parameter signature will launch the correct nested app. If no
manifest_id is given, and FindBestAppWithUrlInScope detects that any app has the
install_url in scope, that app is treated as a match and will be launched. A manifest_id should be
passed to guarantee the correct app.
WebAppFilter for background document
(Current document uses InstalledInChrome)
Both registrar lookups outlined above require a WebAppFilter to specify the types of web
apps we’re looking for. For Web Install, we specify that the app must
(1)Have been installed by the user
OR
(2)Launch in a standalone window
This effectively excludes the Google default apps, which open in a browser tab by default. It also
excludes policy apps.
WebAppLaunchDialog
While current document installs can leverage the intent picker bubble view to launch,
background document installs cannot, as there’s no web contents to reparent. For background
installs we’ve created a new dialog – WebAppLaunchDialog. It mimics the existing install
dialogs, namely WebAppSimpleInstallDialog [code], which uses
DialogModel(Builder) [code] to declaratively create a dialog with the app’s title and icon.
See Open App Dialog section below for a mock. Just like the install dialog, the launch dialog is
also tab modal, meaning it blocks interaction with the tab it’s anchored to, but dismisses on tab
switch (or close). Note that it persists when the window loses focus.
To minimize code duplication and make maintainability easier (especially around important
security issues), create a new WebAppModalDialogDelegate which lifts the shared
functionality out of the existing WebAppInstallDialogDelegate.
11.
On acceptance ofthe dialog, we resolve the promise with kSuccess. We then queue a
WebAppLaunchCommand [code] which handles safely checking that the app is still in the
registrar, and launching. If the app is no longer there, or the dialog closes for any other reason,
resolve with kAbort.
[WIP] – Post OriginTrials – Background document install – additional functionality
If no app with a matching manifest_id is initially found, we will queue a
LoadUrlUntilIdleJob. This will load the spare web contents with the specified
install_url and wait for a predetermined time to allow redirects, allowing up to a maximum
number of redirects.
Once the site stops redirecting, a WaitForManifestJob will wait for the manifest to load, up
to a predetermined time. If a manifest is not found within this time, then Abort with
kDataError.
Once a manifest is found, WebInstallFromUrlCommand will proceed with the manifest id
validation, and showing the install dialog as outlined above.
FetchManifestAndInstallJob (similar to the existing
FetchManifestAndInstallCommand) which will fetch the manifest, generate
WebAppInstallInfo, and download icons for the app. Its manifest id will be validated against
the provided manifest_id.
[WIP] – Post OriginTrials – Code hygiene – command refactoring
Eventually, we will also refactor FetchManifestAndInstallCommand and
WebInstallFromUrlCommand by pulling out their shared functionalities into jobs. Currently,
the commands are as follows –
FetchManifestAndInstallCommand WebInstallFromUrlCommand
12.
UX changes
See WebInstall UX review - Google Docs for more screenshots.
Install Dialog
For background document installs, we will add a string to the relevant existing web app install
dialogs (WebAppSimpleInstallDialog and WebAppDetailedInstallDialog) with the origin of the
app being installed.
See Web Install UX review - Google Docs (pages 9, 10) for the motivation..
(SimpleInstallDialog mock)
Open App Dialog
If navigator.install() is invoked for an app that is already installed, we will show the existing app
launch dialog.
Current document installs will show the normal open dialog, anchored to the omnibox icon.
Background document installs will anchor the dialog to the center of the window, since there
won’t be an omnibox icon.
13.
Incognito/Guest Mode
Web appsare not installable in incognito or guest mode by default, and we want to avoid this
API being a vector for detecting Incognito/Guest mode. The web install permission will be
disabled by default for Incognito/Guest mode which will reflect in the Page Info Flyout UX. This
aligns with how idle detection and notifications permission are handled in unsupported modes.
We will show a new TriggerInstallNotSupportedDialog explaining Web application installation is
not supported in Incognito/Guest mode with the ok button to dismiss, and when the user closes
the dialog it will be reported back to the web content as a denial of installation. This is a nicety
for end users to know why their action did not result in an app install.
14.
Screenshots of prototype.Content / Strings are finalized (as of 7/25/2025).
Security
See this section for a high level overview of how data is flowing between processes.
Note that this API only allows sites to ask to install apps and that even if the install permission is
granted, the user must accept installation of each individual app requested by the site.
Spoofing:
The main security threat is spoofing, which is an existing threat for web apps installed from
browser UI. We will use the same mitigation as browser UI and show the app’s origin
underneath the app name so users can verify the actual vs expected origin.
chrome:// and file:// urls:
Keeping in line with existing web app installation via browser UI, Web Install API will not be
allowed to install chrome:// or file:// urls, and the API will not be enabled for these urls either.
Spamming:
Although the following nuisance came up in discussions around security, it is not deemed a
security issue. A site could spam the user with install prompts as they interact with the page,
and this is mitigated via the web app installation permission and requiring user activation to
show the prompt. The user can also remedy their situation by simply closing the malicious tab.
If additional protections are needed, a way to further improve usability is to:
● Ask the user if they want to revoke the permission if the browser detects that the API has
been used too frequently
● Prevent API usage from sites flagged by safebrowsing
Safebrowsing
Add an installed_by field to the web app database. We will store the 10 most recent, unique
URLs which invoked this API to install each app.
The motivation for this being, we’re not ready to hook into SafeBrowsing yet. We need to store
the data so we can eventually retroactively populate a distribution graph by connecting an app
15.
installed via thisAPI to the site that installed it. This enables finding distribution points to help
combat potential API prompt abuse.
Privacy
Minimal set of installation errors:
The original proposal included a growing list of app installation errors, which were reduced down
to just 2 error cases during TAG review due to fingerprinting risk. If the manifest id does not
meet the criteria for each signature, we report a kDataError. All other installation failures,
including the user dismissing the dialog without installing, will be reported back to the site as a
kAbortError.
Incognito/Guest Mode:
If the user is in incognito or guest mode where web apps are not installable and they initiate a
navigator.Install() request, show a new TriggerInstallNotSupportedDialog that rejects the
promise with kAbortError when the dialog closes. This prevents Incognito/Guest mode
detection from developers.
See UX changes for details.
Functional and Unit Testing
Test Approach
Our main goal is to test all 3 API signatures end-to-end, beginning with the JavaScript API
execution, and ending with verification that the JS promise resolved with the expected values,
and the web app was either installed or not. We will also test error cases that should not result
in an app install, such as bad JS input, bad web app manifest states, and permissions policy
being denied.
Ideally, we’d also like to write web platform tests (WPT), but this is on hold indefinitely, as the
ability to install web apps is not yet supported in the testing framework.
Test Cases
● Expected use cases – 0, 1, and 2 parameter signature installs.
● Permissions policy – Required only for background document installs. Installing the
current document (with any of the 3 signatures) should succeed without permission set.
● General manifest validation – as with any feature installing web apps, the manifest must
exist and meet certain criteria for the document to be installable
16.
● Manifest idvalidation – 0 and 1 parameter signatures require a manifest with an explicit
`id` field
● Bad JS input – the API requires valid strings. Additionally, the install_url must be a valid
URL.
● User activation – the API must be invoked with a user gesture
● LoadUrlUntilIdle – some urls may redirect, once or many times, until the ultimate
install_url is reached. Simulate this in tests.
● WaitForManifest – some sites wait to load their manifest. Simulate this in tests.
Automated Test Cases
c/b/web_applications/commands/web_install_from_url_command_browsertest
.cc
● Tests background document install and all related edge cases/error handling
● LoadUrlUntilIdle and WaitForManifest will likely go in here as well.
c/b/web_applications/web_install_browsertest.cc
● Tests current document install, as well as generic failures due to the bad JS input
described above.
Manual Test Cases
Manual testing has been done with this site – https://kbhlee2121.github.io/pwa/web-install/
Telemetry
On the Blink side, we implemented WebDXFeature::kDRAFT_WebInstallAPI, a WebDX
use counter that records all 3 signature usages. See Chromium Docs - UseCounter Wiki.
On the browser side, we added a UMA, Webapp.WebInstallApi.Result, that records
notable outcomes of the browser’s implementation –
Installation results enum, WebInstallApiResult:
○ Success
○ Success, already installed (an app with the same manifest_id was found)
○ Install permission denied
○ Unsupported profile (e.g. incognito, guest, managed browser)
○ Canceled by user (user clicks “cancel” in the install dialog)
○ Installation failed (failures that occur after the user clicks “install”)
○ No custom manifest id (0, 1 param signatures)
○ Manifest id mismatch (2 param signature)
○ Unexpected failures (misc. bucket)
17.
In the interestof being able to identify individual bad actors, we will also add 2 UKMs that use
the same WebInstallApiResult enum. One UKM will be keyed on the requesting site’s URL
(the site that called navigator.install), and the other UKM will be keyed on the target site’s URL
(the URL passed into navigator.install).
Open Questions
● Is there additional information that should be captured in the install dialog to inform users
about installation?
○ Mostly resolved. See UX Changes section for details.
● What are the upcoming plans for the install dialog? Which version should we
match/modify for Web Install?
○ Mostly resolved. See UX Changes section for details.
● Is it appropriate to prompt the user to ok open the app if it was previously installed from a
different source?
○ Resolved. Yes, is it appropriate. See WebAppFilter for background document for
details.
● Is there a security or privacy risk from the background tab navigation that occurs before
the user accepts/rejects the install prompt? Or is this risk similar to embedding an iframe
within a page?
Appendix
Past API Discussions
Both of these are irrelevant now, as we’re no longer considering the API in terms of same and
cross origin.
● Should the manifest_id member be required for both same- and cross-origin scenarios?
○ Web Install API - Cross-Origin · Issue #946 · w3ctag/design-reviews (github.com)
○ [Web Install] Require manifest_id · Issue #746 ·
MicrosoftEdge/MSEdgeExplainers (github.com)
● Allow/block cross-origin install if not specified in manifest
MLInstallabilityPromoter & background install dialog
considerations
Decision: Ultimately, we decided to go with Alternative #2 listed below. See this discussion in the
pwa-dev group for more context – tracking installs per web contents (Web install API)
18.
Context: The existingmechanisms to show an install dialog (and do the install) require an
MLInstallabilityPromoter/Tracker to be created, which are associated with the current
web contents to be installed.
CreateWebAppFromCurrentWebContents/Manifest (code)
- Creates ML promoter from web contents, creates ML tracker and registers the
current web contents install.
- Schedules FetchManifestAndInstallCommand
- OnIconsRetrievedShowDialog runs its dialog_callback -
OnWebAppInstallShowInstallDialog
Issue: For background document installs, we only have the shared_web_contents, which is
not usable to create the MLInstallabilityPromoter. Additionally, we don’t believe it even makes
sense to inform the model of background installs, since there’s nothing to really promote, and no
user engagement to evaluate.
WebInstallFromUrlCommand::OnIconsRetrievedShowDialog (code)
- (New command added for Web Install API, for background documents
specifically)
- We want to call OnWebAppInstallShowInstallDialog, but we only have
shared_web_contents.
Affected classes/functions: places that make/require the ML objects within the install dialog
flow
WebAppDialogUtils::OnWebAppInstallShowInstallDialog (code)
- Takes the ML tracker as a unique_ptr parameter (also the
initiator_web_contents)
- Decides which of the 4 install dialogs to show
a. WebAppSimpleInstallDialog (code)
b. WebAppDetailedInstallDialog (code)
c. WebAppDiyInstallDialog (code)
d. CreateShortcutConfirmationView (code)
- Passes the ML tracker to each dialog class
ShowWebAppDetailedInstallDialog/ShowSimpleInstallDialogForWebApps
(code)
- Takes the ML tracker
- Creates the dialog itself, as well as the dialog delegate
WebAppDialogDelegate (code)
- Takes the ML tracker as a constructor parameter
- CHECKs the tracker and reports the result on each button click
19.
Our proposal: Allowthe ML model objects to be optional where necessary (enforce their
existence with CHECKs to preserve existing invariants), and create no new classes.
Alternatives considered:
1. Diverge and create alternate versions of all the affected classes/functions
- Making a new dialog delegate would require a lot of code duplication, which would then
probably necessitate a shared parent class, when the ML tracker is only used to report
which action the user took in the dialog.
- Additional consideration – because we want to add the origin/publisher string to the
install dialog (see UX review doc), we would also need 2 new dialog classes (such as
BackgroundSimpleInstallDialog and BackgroundDetailedInstallDialog),
which would also require some code duplication, and add to maintainability issues with 6
total dialogs needing to be maintained.
2. Just use the current document’s web contents (ie. the contents that initiated navigator.install)
to make the ML objects (would be passed in from WebInstallServiceImpl (code), who
queues the WebInstallFromUrlCommand)
- It doesn’t make sense to register installs against the current web contents and inform the
model, when we’re installing something else.
- Although, it is possible we’ll end up needing to plumb this somewhere anyways, as the
dialog will need something to anchor to?
Future Work (WIP)
Rejected Alternatives
Manifest Addition: install_sources
The installation target will indicate which origin may install it via properties in the web app
manifest:
● install_sources: An array of objects used to allow or block specific origins from
installing this app
○ origin: The origin of the install target. Note that wildcards are not permitted.
○ action: Either ”allow” or ”deny”
● allow_all_install_sources: A boolean used to allow or block all origins not
mentioned in the above list of install sources. Note that same-origin installation is
permitted even if this is set to false.
Additionally, the install target can add an informational install_url to the manifest that
indicates to installation sources which url is optimal for installing the app. This field is
20.
informational, intended foruse by the install source, and is not used by the browser when
installing an app via the Web Install API.
This was rejected after TAG discussions.
Allowing/Blocking Install Sources
The installation target will use the install_sources manifest member to specify an allow-list
of origins that may install this app. If this member is not empty, then it is assumed that all other
install sources are blocked, and allow_all_install_sources is ignored.
To allow installation from any install source, the install target can set
allow_all_install_sources to true. If set to false, it will block all cross-origin installations,
but same-origin installation will be permitted.
These manifest fields will be sent to the browser via blink::mojom::Manifest, and stored
in/read from the WebAppDatabase/Registrar.
navigator.getInstalledApps()
An origin can request the list of manifest_ids associated with apps installed by that origin via
navigator.getInstalledApps().
This API will follow a similar path to navigator.install(), living on
NavigatorWebInstall and being passed to the browser via
blink::mojom::WebInstallHost. It will also be gated behind the Web Install permission
and can only be called from the main frame.
WebInstallHost will query the WebAppRegistrar for all apps which list the current origin in
their install_sources, and return a list of manifest_ids of matching apps.
To protect users from fingerprinting, it will not return the ids of any apps that were installed by
other origins or via browser UI. The site can use this information to show an “Open” button
instead of an “Install” button for sites it has installed.
Privacy considerations:
To minimize the fingerprinting surface created by getInstalledApps(), clearing the
cache will also clear the list of installed apps. Note that this can only be called from the
main frame, so subframes will not be able to call getInstalledApps() as a tool to
fingerprint users.
21.
Efficiency Improvement forSame-origin Installs
The 0 param install() method uses the current document and doesn’t need to load
additional web content in the background. To prevent an additional trip between the browser and
renderer, we could pass the parsed manifest data to the browser when triggering a
current-document install. However, this overcomplicates the feature and opens the floor for
additional bugs, so this approach was rejected.
Web install permission
Persistent omnibox status icon (intent picker)
Some permissions (like Location, Notification, etc…) show an icon/button in the omnibox
indicating the current status of that permission (and clicking on it allows you to change that
status).
This experience makes sense for permissions which, once granted, an origin can continue to
leverage without further user interaction. Given that Web Install usage requires a user gesture
each time, we do not believe that the Web Install permission should be treated this same way
(in order to reduce UX noise and wasted space in the omnibox).
Custom text for blocked permission omnibox pill
Currently, when you block a permission, this “Not allowed” pill slides out, then disappears.
We considered customizing this string to say "Install web apps blocked".
As of 8/27/24 - This scenario is not deemed required nor supported so this string will not be
added. Keeping this string available here in case we want to support this scenario.
Archive - Permissions Strings
Permissions Strings Review Approvals
Name Required LGTM/NLGTM
David Warren Yes LGTM
Kristin Lee No LGTM
Diego Gonzalez No
22.
Amanda Baker No
PermissionsString Review Notes
Separate doc with a table of all the mocks/string content/notes (Same as above strings and as
the table below):
https://docs.google.com/document/d/1C-U0u1BMZcHuErpoL7Q0XPZ9OjKMN9_gmvejLj-1Drhtt
ps://docs.google.com/document/d/12nSXJLm8mW0gWZ_yjlXfrV8r9gwJliVt4WVa-209-KA/edit?u
sp=drivesdk&disco=AAABo17KyaM8/edit?usp=sharing
Mock Content
(writers only, leave comments)
Notes
(writers only, leave comments)
Web app installations
Sites usually ask to install web applications to
enhance your experience on the site
● Sites can ask to install web apps on your
device
● Don’t allow sites to install web apps on
your device
Not allowed to install web apps on your device
Allowed to ask to install web apps on your
device
Changelist
1. Title: Dan is suggesting working in
“installations”, which seems like a
nice way to better convey “install”
while not using a verb. I argue for
“applications” below, but that would
now be too long. “Web app” seems
fairly understandable, and then we
shift to “applications” in the body.
2. Description: The current proposal,
“Sites can install web applications”
feels wrong because this may not be
true depending on the user’s choice.
23.
Web app installationsapplications
Canask to install web apps
We had trouble understanding the context of
this string. I’ve documented it below.
1. If you're viewing the New York Times
and you open Page info, only
permissions for which you've
changed the default value should
appear in the list. In the screenshot
below, I chose to block the New York
Times from using my Location (I think
Pop-ups and redirects is an exception
in that it appears here even in default
state, probably because a site can
abuse popups). Whether the user has
chosen to Block or Allow Location (as
long as the permission isn't in the
default state), the permission should
appear in this list without any
subtext.
2. If the user chooses "Reset
permissions", Page info immediately
reloads. See the screenshot below.
Now, we have a string beneath
24.
Location to validatethe user's action.
We're showing them the result of the
"Reset permissions" action. In this
case, Location goes back to the
default state which means that the
New York Times can now prompt me
the next time I visit to use my
location. Note also in this screenshot
that the "Reset permissions" button
is gone, because all permissions for
the New York Times are in the default
state.
3. Page info as shown above will persist
as long as the New York Times
remains open. I closed the tab and
then returned to the New York Times
and Page Info looks like the
screenshot below. Location no longer
appears, because it's simply in the
default state and the user no longer
needs confirmation of their past
25.
choice to "Resetpermissions".
Install web apps?
Be able to install web apps
Screen reader announcements:
● "Install web apps allowed"
● "Install web apps not allowed"
Original dev spec permissions content
Strings used on chrome://settings/content:
● Icon:
● Title: “Web applications”
● Description (menu item) (Same as Ask string in details page): “Sites can ask to install
web apps on your device”
26.
● Label forwhen used mid-sentence
(IDS_SITE_SETTINGS_TYPE_WEB_APP_INSTALLATION_MID_SENTENCE): “web
apps”
○ As of 8/30/24: “web applications”
The permission menu item found on chrome://settings/content
Screenshot of the Page Info tooltip as one example where the mid-sentence label is used
Strings used on chrome://settings/content/webApplications:
● Description (details page): “Sites usually ask to install web apps to enhance your
experience on the site”
● Default behavior:
○ Ask: “Sites can ask to install web apps on your device”
○ Block: “Don’t allow sites to install web apps on your device”
● Customized behavior:
○ Blocked Exceptions: “Not allowed to install web apps on your device”
○ Allowed Exceptions: “Allowed to ask to install web apps on your device”
27.
The permission detailpage found at chrome://settings/content/webAppInstallation
Strings used in the notification bubble:
● Omnibox text: “Ask to install web apps?”
○ Maybe: “Install web apps on your device?”
OR
“Install web apps?”
● Bubble text: [site name] wants to “Be able to install web apps”
○ Previously: “Ask to install web apps”
● Omnibox text representing a blocked or expired request for Web Install: "Install web
apps blocked"
○ As of 8/27/24 - This scenario is not deemed required nor supported so this string
will not be added. Keeping this string available here in case we want to support
this scenario. A permission blocked is already represented by the “blocked” icon,
Page Info Bubble and “not allowed” bubble text in the omnibox:
● Screen reader announcements:
● Confirming Web Install permission request has been granted: "Install web apps
allowed"
28.
● Confirming WebInstall permission request has been denied: "Install web apps
not allowed"
Mock:
(Actual) In Chromium:
Strings used in the Page Info bubble:
● Title: “Web applications”
● Description: “Can ask to install web apps”
29.
Icons (Allowed andBlocked) (In need of UX review):
Link to the SVG of install-desktop icon (no slash):
https://drive.google.com/file/d/1xxEqIB2BPLe5JTAvSnu9kYtGKfcGFkYD/view?usp=sharing
Link to the SVG of the new install-desktop-off icon (has the slash):
https://drive.google.com/file/d/1dxDHeF7gohg9qxA91HrAEiCQAefyE-81/view?usp=sharing
Screenshot of the install-desktop and install-desktop-off icons (Currently available in code base
with
30.
feature flag, `WebAppInstallation`.See below for steps).
To view these icons in Settings:
1. Launch chrome with feature flag: WebAppInstallation
2. Navigate to chrome://settings/content/webApplications
31.
GIF file ofPage Info that exemplifies the issue with the current icons:
1l8aLbzTkFecEt3wmLBATK1BSU23IFpf2https://drive.google.com/file/d/1l8aLbzTkFecEt3wmLB
ATK1BSU23IFpf2/view?usp=sharing
To view these icons in the Page Info:
1. Launch chrome with feature flag: WebAppInstallation
2. Navigate to any web app site (e.g., https://kbhlee2121.github.io/pwa/web-install/) and
open the Page Info Bubble with the button in the left side of the omnibox.
Install element test page
Requires --enable-features=InstallElement,
OR enabling about://flags/#web-app-install-element,
OR an origin trial header for InstallElement
Executes:
<install></install>
Executes:
<install installurl="https://mustjab.github.io/"></install>
Executes:
<install installurl="https://diek.us/bubble/" manifestid="https://diek.us/bubble/"></install>
DataError cases - manifest id validation (install
element)
1 param, background doc, NO id in manifest
Executes:
32.
<install installurl="https://amandabaker.github.io/pwa/web-install/index.html"></install>
2 params,background doc, YES id in manifest, mismatched
input
Executes:
<install installurl="https://mustjab.github.io/" manifestid="mismatched-manifest-id"></install>
2 params, background doc, NO id in manifest, mismatched
input
Executes:
<install installurl="https://amandabaker.github.io/pwa/web-install/index.html"
manifestid="mismatched-manifest-id"></install>
More Error Cases
2 params, background doc, invalid type for install url
Executes:
<install installurl=12345 manifestid="https://diek.us/bubble/"></install>
2 params, background doc, invalid type for manifest id
Executes:
<install installurl="https://diek.us/bubble/" manifestid=67890></install>
2 params, background doc, variable is a number for install url
33.
Executes:
<script>const numUrl =99999;</script>
<install id="installVarNumberUrl" manifestid="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarNumberUrl').setAttribute('installurl',
numUrl);</script>
2 params, background doc, variable is a number for manifest id
Executes:
<script>const numManifest = 88888;</script>
<install id="installVarNumberManifest" installurl="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarNumberManifest').setAttribute('manifestid',
numManifest);</script>
2 params, background doc, variable is null for install url
Executes:
<script>const nullUrl = null;</script>
<install id="installVarNullUrl" manifestid="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarNullUrl').setAttribute('installurl',
nullUrl);</script>
2 params, background doc, variable is null for manifest id
Executes:
<script>const nullManifest = null;</script>
<install id="installVarNullManifest" installurl="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarNullManifest').setAttribute('manifestid',
nullManifest);</script>
2 params, background doc, variable is undefined for install url
34.
Executes:
<script>let undefinedUrl;</script>
<install id="installVarUndefinedUrl"manifestid="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarUndefinedUrl').setAttribute('installurl',
undefinedUrl);</script>
2 params, background doc, variable is undefined for manifest id
Executes:
<script>let undefinedManifest;</script>
<install id="installVarUndefinedManifest" installurl="https://diek.us/bubble/"></install>
<script>document.getElementById('installVarUndefinedManifest').setAttribute('manifestid',
undefinedManifest);</script>