Web Preferences API

Unofficial Proposal Draft,

This version:
https://wicg.github.io/web-preferences-api/
Issue Tracking:
GitHub
Inline In Spec
Editor:
(unaffiliated)
Tests:
web-platform-tests web-preferences-api/ (not started)

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.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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.

1. Introduction

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:

Alternatively, sites can and do offer site-level settings, but this currently comes with a number of issues:

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.

2. Extensions to the Navigator interface

[Exposed=Window, SecureContext]
partial interface Navigator {
  [SameObject] readonly attribute PreferenceManager preferences;
};

2.1. preferences attribute

When getting the preferences attribute always return the same instance of the PreferenceManager object.

3. PreferenceManager interface

[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;
};

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].

3.1. 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 Media Queries 5 § 11.5 Detecting the desire for light or dark color schemes: the prefers-color-scheme feature.

The get valid values for colorScheme algorithm, when invoked, must run these steps:
  1. Let validValues be a new empty sequence.

  2. Add "light" to validValues.

  3. Add "dark" to validValues.

  4. Return validValues.

If an override is set for this preference:

How does this work with forced-colors or forced dark mode features?

3.2. 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 Media Queries 5 § 11.3 Detecting the desire for increased or decreased color contrast from elements on the page: the prefers-contrast feature.

The get valid values for contrast algorithm, when invoked, must run these steps:
  1. Let validValues be a new empty sequence.

  2. Add "more" to validValues.

  3. Add "less" to validValues.

  4. Add "no-preference" to validValues.

  5. Return validValues.

If an override is set for this preference:

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.

3.3. 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 Media Queries 5 § 11.1 Detecting the desire for less motion on the page: the prefers-reduced-motion feature.

The get valid values for reducedMotion algorithm, when invoked, must run these steps:
  1. Let validValues be a new empty sequence.

  2. Add "reduce" to validValues.

  3. Add "no-preference" to validValues.

  4. Return validValues.

If an override is set for this preference:

Note: An example of a UA feature that is affected by this preference could be disabling smooth scrolling, or pausing marquee elements.

3.4. 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 Media Queries 5 § 11.2 Detecting the desire for reduced transparency on the page: the prefers-reduced-transparency feature.

The get valid values for reducedTransparency algorithm, when invoked, must run these steps:
  1. Let validValues be a new empty sequence.

  2. Add "reduce" to validValues.

  3. Add "no-preference" to validValues.

  4. Return validValues.

If an override is set for this preference:

3.5. 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 Media Queries 5 § 11.6 Detecting the desire for reduced data usage when loading a page: the prefers-reduced-data feature.

The get valid values for reducedData algorithm, when invoked, must run these steps:
  1. Let validValues be a new empty sequence.

  2. Add "reduce" to validValues.

  3. Add "no-preference" to validValues.

  4. Return validValues.

If an override is set for this preference:

3.6. PreferenceObject interface

[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;
};

3.6.1. override attribute

The override attribute, when accessed, must run these steps:
  1. Let preference be the preference object’s name.

  2. Let override be null.

  3. If an override for preference exists, set override to the value of that override.

  4. Return override.

3.6.2. value attribute

The value attribute, when accessed, must run these steps:
  1. Let preference be the preference object’s name.

  2. Let value be null.

  3. If an override for preference exists, set value to the value of that override.

  4. If value is null, set value to the UA value of the preference.

  5. Return value.

3.6.3. validValues attribute

The validValues attribute, when accessed, must run these steps:
  1. Let preference be the preference object’s name.

  2. Let validValues be a new empty sequence.

  3. If preference is "colorScheme", set validValues to the result of get valid values for colorScheme.

  4. If preference is "contrast", set validValues to the result of get valid values for contrast.

  5. If preference is "reducedMotion", set validValues to the result of get valid values for reducedMotion.

  6. If preference is "reducedTransparency", set validValues to the result of get valid values for reducedTransparency.

  7. If preference is "reducedData", set validValues to the result of get valid values for reducedData.

  8. Return validValues.

3.6.4. onchange event handler attribute

The onchange attribute is an event handler IDL attribute for the onchange event handler, whose event handler event type is change.

Whenever the user agent is aware that the state of a PreferenceObject instance value has changed, it asynchronously runs the PreferenceObject update steps:
  1. Let preference be the PreferenceObject object that value is associated with.

  2. If this's relevant global object is a Window object, then:

    1. Let document be preference’s relevant global object's associated Document.

    2. If document is null or document is not fully active, terminate this algorithm.

  3. fire an event named change at preference.

3.6.5. requestOverride() method

