Skip to content

Commit 132d01c

Browse files
authored
Revise 'application-context' to be a boolean media feature (#1339)
Updated the 'application-context' media feature to be boolean instead of enumerated, clarifying its behavior and usage in CSS and JavaScript.
1 parent 0bfc71e commit 132d01c

1 file changed

Lines changed: 45 additions & 24 deletions

File tree

ApplicationContextMediaFeature/explainer.md

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,31 @@ The two concepts of "is this an installed app?" and "what is the current display
7272

7373
### Syntax
7474

75-
A new CSS media feature named `application-context`, used as an enumerated media feature with discrete values:
75+
A new CSS media feature named `application-context`, used as a boolean media feature:
7676

7777
```css
78-
@media (application-context: installed) {
78+
@media (application-context) {
7979
/* Styles applied only inside an installed app window */
8080
}
8181

82-
@media (application-context: browser) {
82+
@media not (application-context) {
8383
/* Styles applied only in a regular browser tab */
8484
}
8585
```
8686

87-
The name `application-context` communicates that the feature describes the context in which the application is running, not whether the app is installed on the device globally.
87+
The name `application-context` communicates that the feature describes the context in which the application is running, not whether the app is installed on the device globally. The feature matches (evaluates to `true`) when the document is running in an installed app window, and does not match (evaluates to `false`) in a regular browser tab.
8888

8989
### Behavior Rules
9090

91-
1. **Matches `installed`** when the document is in an application context: a top-level browsing context with a manifest applied, presented in its own OS-level app window.
92-
2. **Remains `installed` regardless of display mode.** Whether the app is in `standalone`, `fullscreen`, or `minimal-ui` mode, the `application-context` media feature continues to match `installed`.
93-
3. **Only applies to top-level browsing contexts and same-origin iframes.** In cross-origin iframes, the feature matches `browser`. Same-origin iframes inherit the top-level context, since they already have access to the top-level window via `window.top`.
94-
4. **Matches `browser` in browser tabs.** Even if the same URL has an installed app elsewhere, opening it in a regular browser tab means `(application-context: installed)` does not match. The feature reflects the *current* browsing context, not global installation state.
91+
1. **Matches** when the document is in an application context: a top-level browsing context with a manifest applied, presented in its own OS-level app window.
92+
2. **Remains matching regardless of display mode.** Whether the app is in `standalone`, `fullscreen`, or `minimal-ui` mode, the `application-context` media feature continues to match.
93+
3. **Only applies to top-level browsing contexts and same-origin iframes.** In cross-origin iframes, the feature does not match. Same-origin iframes inherit the top-level context, since they already have access to the top-level window via `window.top`.
94+
4. **Does not match in browser tabs.** Even if the same URL has an installed app elsewhere, opening it in a regular browser tab means `(application-context)` does not match. The feature reflects the *current* browsing context, not global installation state.
9595
5. **Usable via `matchMedia()`.** JavaScript can query and listen for changes using `window.matchMedia()`, following standard media query semantics.
9696

9797
### Behavior Summary
9898

99-
| Context | `(application-context: installed)` | `(display-mode: standalone)` |
99+
| Context | `(application-context)` | `(display-mode: standalone)` |
100100
|---------|:-------------------:|:----------------------------:|
101101
| Browser tab | no match | no match |
102102
| Installed, standalone mode | match | match |
@@ -118,7 +118,7 @@ A PWA shows an install banner to browser-tab users but hides it for users alread
118118
display: flex;
119119
}
120120

