Skip to content

Latest commit

 

History

History
558 lines (417 loc) · 24.5 KB

explainer-manifest-url.md

File metadata and controls

558 lines (417 loc) · 24.5 KB

User-Initiated Installation of a Web Application - Manifest-URL Design

Authors

Participate

Status of this Document

This document is a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization.

Table of contents

Introduction

The <install> element is a declarative HTML element that allows web developers to offer installation of web applications directly from a page. It renders a user-agent-controlled button whose text and iconography are determined by the browser, providing a strong signal of user intent and protection against spoofing. The element is part of the Permission Element family, sharing the same security model, styling restrictions, and validation infrastructure.

Relationship to other proposals

  • Web Install API (MSEdgeExplainers) -- defines the backing install algorithm, manifest fetch and validation pipeline, consent UI contract, error taxonomy, and cross-origin / sandbox / activation gates. Normative for backend behavior. This document references but does not re-specify those algorithms.
  • Permission Element spec (WICG) -- defines the base infrastructure that <install> is built on: HTMLCapabilityElementBase, InPagePermissionMixin, the blocker system, intersection observer visibility checks, and styling restrictions. This infrastructure ships in production via the <geolocation> (Chrome 144) and <usermedia>elements.
  • This document -- defines install-specific properties/behaviors on top of the CapabilityElementBase and WebInstall backend.
If you're asking… See
How do I add an install button to my page? This doc
Why won't the element activate? Permission Element spec
What styling am I allowed to apply? Permission Element spec
Why did the manifest fail to fetch or parse? Web Install API
What events fire after activation? This doc

User-Facing Problem

Think about all the websites you use regularly - email, online shopping, social media, streaming sites, etc. For most users, this requires launching a browser and clicking or typing to get to those sites every time. Web applications enable developers to provide native, "app-like" experiences to end users while building on the trust set by their browser. However, for end users there's no standard, cross-platform way to acquire web applications. The process of distributing and installing web apps is both fragmented and limited:

  • Each browser has different, often hidden, entry points for installation (address bar icons, menu items, prompts). Users may not understand what the icon/prompt in the browser's address bar does, or how to deep search several layers of browser UX to add the app to their devices.
  • Users may not know that a web app exists for the site they're visiting, for a related origin they're reading about, or that "installation" is even possible on the web -- and many users don't use app stores to discover new app experiences.
  • Developers have no standard declarative mechanism to present an install action to users.
  • Cross-origin installation (e.g. an app catalog installing apps from other sites) has no built-in web platform support.

Goals

Element-specific:

  • Provide a declarative way to install web applications, requiring no JavaScript for basic usage.
  • Give the user agent control over the button's content and presentation, providing a trustworthy signal of user intent.
  • Offer progressive enhancement through fallback content for browsers that don't support the element.
  • Integrate with the existing Permission Element infrastructure for consistent security, styling, and validation behavior.

Shared with navigator.install:

  • Enable a site to install a web app identified by its manifest URL, subject to user consent.
  • Extend the functionality of beforeinstallprompt, which cannot install content other than the current loaded web application.
  • Keep the consent UI clearly attributable: the user sees which site is asking and which app is being installed.
  • Avoid creating a cross-origin probing surface: the calling site should learn as little as possible about install state, manifest contents, or app identity.

Non-goals

Element-specific:

  • Replace navigator.install(). The imperative and declarative APIs serve complementary use cases and share a backend implementation.

Shared with navigator.install:

  • Define what "installation" means. This varies by platform and browser.
  • Silent or unattended installation. User consent is always required.
  • Installing arbitrary web content that is not an app. The target must have a manifest file. See historical context
  • Installing native apps, browser extensions, or other non-PWA artifacts.
  • Reporting install state of arbitrary apps back to the caller.

Use Cases

Install me! (Same origin install button)

A web developer can ergonomically trigger the user agent's installation flow, eliminating the need to subscribe to events, or try and direct users through potentially several layers of browser UX to discover the installation entry point on their own.

