MicrosoftEdge / MicrosoftEdge/MSEdgeExplainers
[Web Install] What result information should be exposed to the installing origin
Nobody has claimed this yet.
- Dominant language
- HTML
- Stars
- 1.4k
- Forks
- 286
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 8
Description
Summary
What information should navigator.install() (and <install>'s InstallResultEvent) expose to the calling origin after an install attempt — particularly in the cross-origin case, where origin A is installing origin B's app.
Current behavior
Today, the calling origin receives 1 of 3 results:
| Result | Meaning | How it surfaces |
|---|---|---|
kSuccess |
Install accepted by user (or app was already installed and user chose to open it) | navigator.install() resolves with a WebInstallResult containing the installed app's computed manifest id |
kDataError |
Manifest fetch failed, manifest invalid, no manifest_id found, manifest_id mismatch, manifest not installable |
navigator.install() rejects with DOMException("DataError") |
kAbortError |
Failure for any other reason, including user cancellation, permission denied, install already in progress, profile destruction, etc. | navigator.install() rejects with DOMException("AbortError") |
Problems with current behavior
-
kAbortErroris overloaded. Only 1 of ~10 cases mapped tokAbortErroris actually "user cancelled." The rest are internal failures (permission denied, dialog already showing, etc.). This was raised by developers during trials as a usability limitation. Furthermore, it does not match the web platform convention whereAbortErrormeans the user cancelled the operation (see Payment Request API, File Picker API, Web Share API, Credential Management API). -
kSuccessleaks cross-origin information. Privacy reviewer raised the concern that origin A could enumerate the cross-origin apps that were successfully installed. -
manifest_idwas previously returned on success. We have already agreed to stop exposing the computedmanifest_idin the resolved result, as it would let a caller probe cross-origin app identity.
Proposed options
Three options, in order of decreasing information:
Option 1: Three distinct outcomes (current approach % minor tweaks)
AbortErrorrejection → user cancelled only (narrowed from catch-all)DataErrorrejection → manifest/data issue (unchanged)- Promise resolves → success (
manifest_idis dropped, otherwise unchanged)
Tradeoffs:
- ✅ Best developer experience —
DataErrorsignals fixable issues,AbortErrorenables retry UX - ✅ Origin Trial developer feedback confirmed "did the user cancel?" is the most-requested signal
- ✅ Aligns
AbortErrorwith web platform convention (user-initiated cancellation only) - ❌ Success is still exposed
- ❌ What happens to the catch-all failure cases that were dropped from
AbortError?
Option 2: Two distinct outcomes
AbortErrorrejection → user cancelled onlyDataErrorrejection → manifest/data issue- Everything else (success, internal failures) → promise resolves
Tradeoffs:
- ✅ Preserves the retry UX signal (
AbortError) - ✅ Preserves developer diagnostics (
DataError) - ✅ Success and internal failures are indistinguishable — reduces cross-origin leak
- ❌ A bit illogical to resolve the promise for internal failures
Option 3: One distinct outcome (maximum privacy)
DataErrorrejection → manifest/data issue- Everything else (success, abort, internal failures) → promise resolves silently
Tradeoffs:
- ✅ Maximum privacy — caller only learns about fixable data errors
- ✅ Simplest API surface
- ❌ Eliminates the retry UX signal that OT developers explicitly requested
- ❌ Developers lose all ability to distinguish user cancellation from success
Side-channel considerations
Regardless of which option is chosen, the calling origin can partially infer outcomes through observable side channels:
- Focus/blur events reveal whether a browser-rendered dialog was shown
- Timing differences reveal which code path was taken (a fast rejection suggests a data error; a multi-second pause suggests user interaction with a dialog)
These side channels exist independently of the promise result and limit the incremental privacy benefit of withholding explicit results. This was acknowledged by a previous security reviewer.
Context from security/privacy review
From Nicolás Peña Moreno (June 2026):
"I think it would be better not to tell Origin A anything, if possible."
When asked whether distinguishing abort (user cancelled) from other failures is acceptable: "Yea that seems ok."
Nicolás also asked what happens when the app is already installed and the user (a) opens it or (b) dismisses the dialog — confirming this is a live concern for the already-installed case.
We have agreed to:
- Stop exposing the computed
manifest_idon successful install - Keep
kDataErrorfor developer usability (manifest id prerequisites make this critical)
Open sub-questions
- If we narrow
AbortErrorto user-cancelled-only, what happens to the other ~9 internal failure cases currently mapped to it? Options: silent resolve, new error type, or collapse intoAbortErroranyway. - What result does the caller see when the app is already installed and the user (a) opens it or (b) dismisses the dialog?
- Should the
<install>element'sInstallResultEventuse the same taxonomy, or can it differ? (Current position: same taxonomy, since they share a backend.)
Related
- Privacy analysis in WebInstall explainer
- WICG/install-element explainer references this decision in its error handling section
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the WebInstall explainer section linked under Related, then compare the install-element explainer's error-handling section and the listed privacy review concerns. Done means resolving the open questions into a documented result taxonomy for navigator.install() and InstallResultEvent, including already-installed and internal-failure cases.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100