121-
@media (application-context: installed) {
121+
@media (application-context) {
122122
.install-banner {
123123
display: none;
124124
}
@@ -136,7 +136,7 @@ An installed app shows a back button and "open in browser" link that don't make
136136
display: none;
137137
}
138138

139-
@media (application-context: installed) {
139+
@media (application-context) {
140140
.app-nav {
141141
display: flex;
142142
}
@@ -148,7 +148,7 @@ An installed app shows a back button and "open in browser" link that don't make
148148
A site conditionally shows a service worker update prompt only in the installed experience:
149149

150150
```js
151-
if (window.matchMedia('(application-context: installed)').matches) {
151+
if (window.matchMedia('(application-context)').matches) {
152152
showUpdatePrompt();
153153
}
154154
```
@@ -158,16 +158,16 @@ if (window.matchMedia('(application-context: installed)').matches) {
158158
Although uncommon, a document could transition between contexts (e.g., a browser tab being "captured" into an app window). Developers can listen for this reactively:
159159

160160
```js
161-
window.matchMedia('(application-context: installed)').addEventListener('change', (e) => {
161+
window.matchMedia('(application-context)').addEventListener('change', (e) => {
162162
document.body.classList.toggle('is-installed', e.matches);
163163
});
164164
```
165165

166166
## Alternatives Considered
167167

168-
### A Boolean Media Feature (`installed`)
168+
### Naming the Boolean Feature `installed`
169169

170-
An alternative approach is to define a boolean media feature named `installed`:
170+
An alternative is to name the boolean media feature `installed` instead of `application-context`:
171171

172172
```css
173173
@media (installed) {
@@ -179,13 +179,32 @@ An alternative approach is to define a boolean media feature named `installed`:
179179
}
180180
```
181181

182-
This design is simpler to author, following the pattern of other boolean media features like `(hover)` or `(scripting)`. However:
182+
This name is shorter and follows the pattern of other boolean media features like `(hover)` or `(scripting)`. However:
183+
184+
- **Naming ambiguity.** The name `installed` suggests a statement about global installation state. A developer might reasonably expect `(installed)` to be `true` if the app is installed on the device, even when viewed in a browser tab. In reality, the feature only matches when running *inside* an installed app window. The name `application-context` makes this distinction explicit, and describes the current context, not a global property.
185+
186+
**Conclusion:** Both names describe the same boolean signal, but `application-context` offers clearer semantics and avoids conflation with global installation state.
187+
188+
### An Enumerated Media Feature
189+
190+
Rather than a boolean, `application-context` could be defined as an enumerated media feature with discrete values such as `installed` and `browser`:
191+
192+
```css
193+
@media (application-context: installed) {
194+
/* Styles for an installed app window */
195+
}
196+
197+
@media (application-context: browser) {
198+
/* Styles for a regular browser tab */
199+
}
200+
```
201+
202+
An enumerated feature offers an explicit `browser` value and room to add future context values. However:
183203

184-
- **Naming ambiguity.** The name `installed` suggests a statement about global installation state. A developer might reasonably expect `(installed)` to be `true` if the app is installed on the device, even when viewed in a browser tab. In reality, the feature would only match when running *inside* an installed app window. The name `application-context` makes this distinction explicit, and describes the current context, not a global property.
185-
- **Limited extensibility.** A boolean feature can only express two states. If future application contexts emerge, a boolean feature cannot accommodate them without introducing additional media features. An enumerated feature like `application-context` can grow by adding new values.
186-
- **No `browser` counterpart.** With a boolean feature, styling for the browser-tab case requires `not (installed)`, which is less readable and less intentional than `(application-context: browser)`.
204+
- **Unnecessary complexity for a binary state.** The installed-versus-browser distinction is fundamentally binary. A boolean feature expresses it more simply: `(application-context)` for the installed case and `not (application-context)` for the browser case, matching the existing pattern of boolean media features.
205+
- **Speculative extensibility.** The additional values an enumerated feature could accommodate are hypothetical. If new application contexts ever emerge, they can be represented by dedicated media features at that time, rather than designing for them speculatively today.
187206

188-
**Conclusion:** While the boolean form is simpler for a binary state check, the `application-context` enumerated approach offers clearer semantics and room to grow.
207+
**Conclusion:** A boolean feature is the simplest fit for a binary state. The enumerated form adds overhead without a corresponding benefit for the current use case.
189208

190209
### Standardizing `navigator.standalone`
191210

@@ -221,7 +240,7 @@ A dedicated JS property could work, but:
221240

222241
### Privacy
223242

224-
- **No cross-site information leak.** The feature only reflects the current browsing context. It does not reveal whether the app is installed on the device, only whether the current document is *running* in an app window. A site opened in a browser tab always sees `(application-context: installed)` as non-matching, even if the user has the app installed.
243+
- **No cross-site information leak.** The feature only reflects the current browsing context. It does not reveal whether the app is installed on the device, only whether the current document is *running* in an app window. A site opened in a browser tab always sees `(application-context)` as non-matching, even if the user has the app installed.
225244
- **No new fingerprinting surface.** The information exposed (the current app context) is already inferable from existing signals like `display-mode: standalone`, except that `application-context` is stable across display mode changes. It does not expose any new bits of entropy beyond what the user has already disclosed by opening the app window.
226245
- **Cross-origin iframe isolation.** The feature evaluates to `browser` in cross-origin iframes, preventing embedded third-party content from detecting the host app's installation state. Same-origin iframes are permitted to inherit the top-level context, as they already have full access to the top-level window via `window.top` and do not represent a privacy boundary.
227246

@@ -247,7 +266,7 @@ Without a built-in property on `Client`, developers would manually relay the app
247266
```js
248267
// On page load, inform the service worker of the current app context
249268
if (navigator.serviceWorker.controller) {
250-
const isInstalled = window.matchMedia('(application-context: installed)').matches;
269+
const isInstalled = window.matchMedia('(application-context)').matches;
251270
navigator.serviceWorker.controller.postMessage({
252271
type: 'app-context-report',
253272
context: isInstalled ? 'installed' : 'browser'
@@ -289,11 +308,11 @@ This approach has several drawbacks:
289308
290309
### Proposed Extension
291310
292-
A natural complement to the `app-context` CSS media feature would be exposing the same information on the [`WindowClient`](https://developer.mozilla.org/en-US/docs/Web/API/WindowClient) interface in the Service Worker API. For example, an `appContext` property:
311+
A natural complement to the `application-context` CSS media feature would be exposing the same information on the [`WindowClient`](https://developer.mozilla.org/en-US/docs/Web/API/WindowClient) interface in the Service Worker API. For example, a boolean `applicationContext` property:
293312
294313
```js
295314
const allClients = await self.clients.matchAll({ type: 'window' });
296-
const installedClient = allClients.find(client => client.appContext === 'installed');
315+
const installedClient = allClients.find(client => client.applicationContext);
297316

298317
if (installedClient) {
299318
// An installed app window exists — post a message to it
@@ -312,6 +331,8 @@ Many thanks for valuable feedback and advice from:
312331
- Alison Maher
313332
- Alex Russell
314333
- Rob Paveza
334+
- Marcos Caceres
335+
- Ben Francis
315336
316337
References:
317338
- [W3C Media Queries Level 5](https://drafts.csswg.org/mediaqueries-5/)

0 commit comments

Comments
 (0)