This is the simplest case -- no attributes are needed:

<install></install>

Cross-origin app catalog

A web-based app store or catalog can install apps from other origins by supplying the manifest URL of each listed app:

<install manifest="https://music.youtube.com/manifest.webmanifest"
</install>

Suite of web apps

A productivity suite can install related apps from the same origin:

<install manifest="https://suite.example/mail/manifest.webmanifest">Install Email</install>
<install manifest="https://suite.example/calendar/manifest.webmanifest">Install Calendar</install>
<install manifest="https://suite.example/tasks/manifest.webmanifest">Install Tasks</install>

Proposed Approach

DISCLAIMER!

As noted in the related proposals section, this approach assumes familiarity with the permission element spec, which outlines in detail the element's behavior, including styling and activation restrictions, error handling, etc.

A declarative <install> element that renders a button whose content and presentation is controlled by the user agent. Similar to other permission elements (e.g. <geolocation>), the user agent's control over the element's content means that it can make plausible assumptions about a user's contextual intent. Users who click on a button labeled "Install" are unlikely to be surprised if an installation flow begins.

Element content

The element renders standardized text and iconography controlled by the user agent:

A button whose text reads "Install", with an icon signifying the action of installation.

Element attributes

  • manifest -- URL of the web app manifest to install.
  • manifestId -- The manifest id of the app to install.

Both attributes are optional. The developer may omit manifestId, if and only if the JSON at manifest contains an id field.

As an added convenience, the developer may omit manifest, in which case the currently loaded page's manifest is targeted for install.

Sample Code

<!-- Install the current page. -->
<install></install>

<!-- Install a specific app by its manifest URL. -->
<install manifest="https://app.example.com/manifest.webmanifest"></install>

<!-- Install a specific app whose manifest does not declare an `id` -->
<install manifest="https://app.example.com/manifest.webmanifest"
         manifestId="https://app.example.com/?source=catalog"></install>

Element fallback content

If the user agent doesn't support installation, fallback content can be rendered:

A hyperlink reading "Launch YouTube Music".

<install manifest="https://music.youtube.com/manifest.webmanifest"
         manifestId="https://music.youtube.com/?source=pwa">
  <a href="https://music.youtube.com/" target="_blank">
    Launch YouTube Music
  </a>
</install>

Activation behavior

On activation, the element invokes the install algorithm defined in Web Install API with the optionally supplied manifest and manifestId. The backend's algorithms for manifest fetch, validation, consent UI, and error mapping apply unchanged.

Example consent UI for users to review security-sensitive fields such as the app name, origin, and icon before installation proceeds:

An installation prompt for YouTube Music.

Where navigator.install() uses promise rejections with DOMException names, the <install> element surfaces outcomes through two mechanisms: pre-click validation via the InPagePermissionMixin, and post-click results via an asynchronous, bubbling InstallResultEvent (see Results, errors, and debuggability below).

What if the app is already installed?

The UA can choose to render the element as a "Launch" button and activation would follow established launch handler algorithms. UAs must ensure installation status is not exposed to side-channel attacks. See Privacy for more details.

A button showing Launch option when app is already installed.

What if the element is rendered in the installed app context?

As a convenience, the UA could hide the element in installed windows. There is also an active proposal for a media feature that answers the question "am I in an installed app window?" that allows developers to easily customize the look and feel of their installed app experience.

Results, errors, and debuggability

The element surfaces errors through two distinct mechanisms, reflecting the difference between pre-activation validation and post-activation installation results.

Pre-activation: InPagePermissionMixin

The element inherits the standard InPagePermissionMixin validation interface (isValid, invalidReason, onvalidationstatuschange) from the Permission Element spec. These cover presentation restrictions common to all capability elements (visibility, styling, occlusion, temporal cooldowns) and are not install-specific.

