-
Notifications
You must be signed in to change notification settings - Fork 37
Add manifest scope improvements doc #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gh-pages
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||
| # Web App Manifest Scope: Problems and Proposed Improvements | ||||||
|
|
||||||
| **Author:** Lu Huang (luhua@microsoft.com) | ||||||
|
|
||||||
| ## Introduction | ||||||
|
|
||||||
| The web app manifest `scope` member defines which URLs belong to an installed web application. Today, scope is a single URL prefix — e.g., `"scope": "/app/"` — and every browser behavior tied to "is this URL part of the app?" inherits that single boundary. This was adequate when scope only governed display-mode switching (standalone vs. browser chrome), but as the platform adds more scope-dependent features — **navigation capturing**, **launch routing** — a single prefix is no longer expressive enough for real-world applications. | ||||||
|
|
||||||
| The [`scope_extensions`](https://wicg.github.io/manifest-incubations/#scope_extensions-member) feature — now shipping in Chromium — addressed the multi-origin dimension of this problem by letting an app extend its scope to additional origins. When `scope_extensions` was designed, fine-grained same-origin scope control (path exclusions, URL pattern filtering) was [explicitly listed as a non-goal](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md#non-goals) to keep the proposal focused. With `scope_extensions` shipped, the time is right to tackle these harder problems. The `scope_extensions` explainer's ["Future work"](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md#future-work-under-consideration) section anticipated this, suggesting include/exclude lists and URL patterns as next steps. | ||||||
|
|
||||||
| This document describes the concrete problems app developers are hitting and proposes areas of specification work to address them. Specific proposals for each area will be added in their own documents alongside this one. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Problem 1: Scope Cannot Exclude Paths | ||||||
|
|
||||||
| **The pain.** Many web apps set `"scope": "/"` because their application routes span the root of their origin. However, the same origin may also host content that is *not* part of the app experience — marketing pages, documentation, blog posts, or API endpoints. Today there is no way to say "my app is everything on this origin *except* `/blog/` and `/docs/`." | ||||||
|
|
||||||
| **Why it matters now.** Before navigation capturing, the consequence was minor: out-of-place pages simply appeared in the standalone window with app chrome. With navigation capturing, clicking a link to `/blog/post-1` on any surface (email, chat, another browser tab) *launches the installed app*, pulling the user into an app window for content the developer never intended to be an app experience. Developers are forced to either shrink their scope (breaking deep links to real app pages) or accept that non-app content appears in the app window. | ||||||
|
|
||||||
| **What developers need.** A way to *exclude* specific paths or path patterns from scope, so that scope reflects the true boundary of the application. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Problem 2: Navigation Capturing Scope Cannot Differ from App Scope | ||||||
|
|
||||||
| **The pain.** Even when a developer is comfortable with a broad app scope (all pages *should* render in standalone mode), they may not want every in-scope URL to *capture navigations* from outside the app. For example, a productivity suite at `app.example.com` may want its `/dashboard/` and `/editor/` paths to capture navigations but not `/settings/privacy-policy`, which is better opened in a regular browser tab. | ||||||
|
|
||||||
| **Why it matters.** Navigation capturing is an all-or-nothing proposition today: it applies to every URL inside scope. Developers cannot differentiate between "this URL is part of my app" and "this URL should pull the user into the app window when clicked externally." These are distinct intents, and conflating them forces developers into workarounds (e.g., moving content to a different origin). | ||||||
|
|
||||||
| **What developers need.** A mechanism to define a **capture scope** that is a subset of (or different from) the application scope — controlling which URLs trigger navigation capturing independently of which URLs render in the app window. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Problem 3: Launch Behavior Cannot Vary by URL | ||||||
|
|
||||||
| **The pain.** The `launch_handler` manifest member lets a developer choose a `client_mode` — `navigate-new`, `navigate-existing`, `focus-existing` — but it applies uniformly to all launches. A mapping app might want `/map/` navigations to reuse an existing window (`navigate-existing`) while `/trip/new` should open a fresh window (`navigate-new`). There is no way to express this today. | ||||||
|
|
||||||
| **Why it matters.** Different areas of an app have different interaction models. Forcing a single launch behavior leads to UX compromises: either users lose context when an existing window is reused for unrelated content, or they accumulate unnecessary windows when every launch opens a new one. | ||||||
|
|
||||||
| **What developers need.** The ability to specify **multiple launch handlers**, each associated with a set of URL patterns, so launch behavior can be tailored to the content being opened. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is URL scoping enough? I'm wondering if the app might want to factor in even more dynamic information, like "what URL is my current app page on"? (e.g. YouTube PWA wants to open a video link in the current window if nothing is playing, but if something is playing wants to pause it and open a new window). If so, this might be more wholistically solved by giving the app more permissions within the context of its launch handler. Namely, letting it easily launch a new window of itself with params similar to the LaunchParams it received. |
||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Problem 4: `scope_extensions` Lacks Per-Origin Feature Control | ||||||
|
|
||||||
| **The pain.** The `scope_extensions` manifest member (and the corresponding `web-app-origin-association` file) allows an app to include additional origins in its scope. However, the association file cannot express which features the extended origin opts into. For example, `help.example.com` may want to appear inside the app window (in-scope for display purposes) but should *not* be subject to navigation capturing — external links to help articles should open normally in the browser. | ||||||
|
|
||||||
| **Why it matters.** Without annotations, extending scope is all-or-nothing: an associated origin inherits every scope-dependent behavior, including navigation capturing and link handling. This discourages adoption of `scope_extensions` because the side effects are too broad. | ||||||
|
|
||||||
| **What developers need.** The ability to annotate entries in the `web-app-origin-association` file (or in `scope_extensions` itself) to opt into or out of specific features such as navigation capturing on a per-origin basis. | ||||||
|
|
||||||
| **Prior consideration.** The `scope_extensions` explainer already [proposed](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md#future-work-under-consideration) a future `"authorize"` field in the association file — e.g., `"authorize": ["intercept-links"]` — that would let an associated origin explicitly grant or withhold permission for security-sensitive capabilities like navigation capturing. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Proposed Design Direction: Two-Layer Scope Architecture | ||||||
|
|
||||||
| The problems above point to a unifying design: **separate the concept of "app boundary" from the concept of "feature behavior within that boundary."** | ||||||
|
|
||||||
| ### Layer 1 — OS-to-App Scope (coarse-grained) | ||||||
|
|
||||||
| This layer answers: *"Does this URL belong to the application?"* It determines install-time registration with the OS, deep-link routing, and display-mode application. Because it must map to OS-level URL filtering (Android intent filters, Windows protocol/URI handling, etc.), it should remain **simple** — path prefixes, origin lists, and at most basic include/exclude patterns. | ||||||
|
|
||||||
| Design constraints: | ||||||
| - Must be expressible using primitives that operating systems support for URL filtering. Prior work on the (now-obsolete) [`pwa-url-handler` explainer](https://github.com/WICG/pwa-url-handler/blob/main/explainer.md#os-specific-implementation-notes) documented these OS mechanisms: Android [intent filters](https://developers.google.com/web/fundamentals/integration/webapks?hl=ro#android_intent_filters) via WebAPK, Windows ["Apps for Websites"](https://docs.microsoft.com/en-us/windows/uwp/launch-resume/web-to-app-linking), and iOS/macOS [Universal Links](https://developer.apple.com/ios/universal-links/). All are origin- or prefix-based — none support regex or complex pattern matching — which bounds the expressiveness of this layer. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW - A browser could choose to support a more complex syntax at this layer, and attempt to best-effort translate it to the OS it is on, erring on the side of a broader capture, with a fall-back of trying to have it handled in the browser. I wouldn't recommend it for a v1, but worth considering as a potential future while designing the API shape. So for example, on Windows or iOS/macOS the browser might try to break it into include/exclude paths with basic wildcards, whereas on Android it wouldn't have an 'exclude' option. In any case the browser would always check the URL at launch time, and if it didn't match the more complex syntax provided by the PWA it would launch it in the browser mode instead. (Or if it wanted to be super fancy, it could attempt to look up the default browser and forward it along to that. Should be possible on at least Windows, though likely not on Android/iOS). |
||||||
| - Must perform well on low-end devices (no regex evaluation at navigation time). | ||||||
| - Keep the existing `scope` member as-is for backward compatibility; introduce new top-level manifest members (e.g., `scope_exclusions` or an enhanced `scope` object) to add or remove paths. | ||||||
|
|
||||||
| ### Layer 2 — In-App Behavior (fine-grained) | ||||||
|
|
||||||
| This layer answers: *"Now that we know this URL is part of the app, how should the browser handle it?"* This is where richer pattern matching (potentially `URLPattern`) becomes appropriate, because evaluation happens inside the browser after the OS has already routed the URL to the app. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm following correctly, we're also talking about this 'layer' being where we think about a clicked link within the app, which we don't know yet if "this URL is part of the app". Small wording tweak suggestion:
Suggested change
|
||||||
|
|
||||||
| **Prior art.** The [`tab_strip.home_tab.scope_patterns`](https://wicg.github.io/manifest-incubations/#home_tab-member) feature in the Manifest Incubations spec already uses `URLPattern` to define which URLs belong to a tabbed app's home tab vs. regular tabs. This demonstrates that URL-pattern-based behavior differentiation *within* a manifest is already a proven pattern, and the work proposed here can build on that precedent. | ||||||
|
|
||||||
| Possible features governed by this layer: | ||||||
| - **Capture scope**: which in-scope URLs trigger navigation capturing. | ||||||
| - **Client mode per URL pattern**: different `launch_handler` behaviors for different URL sets. | ||||||
| - **Display mode overrides**: e.g., certain paths rendered `minimal-ui` while the rest use `standalone`. | ||||||
|
|
||||||
| This layer could be expressed as extensions to existing manifest members (e.g., a `url_patterns` field inside `launch_handler`) or as a new top-level member that maps URL patterns to behavior overrides. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Design Principles | ||||||
|
|
||||||
| 1. **Backward compatibility.** The existing `scope` member must continue to work unchanged. New capabilities are additive. | ||||||
| 2. **One syntax, three locations.** The `scope_extensions` explainer's [future work section](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md#future-work-under-consideration) identifies that fine-grained scoping mechanisms "could be reused in 3 different places: in the association file, in `scope_extensions` in the manifest, at the top level in the manifest." Whatever pattern language is adopted should work consistently across all three. Developers should learn one new concept, not three. | ||||||
| 3. **OS-mappable where it counts.** Layer 1 scope definitions must map to OS URL filtering primitives. Overly powerful syntax (full regex, complex URLPattern) at this layer risks poor performance and inconsistent OS support. | ||||||
| 4. **Progressive enhancement.** Apps that don't need fine-grained control should not need to add any new manifest members. The defaults should match today's behavior. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Summary | ||||||
|
|
||||||
| | # | Problem | Proposed Solution Area | | ||||||
| |---|---------|----------------------| | ||||||
| | 1 | Cannot exclude paths from scope | New manifest members for scope include/exclude lists | | ||||||
| | 2 | Navigation capture scope ≡ app scope | Separate "capture scope" concept (Layer 2) | | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if 2,3,4 could be combined into one thing? E.g.: "launch_handlers": [
// Patterns are evaluated in order, until one applies to the url.
{ "url_pattern": <..pattern..>,
"client_mode": "focus-existing" },
{ "url_pattern": <...pattern..>,
"client_code": "never_capture" },
...
// the pattern could be for exteneded urls! so we could say that an
// extended scope, for example, never captures.
// to maintain backwards compatibility, if none apply, the "launch_handler" entry is used as the default
],IDK if this is better - to NOT capture something into the app, you have to specify a handler for it. Questions here would be for backwards compatibility in general too - all solutions need to make sure that old versions or chrome don't break - e.g. the manifest parser doesn't 'throw away' the manifest if it fails to parse something here (I don't think that's the case for most of this)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. I've not thought about specifying capturing scope from
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this current doc is solution-agnostic enough to be merged? |
||||||
| | 3 | Single launch behavior for all URLs | URL-pattern-keyed launch handlers (Layer 2) | | ||||||
| | 4 | `scope_extensions` lacks feature annotations | Per-origin opt-in/out in association file | | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe another way to say what I said above is - maybe we can flatten the concept of the per-origin opt-in and opt-out into the same concept of specializing launch handling per url, by introducing a 'never-capture' value? But maybe there are reasons to not do that? |
||||||
|
|
||||||
| These improvements would bring the manifest's scope model in line with the growing set of features that depend on it, giving developers the control they need without sacrificing simplicity or cross-platform compatibility. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| *Related: [Chromium issue 380489997](https://issues.chromium.org/issues/380489997) — AppManifest "scope" better fine-grained control by developer* | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would lean into the 'different' rather than 'subset' direction. Though a very contrived case, I could imagine wanting to capture a URL activation, but still show it as 'outside the scope of the app' in the UI. Possibly for something like an old URL location that now attempts to guide you to the new URL, but less aggressively than an automatic redirect.