The requestOverride(value) method, when invoked, must run these steps:
  1. Let result be a new promise.

  2. Let allowed be false.

  3. Set allowed to the result of executing a UA defined algorithm for deciding whether the request is allowed.

  4. If allowed is false, return a promise rejected with a "NotAllowedError" DOMException.

  5. Let value be the method’s argument.

  6. Let result be a new promise.

  7. If value is null or an empty string:

    1. Run clearOverride.

    2. Resolve and return result.

  8. Let currentValue be the preference object’s value.

  9. Let validValues be null.

  10. If preference is "colorScheme", set validValues to the result of get valid values for colorScheme.

  11. If preference is "contrast", set validValues to the result of get valid values for contrast.

  12. If preference is "reducedMotion", set validValues to the result of get valid values for reducedMotion.

  13. If preference is "reducedTransparency", set validValues to the result of get valid values for reducedTransparency.

  14. If preference is "reducedData", set validValues to the result of get valid values for reducedData.

  15. If value is not in validValues:

    1. Reject result with a "TypeError" DOMException.

    2. Return result.

  16. Let previousOverride be null.

  17. If an override for preference exists, set previousOverride to the value of that override.

  18. If value is different from previousOverride:

    1. Set the preference override for preference to value.

  19. If previousOverride is null, then:

    1. If value is the same as currentValue, then:

      1. Fire an event named change at this.

  20. Resolve and return result.

This algorithm needs more detail on what exactly setting the preference override does.

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.

3.6.6. clearOverride method

The clearOverride() method, when invoked, must run these steps:
  1. Let preference be the preference object’s name.

  2. Let override be null.

  3. If an override for preference exists, set override to the value of that override.

  4. If override is null, then return.

  5. Clear the override for preference.

  6. Let newValue be the preference object’s value.

  7. If newValue is equal to override, then:

  8. Fire an event named change at this.

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.

3.6.7. Garbage Collection

A PreferenceObject object MUST NOT be garbage collected if it has an event listener whose type is change.

4. 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:

const colorSchemeSupported = 'colorScheme' in navigator.preferences;

4.1. Requesting a preference override

To request a preference override, the requestOverride method can be called.

navigator.preferences.colorScheme.requestOverride('dark')
    .then(() => {
         // The preference override was successful.
    })
    .catch((error) => {
         // The preference override request was rejected.
    });

4.2. Clearing a preference override

To clear an override and return the preference to the browser default, the clearOverride method can be called.

navigator.preferences.colorScheme.clearOverride();

4.3. 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.

const colorScheme = navigator.preferences.colorScheme.override; // "light" | "dark" | null

4.4. Getting valid values for a preference

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.

const validValues = navigator.preferences.colorScheme.validValues; // ["light", "dark"]

5. Security and Privacy Considerations

This section is non-normative.

5.1. Storage of preference overrides

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.

Should this be in a normative section somewhere? See #23

5.2. Avoiding fingerprinting

This API exposes no new fingerprinting surfaces beyond that which already exist in the platform.

5.3. Permissions & User Activation

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.

5.4. Sub-resources

Note: See #8 for discussion regarding this.

For the spec we can probably find an existing definition to reference, but for the purposes of this explainer:

Wherever the override value is passed down it should probably be done so in an opaque manner.

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?

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.

6. Acknowledgements

This section is non-normative.

TODO fill in acknowledgements section

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. URL: https://drafts.csswg.org/css-cascade-5/
[CSS-COLOR-ADJUST-1]
Elika Etemad; et al. CSS Color Adjustment Module Level 1. URL: https://drafts.csswg.org/css-color-adjust-1/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. URL: https://drafts.csswg.org/cssom-view/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MEDIAQUERIES-5]
Dean Jackson; et al. Media Queries Level 5. URL: https://drafts.csswg.org/mediaqueries-5/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SAVEDATA]
Save Data API. Editor's Draft. URL: https://wicg.github.io/savedata/
[USER-PREFERENCE-MEDIA-FEATURES-HEADERS]
User Preference Media Features Client Hints Headers. Draft Community Group Report. URL: https://wicg.github.io/user-preference-media-features-headers/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

[Exposed=Window, SecureContext]
partial interface Navigator {
  [SameObject] readonly attribute PreferenceManager preferences;
};

[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;
};

[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;
};

Issues Index

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.
Unsure how to link to html spec for the source media attribute example.
How does this work with forced-colors or forced dark mode features?
This algorithm needs more detail on what exactly setting the preference override does.
Is TypeError correct here?
Should this be in a normative section somewhere? See #23
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?
TODO fill in acknowledgements section