A violation of these restrictions prevents the element from being activated, either temporarily or permanently. Developers can inspect isValid and invalidReason, and user agents may also surface the problem in developer tooling.

Install-data problems (a malformed manifest URL or manifestId, a manifest that fails to fetch or parse, etc.) are not surfaced here. They are reported after activation as an "invalid_data" installresult (see below).

Note: The mixin also exposes initialPermissionStatus, permissionStatus, onpromptaction, and onpromptdismiss on <install>, but these members are not actionable for this element. The permission status does not determine whether installation can proceed, and install outcomes are reported through installresult.

Post-activation: InstallResultEvent

Outcomes that occur after the user clicks are surfaced by a dedicated event named installresult. Its event object is an InstallResultEvent, which carries a result attribute reporting one of three values:

result Meaning
"success" The app was installed.
"aborted" The user cancelled, or a browser-side condition prevented the install from completing. No action needed.
"invalid_data" A developer error: the element was given install data it can't act on. See note below.

Note: "invalid_data" is a developer error, not a user action. It covers a malformed manifest URL or manifestId, a manifest that can't be fetched or parsed, a manifestId that doesn't match the UA-computed id, or a manifestId with no manifest to install from. It does not disable the element: the developer can correct the markup and the user can retry. (An earlier design disabled the element and surfaced an install_data_invalid invalidReason; that behavior was removed in favor of the installresult event.)

For same-origin installs, user agents can supplement the coarse "invalid_data" result with actionable diagnostics in developer tooling, such as whether no manifest was found, the manifest could not be fetched or parsed, or required fields were missing or invalid. These diagnostics should not expose cross-origin manifest details or change the result visible to the page.

enum InstallResult { "success", "aborted", "invalid_data" };

[RuntimeEnabled=InstallElement, Exposed=Window]
interface InstallResultEvent : Event {
  constructor(DOMString type, optional InstallResultEventInit eventInitDict = {});
  readonly attribute InstallResult result;
};

dictionary InstallResultEventInit : EventInit {
  InstallResult result;
};

The result is on the event, not the element, because the install flow is asynchronous. A result on the element could be overwritten by a later attempt before the handler runs. The event is enqueued for async dispatch so timing stays consistent across all outcome paths.

Developers can listen with addEventListener:

const el = document.querySelector('install');
el.addEventListener('installresult', (event) => {
  switch (event.result) {
    case 'success':
      // The user accepted the install (the UA should take a reasonable action, such as launching, if the app is already installed).
      break;
    case 'aborted':
      // The user cancelled, or a browser-side condition stopped the install. No action needed.
      break;
    case 'invalid_data':
      // The manifest / manifestId was invalid. Fix the attributes and retry.
      break;
  }
});

The oninstallresult content attribute works too:

<install manifest="https://app.example.com/manifest.webmanifest"
         oninstallresult="report(event.result)"></install>

As does the matching IDL property (el.oninstallresult = ...).

When the user agent dispatches installresult it sets bubbles = true, so a page with several <install> elements can subscribe once on a common ancestor and read event.target to tell them apart.

// One delegated listener on the catalog container handles every <install>
// inside it; event.target identifies which app the result belongs to.
document.getElementById('app-catalog').addEventListener('installresult', (event) => {
  console.log(`${event.target.id}: ${event.result}`);
});

The initial result set is deliberately coarse and may be refined over time (e.g. narrowing "aborted" to specifically mean user cancellation). How much a cross-origin installer should learn about an outcome is tracked in Open Questions.

Alternatives Considered

See Web Install API alternatives for full analysis.

Install by installurl

An alternative design where the element takes a page URL (installurl) instead of a manifest URL, and loads the page in the background to discover its <link rel="manifest">.

This has the advantage of simpler developer ergonomics for catalogs (page URLs may be more stable than manifest URLs), but it introduces a variety of security, privacy, and performance concerns.

Declarative install with <a>

<a href="manifest_url" rel="install">

