Skip to content

[Web Install] What result information should be exposed to the installing origin #1341

Description

@LiaHiscock

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

  1. kAbortError is overloaded. Only 1 of ~10 cases mapped to kAbortError is 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 where AbortError means the user cancelled the operation (see Payment Request API, File Picker API, Web Share API, Credential Management API).

  2. kSuccess leaks cross-origin information. Privacy reviewer raised the concern that origin A could enumerate the cross-origin apps that were successfully installed.

  3. manifest_id was previously returned on success. We have already agreed to stop exposing the computed manifest_id in 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)

  • AbortError rejection → user cancelled only (narrowed from catch-all)
  • DataError rejection → manifest/data issue (unchanged)
  • Promise resolves → success (manifest_id is dropped, otherwise unchanged)

Tradeoffs:

  • ✅ Best developer experience — DataError signals fixable issues, AbortError enables retry UX
  • ✅ Origin Trial developer feedback confirmed "did the user cancel?" is the most-requested signal
  • ✅ Aligns AbortError with 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

  • AbortError rejection → user cancelled only
  • DataError rejection → 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)

  • DataError rejection → 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:

  1. Stop exposing the computed manifest_id on successful install
  2. Keep kDataError for developer usability (manifest id prerequisites make this critical)

Open sub-questions

  • If we narrow AbortError to user-cancelled-only, what happens to the other ~9 internal failure cases currently mapped to it? Options: silent resolve, new error type, or collapse into AbortError anyway.
  • 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's InstallResultEvent use the same taxonomy, or can it differ? (Current position: same taxonomy, since they share a backend.)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Web Install APIImperative install for web apps from a web page.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions