-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.bs
417 lines (303 loc) · 20.9 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
<pre class='metadata'>
Title: Web Preferences API
Shortname: web-preferences-api
Level: 1
Status: UD
Group: WICG
Repository: wicg/web-preferences-api
Markup Shorthands: css no, markdown yes
URL: https://wicg.github.io/web-preferences-api/
Editor: Luke Warlow, unaffiliated, [email protected]
!Tests: <a href=https://github.com/w3c/web-platform-tests/tree/master/web-preferences-api>web-platform-tests web-preferences-api/</a> (<a href=https://github.com/w3c/web-platform-tests/labels/web-preferences-api>not started</a>)
Abstract: The Web Preference API aims to provide a way for sites to override the value for a given user preference (e.g. color-scheme preference) in a way that fully integrates with existing Web APIs.
</pre>
<pre class="link-defaults">
spec:html; type:dfn; text:origin
</pre>
Issue: This spec requires large amounts of work to section it and word it in ways that are normative. It is currently a very rough draft.
# Introduction # {#sec-intro}
*This section is non-normative.*
Currently, website authors have a choice when wishing to honour a user's preference for a given setting:
They can choose to "use the platform" where the user must indicate their preference via their OS or, if lucky, they can override in the browser. This comes with a number of issues:
- Relies on the user's OS or browser offering the ability to change the setting
- Relies on the user knowing how to change the setting in their OS or browser
- No ability to override the setting for a specific site
- No ability to sync preferences for a site across devices
Alternatively, sites can and do offer site-level settings, but this currently comes with a number of issues:
- No integration with [[mediaqueries-5#mf-user-preferences]]
- No integration with conditional resource loading (e.g. using `media="(prefers-contrast: more)"` on a `source` element)
- No integration with JS APIs for retrieval of these preferences (e.g. `matchMedia` in [[cssom-view#extensions-to-the-window-interface]]
- No integration with [[USER-PREFERENCE-MEDIA-FEATURES-HEADERS]]
- No integration with the [[css-color-adjust-1#preferred]] concept
- The various client storage mechanisms that could store these preferences can be cleared in a number of scenarios
Issue: Unsure how to link to html spec for the source media attribute example.
The **Web Preferences API** aims to solve this by providing a way for sites to override the value for a given a user preference.
It is intended for this override to apply permanently and be scoped per origin.
The override should be passed down to sub-resource where possible, see privacy section for details. This explainer refers to "site" but it should be read to mean [=origin=].
# Extensions to the {{Navigator}} interface # {#extensions-to-the-navigator-interface}
<script type=idl>
[Exposed=Window, SecureContext]
partial interface Navigator {
[SameObject] readonly attribute PreferenceManager preferences;
};
</script>
## {{preferences}} attribute ## {#preferences-attribute}
When getting the {{preferences}} attribute always return the same instance of the {{PreferenceManager}} object.
# {{PreferenceManager}} interface # {#preference-manager}
<script type=idl>
[Exposed=Window, SecureContext]
interface PreferenceManager {
readonly attribute PreferenceObject colorScheme;
readonly attribute PreferenceObject contrast;
readonly attribute PreferenceObject reducedMotion;
readonly attribute PreferenceObject reducedTransparency;
readonly attribute PreferenceObject reducedData;
};
</script>
Note: The exact set of preferences is down to the browser vendor, but it is expected that the set of preferences will be the same as those defined in [[!mediaqueries-5]].
## {{colorScheme}} attribute ## {#colorscheme-attribute}
The {{colorScheme}} attribute is a {{PreferenceObject}} used to override the user's preference for the color scheme of the site.
This is modeled after the `prefers-color-scheme` user preference media feature as defined in [[mediaqueries-5#prefers-color-scheme]].
<div algorithm='get valid values for colorScheme'>
The <dfn>get valid values for colorScheme</dfn> algorithm, when invoked, must run these steps:
1. Let |validValues| be a new empty [=sequence=].
1. Add `"light"` to |validValues|.
1. Add `"dark"` to |validValues|.
1. Return |validValues|.
</div>
If an override is set for this preference:
- The user agent MUST use this override for the [[mediaqueries-5#prefers-color-scheme]] in all stylesheets applied to an [=origin=] including the [=UA style sheet=].
- The user agent MUST also use this override when queried via `matchMedia()` from [[cssom-view#extensions-to-the-window-interface]].
- The user agent MUST also use this override when calculating the [=used color scheme=].
- The user agent MUST also use this override when sending [[USER-PREFERENCE-MEDIA-FEATURES-HEADERS#sec-ch-prefers-color-scheme]].
- The user agent MUST also use this override for any UA features that are normally affected by [[mediaqueries-5#prefers-color-scheme]].
Issue: How does this work with `forced-colors` or forced dark mode features?
## {{contrast}} attribute ## {#contrast-attribute}
The {{contrast}} attribute is a {{PreferenceObject}} used to override the user's preference for the contrast of the site.
This is modeled after the `prefers-contrast` user preference media feature as defined in [[mediaqueries-5#prefers-contrast]].
<div algorithm='get valid values for contrast'>
The <dfn>get valid values for contrast</dfn> algorithm, when invoked, must run these steps:
1. Let |validValues| be a new empty [=sequence=].
1. Add `"more"` to |validValues|.
1. Add `"less"` to |validValues|.
1. Add `"no-preference"` to |validValues|.
1. Return |validValues|.
</div>
If an override is set for this preference:
- The user agent MUST use this override for the [[mediaqueries-5#prefers-contrast]] in all stylesheets applied to an [=origin=] including the [=UA style sheet=].
- The user agent MUST also use this override when queried via `matchMedia()` from [[cssom-view#extensions-to-the-window-interface]].
- The user agent MUST also use this override when sending [[USER-PREFERENCE-MEDIA-FEATURES-HEADERS#sec-ch-prefers-contrast]].
- The user agent MUST also use this override for any UA features that are normally affected by [[mediaqueries-5#prefers-contrast]].
Note: Unlike the media feature this preference is NOT able to be set to `custom` as this is tightly coupled to the `forced-colors` media feature.
## {{reducedMotion}} attribute ## {#reducedmotion-attribute}
The {{reducedMotion}} attribute is a {{PreferenceObject}} used to override the user's preference for reduced motion on the site.
This is modeled after the `prefers-reduced-motion` user preference media feature as defined in [[mediaqueries-5#prefers-reduced-motion]].
<div algorithm='get valid values for reducedMotion'>
The <dfn>get valid values for reducedMotion</dfn> algorithm, when invoked, must run these steps:
1. Let |validValues| be a new empty [=sequence=].
1. Add `"reduce"` to |validValues|.
1. Add `"no-preference"` to |validValues|.
1. Return |validValues|.
</div>
If an override is set for this preference:
- The user agent MUST use this override for the [[mediaqueries-5#prefers-reduced-motion]] in all stylesheets applied to an [=origin=] including the [=UA style sheet=].
- The user agent MUST also use this override when queried via `matchMedia()` from [[cssom-view#extensions-to-the-window-interface]].
- The user agent MUST also use this override when sending [[USER-PREFERENCE-MEDIA-FEATURES-HEADERS#sec-ch-prefers-reduced-motion]].
- The user agent MUST also use this override for any UA features that are normally affected by [[mediaqueries-5#prefers-reduced-motion]].
Note: An example of a UA feature that is affected by this preference could be disabling smooth scrolling, or pausing marquee elements.
## {{reducedTransparency}} attribute ## {#reducedtransparency-attribute}
The {{reducedTransparency}} attribute is a {{PreferenceObject}} used to override the user's preference for reduced transparency on the site.
This is modeled after the `prefers-reduced-transparency` user preference media feature as defined in [[mediaqueries-5#prefers-reduced-transparency]].
<div algorithm='get valid values for reducedTransparency'>
The <dfn>get valid values for reducedTransparency</dfn> algorithm, when invoked, must run these steps:
1. Let |validValues| be a new empty [=sequence=].
1. Add `"reduce"` to |validValues|.
1. Add `"no-preference"` to |validValues|.
1. Return |validValues|.
</div>
If an override is set for this preference:
- The user agent MUST use this override for the [[mediaqueries-5#prefers-reduced-transparency]] in all stylesheets applied to an [=origin=] including the [=UA style sheet=].
- The user agent MUST also use this override when queried via `matchMedia()` from [[cssom-view#extensions-to-the-window-interface]].
- The user agent MUST also use this override when sending [[USER-PREFERENCE-MEDIA-FEATURES-HEADERS#sec-ch-prefers-reduced-transparency]].
- The user agent MUST also use this override for any UA features that are normally affected by [[mediaqueries-5#prefers-reduced-transparency]].
## {{reducedData}} attribute ## {#reduceddata-attribute}
The {{reducedData}} attribute is a {{PreferenceObject}} used to override the user's preference for reduced data usage on the site.
This is modeled after the `prefers-reduced-data` user preference media feature as defined in [[mediaqueries-5#prefers-reduced-data]].
<div algorithm='get valid values for reducedData'>
The <dfn>get valid values for reducedData</dfn> algorithm, when invoked, must run these steps:
1. Let |validValues| be a new empty [=sequence=].
1. Add `"reduce"` to |validValues|.
1. Add `"no-preference"` to |validValues|.
1. Return |validValues|.
</div>
If an override is set for this preference:
- The user agent MUST use this override for the [[mediaqueries-5#prefers-reduced-data]] in all stylesheets applied to an [=origin=] including the [=UA style sheet=].
- The user agent MUST also use this override when queried via `matchMedia()` from [[cssom-view#extensions-to-the-window-interface]].
- The user agent MUST also use this override when sending [[SAVEDATA#save-data-request-header-field]].
- The user agent MUST also use this override when calculating the [[SAVEDATA#savedata-attribute]].
- The user agent MUST also use this override for any UA features that are normally affected by [[mediaqueries-5#prefers-reduced-data]].
## {{PreferenceObject}} interface ## {#preferenceobject-interface}
<script type=idl>
[Exposed=Window, SecureContext]
interface PreferenceObject : EventTarget {
readonly attribute DOMString? override;
readonly attribute DOMString value;
readonly attribute FrozenArray<DOMString> validValues;
undefined clearOverride();
Promise<undefined> requestOverride(DOMString? value);
attribute EventHandler onchange;
};
</script>
### {{override}} attribute ### {#override-attribute}
<div algorithm='get preference override'>
The <dfn attribute for=PreferenceObject>override</dfn> attribute, when accessed, must run these steps:
1. Let |preference| be the preference object's name.
1. Let |override| be null.
1. If an override for |preference| exists, set |override| to the value of that override.
1. Return |override|.
</div>
### {{value}} attribute ### {#value-attribute}
<div algorithm='get preference value'>
The <dfn attribute for=PreferenceObject>value</dfn> attribute, when accessed, must run these steps:
1. Let |preference| be the preference object's name.
1. Let |value| be null.
1. If an override for |preference| exists, set |value| to the value of that override.
1. If |value| is null, set |value| to the UA value of the preference.
1. Return |value|.
</div>
### {{validValues}} attribute ### {#validValues-attribute}
<div algorithm>
The <dfn attribute for=PreferenceObject>validValues</dfn> attribute, when accessed, must run these steps:
1. Let |preference| be the preference object's name.
1. Let |validValues| be a new empty [=sequence=].
1. If |preference| is "{{colorScheme}}", set |validValues| to the result of [=get valid values for colorScheme=].
1. If |preference| is "{{contrast}}", set |validValues| to the result of [=get valid values for contrast=].
1. If |preference| is "{{reducedMotion}}", set |validValues| to the result of [=get valid values for reducedMotion=].
1. If |preference| is "{{reducedTransparency}}", set |validValues| to the result of [=get valid values for reducedTransparency=].
1. If |preference| is "{{reducedData}}", set |validValues| to the result of [=get valid values for reducedData=].
1. Return |validValues|.
</div>
### {{onchange}} event handler attribute ### {#onchange-attribute}
The <dfn attribute for=PreferenceObject>onchange</dfn> attribute is an [=event handler IDL attribute=] for
the {{onchange}} [=event handler=], whose [=event handler event type=]
is <dfn class="event" data-dfn-for="PreferenceObject">change</dfn>.
<div algorithm="update steps">
Whenever the [=user agent=] is aware that the state of a {{PreferenceObject}}
instance |value| has changed, it asynchronously runs the <dfn algorithm for="PreferenceObject">{{PreferenceObject}}
update steps</dfn>:
1. Let |preference| be the {{PreferenceObject}} object that |value| is associated with.
1. If [=this=]'s [=relevant global object=] is a {{Window}} object, then:
1. Let |document| be |preference|'s [=relevant global object=]'s [=associated Document=].
1. If |document| is null or |document| is not [=Document/fully active=], terminate this algorithm.
1. <a>fire an event</a> named <code>change</code> at |preference|.
### {{requestOverride()}} method ### {#request-override-method}
<div algorithm='request preference override'>
The <dfn method for=PreferenceObject>requestOverride(value)</dfn> method, when invoked, must run these steps:
1. Let |result| be [=a new promise=].
1. Let |allowed| be `false`.
1. Set |allowed| to the result of executing a UA defined algorithm for deciding whether the request is allowed.
1. If |allowed| is `false`, return [=a promise rejected with=] a "{{NotAllowedError}}" {{DOMException}}.
1. Let |value| be the method's argument.
1. Let |result| be [=a new promise=].
1. If |value| is `null` or an empty [=string=]:
1. Run {{clearOverride}}.
1. [=Resolve=] and return |result|.
1. Let |currentValue| be the preference object's |value|.
1. Let |validValues| be null.
1. If |preference| is "{{colorScheme}}", set |validValues| to the result of [=get valid values for colorScheme=].
1. If |preference| is "{{contrast}}", set |validValues| to the result of [=get valid values for contrast=].
1. If |preference| is "{{reducedMotion}}", set |validValues| to the result of [=get valid values for reducedMotion=].
1. If |preference| is "{{reducedTransparency}}", set |validValues| to the result of [=get valid values for reducedTransparency=].
1. If |preference| is "{{reducedData}}", set |validValues| to the result of [=get valid values for reducedData=].
1. If |value| is not in |validValues|:
1. [=Reject=] |result| with a "{{TypeError}}" {{DOMException}}.
1. Return |result|.
1. Let |previousOverride| be null.
1. If an override for |preference| exists, set |previousOverride| to the value of that override.
1. If |value| is different from |previousOverride|:
1. Set the preference override for |preference| to |value|.
1. If |previousOverride| is null, then:
1. If |value| is the same as |currentValue|, then:
1. <a>Fire an event</a> named <code>change</code> at [=this=].
1. [=Resolve=] and return |result|.
</div>
Issue: This algorithm needs more detail on what exactly setting the preference override does.
Issue: Is TypeError correct here?
Note: The `change` event is fired when the computed value changes, but when a new override is set it is also fired if the value hasn't changed.
### {{clearOverride}} method ### {#clear-override-method}
<div algorithm='clear preference override'>
The <dfn method for=PreferenceObject>clearOverride()</dfn> method, when invoked, must run these steps:
1. Let |preference| be the preference object's name.
1. Let |override| be null.
1. If an override for |preference| exists, set |override| to the value of that override.
1. If |override| is null, then return.
1. Clear the override for |preference|.
1. Let |newValue| be the preference object's |value|.
1. If |newValue| is equal to |override|, then:
1. <a>Fire an event</a> named <code>change</code> at [=this=].
</div>
Note: The `change` event is fired when the computed value changes, but when an override is cleared it is also fired if the value hasn't changed.
### Garbage Collection
A {{PreferenceObject}} object MUST NOT be garbage collected if it has an [=event listener=] whose type is `change`.
# Usage Examples # {#usage-examples}
*This section is non-normative.*
Each preference the browser supports will be exposed as a property on the `navigator.preferences` object.
Feature detection for a given preference is as simple as:
```js
const colorSchemeSupported = 'colorScheme' in navigator.preferences;
```
## Requesting a preference override ## {#usage-request-a-preference-override}
To request a preference override, the {{requestOverride}} method can be called.
```js
navigator.preferences.colorScheme.requestOverride('dark')
.then(() => {
// The preference override was successful.
})
.catch((error) => {
// The preference override request was rejected.
});
```
## Clearing a preference override ## {#usage-clearing-a-preference-override}
To clear an override and return the preference to the browser default, the {{clearOverride}} method can be called.
```js
navigator.preferences.colorScheme.clearOverride();
```
## Getting a preference override ## {#usage-getting-a-preference-override}
To get the value of a preference override, the {{override}} property can be read.
Each preference property's override property will return the string value indicating the preference, or null if no override is set.
```js
const colorScheme = navigator.preferences.colorScheme.override; // "light" | "dark" | null
```
## Getting valid values for a preference ## {#get-validValues}
Each {{PreferenceObject}} contains a validValues attribute that can be used to determine the valid values for a preference.
This is useful for sites that want to dynamically generate UI for overriding preferences.
It also allows sites to determine if a preference value is supported before attempting to set it.
```js
const validValues = navigator.preferences.colorScheme.validValues; // ["light", "dark"]
```
# Security and Privacy Considerations # {#sec-security}
*This section is non-normative.*
## Storage of preference overrides ## {#storage}
The overrides set by this API are intended to be persisted by the browser. These settings are clearable by any means the browser sees fit to implement.
Issue: Should this be in a normative section somewhere? See [#23](https://github.com/WICG/web-preferences-api/issues/23)
## Avoiding fingerprinting ## {#fingerprinting}
This API exposes no new fingerprinting surfaces beyond that which already exist in the platform.
## Permissions & User Activation ## {#permissions}
As the {{requestOverride}} method is a promise it gives user agents more control over the process of overriding a preference.
The {{requestOverride}} method is gated behind a UA defined algorithm for determining if the action can proceed.
This could include a user prompt, or it could be a simple check to see if the user has interacted with the page.
## Sub-resources ## {#sub-resources}
Note: See <a href="https://github.com/lukewarlow/web-preferences-api/issues/8">#8</a> for discussion regarding this.
For the spec we can probably find an existing definition to reference, but for the purposes of this explainer:
- Any [=same origin=] subresource (e.g. iframes) should get the overridden value.
- Any cross-origin subresource that already has communication with the parent (e.g. `postMessage`) should get the override value.
- Any cross-origin subresource with no external communication (e.g. an SVG loaded as an image) should get the override value.
- Any cross-origin subresource that has no communication with parent but can communicate externally should **NOT** get the override value.
Wherever the override value is passed down it should probably be done so in an opaque manner.
Issue: How do any potential iframe restrictions interact with permissions policy, should we restrict the ability to grant this permission to cross-origin iframes, or would these restrictions be separate from permissions policy?
<div class="example">
If the parent frame sets `colorScheme` to `dark` then the iframe should see `prefers-color-scheme` as dark but shouldn't read `navigator.preferences.colorScheme.override` as `dark`.
</div>
# Acknowledgements # {#sec-acknowledgements}
*This section is non-normative.*
Issue: TODO fill in acknowledgements section