The Web Install API proposal considered a different declarative approach. This gives the user agent less control over the content and presentation but has the advantage of built-in progressive enhancement. The <install> element approach was chosen because it gives the user agent full control over the button's rendering, consistent with the PEPC model.

Alternative element names

Names like <pwa> or <webapp> could more broadly describe the range of behavior (install and launch). <install> was preferred because launching is a privacy-preserving fallback rather than the primary purpose.

Open Questions

See Web Install API's Open Questions for shared open questions. Below are element-specific.

Can the element pre-fetch the manifest to render app metadata?

Rendering the app name, origin, or icon in the element would provide a stronger signal of user intent but introduces performance, UX, security, and accessibility complications (e.g. long app names, icon contrast ratios, button layout changes).

A button showing Launch option when app is already installed.

Will this work with WebXR/WebGL scenarios?

No, this is a known limitation of the element-based approach.

The HTML in Canvas proposal makes this possible, but additional consideration is needed to avoid privacy/security leaks. See #9.

How does it behave in iframes?

Currently the element can only be activated in top-level browsing contexts for security purposes. Same-origin iframes are unlikely to pose a risk and may be supported in the future.

How does it behave in sandboxed contexts?

Currently the element is disabled in all sandboxed contexts. If a use case for installing from a sandbox presents itself, a strict allow-list can be implemented. This decision is open to reevaluation.

What text should be in the button?

The button's rendering is implementation-defined. User agents may combine an action verb with the application's origin, render the app's name if trusted, or use other approaches. It is worth discussing what considerations user agents should pay attention to, but specifying the content too precisely would be unhelpful.

Handling long names and origins

User agents will need to consider how to handle very long words, including appropriate resizing, eliding, and truncation logic (similar to what the installation dialog already implements). User agents should apply the same considerations they use elsewhere for displaying origins and names.

What result information should be exposed to the installing origin?

The InstallResultEvent result values and navigator.install() promise rejections share the same underlying question: how much should the installing origin learn about the outcome of an install attempt, particularly in the cross-origin case?

This is a shared backend concern. See the Web Install API explainer's privacy section.

Accessibility, Localization, Privacy, and Security Considerations

Accessibility

Like all permission elements, <install> renders as a button and inherits standard button accessibility semantics, including keyboard navigation and focus management. The default tabindex is 0. It should have a "Button" role in the accessibility tree, and screen readers should announce the element's text content (controlled by the user agent). The element should also be legible in Dark Mode, High Contrast, etc, and work with system keyword CSS keywords.

Localization

The element observes the lang attribute to select localized text for the button label (e.g. "Install" in the page's language). App names from manifests may also be localized based on browser language.

Privacy

A large portion of the privacy concerns are shared with navigator.install, and can be found in the API's privacy section.

Anything related to the element, but not <install>-specific can be found in PEPC privacy.

<install>-specific concerns -

  • The element's rendering must not reveal whether an app is installed. If UAs chose to render the "Launch" state, they must consider/mitigate the following concerns:
    • Width differences between "Install" and "Launch" states
    • Any detectable state via the DOM, CSS content, etc
    • SVG foreignObject considerations
      • Tainting the canvas for readback
      • Limit or completely restrict install functionality

Security

Similar to privacy, only the element-specific security concerns are listed here. For full details reference the API security section.

  • The element inherits PEPC presentation restrictions: visibility checks, contrast ratio requirements, font size bounds, occlusion detection, and temporal cooldowns. These prevent clickjacking and ensure the user can see and understand what they're clicking.
  • The element cannot be activated in cross-origin subframes, fenced frames, and all sandboxed contexts.

Stakeholder Feedback

References & Acknowledgements

This proposal builds on the Permission Element (PEPC) infrastructure and the Web Install API.

Many thanks for valuable feedback and advice from:

  • Marcos Cáceres
  • Diego Gonzalez
  • Lu Huang
  • Alex Russell
  • Arthur Sonzogni
  • Daniel Murphy
  • Mike West