Skip to content

Commit

Permalink
Update proposal based on feedback
Browse files Browse the repository at this point in the history
Fixes #4, #5 and #6
  • Loading branch information
lukewarlow committed Aug 31, 2023
1 parent 61989c3 commit 9618734
Showing 1 changed file with 53 additions and 60 deletions.
113 changes: 53 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Web Preference API (name TBD)
# Web Preferences API (name TBD)

Authors: [Luke Warlow](https://github.com/lukewarlow)

Expand All @@ -24,20 +24,19 @@ Alternatively, sites can and do offer site-level settings, but this currently co
- No integration with the `color-scheme` CSS property
- The various client storage mechanisms that could store these preferences can be cleared in a number of scenarios

The **Web Preference API** aims to solve this by providing a way for sites to indicate a user preference for a given pre-defined setting.
The **Web Preferences API** aims to solve this by providing a way for sites to indicate a user preference for a given pre-defined setting.

It is intended for this override to apply permanently and be scoped per origin. (This explainer refers to "site" but it should be read to mean origin).

### Goals

- Provide a way for sites to override a given user preference in a way that fully integrates with existing browser APIs
- Provide a way for sites to determine the user's preference for a given setting, without having to resort to `matchMedia`
- Increase usage of these preferences, leading to a more accessible web

### Non-Goals

- Provide a way for sites to store arbitrary site-specific preferences -- local storage or other storage APIs should be used instead
- Provide a way for sites to determine the origin of a user preference, beyond User Agent VS site (e.g. a site won't be able to determinate if a setting comes from the OS or browser)
- Provide a way for sites to determine the source of a user preference, beyond User Agent VS site (e.g. a site won't be able to determine if a setting comes from the OS or browser)
- Force browsers to provide a UI for overriding OS level user preferences (although this would be nice)
- Force browsers to provide a UI for overriding user preferences per site (although this would be nice)

Expand All @@ -51,46 +50,45 @@ An example of a custom element library for this is [dark-mode-toggle](https://ww
This library currently requires users to use a class to indicate the dark mode preference, rather than being able to use the preference media query.
It also contains a "hack" to allow the media attribute on `<link>` elements to work.

With the **Web Preference API**, this library could be updated to remove the "hack" for `<link>` elements, and all limitations such as requiring a dark mode class would be removed.
With the **Web Preferences API**, this library could be updated to remove the "hack" for `<link>` elements, and all limitations such as requiring a dark mode class would be removed.

### Syncing preferences across devices

Like with the previous use case, if a site wanted to sync a user's animation preference across devices, they'd currently have to remove any usages of media queries and swap to using the DOM and a CSS selector (e.g. class, or attribute).

With the **Web Preference API**, this would no longer be the case and sites could use a simple sync function on page load to ensure the server and client preference matches.

This would have the added effect of the site benefiting from any potential UA stylesheet to reduce animations for users who have indicated a preference for reduced motion.
This would have the added effect of the site benefiting from any potential future UA stylesheet to reduce animations for users who have indicated a preference for reduced motion.

### Fully Themed Browser UI

Currently, if a site decides not to use `prefers-color-scheme`, they're likely also not using the `color-scheme` property to declare support for dark mode.

This likely results in having to manually theme all browser provided UI (e.g. form controls, scrollbars) for dark mode. This is a lot of work and is likely to be missed in some places.

With the **Web Preference API**, sites could simply use the `color-scheme` property and rely on the browser to theme all browser provided UI.
With the **Web Preferences API**, sites could simply use the `color-scheme` property and rely on the browser to theme all browser provided UI.

## Proposed Solution

### The `navigator.preference` object
### The `navigator.preferences` object

A new `navigator.preference` object will be added to the platform. This object will be the entry point to this API.
A new `navigator.preferences` object will be added to the platform. This object will be the entry point to this API.

```ts
interface Navigator {
readonly preference: PreferenceManager;
}

interface PreferenceManager {
setOverride(name: string, value: string): Promise<void>;
clearOverride(name: string): Promise<void>;
get(name: string): Promise<PreferenceResult>;
getSupported(): Promise<PreferenceSupportData[]>;
}
// null means the preference is not overriden
colorScheme: string | null; // "light" | "dark" | null
contrast: string | null; // "no-preference" | "more" | "less" | null
reducedMotion: string | null; // "no-preference" | "reduce" | null
reducedTransparency: string | null; // "no-preference" | "reduce" | null
reducedData: string | null; // "no-preference" | "reduce" | null
// Future preferences can be added here, the exact properties will be down to the browser support.

interface PreferenceResult extends EventTarget {
readonly value: string;
readonly isOverride: boolean;
onchange: ((this: PreferenceResult, ev: Event) => any) | null;
getSupported(): Promise<PreferenceSupportData[]>;
}

interface PreferenceSupportData {
Expand All @@ -99,55 +97,44 @@ interface PreferenceSupportData {
}
```

### The `navigator.preference.setOverride` method
### The `navigator.preferences.[preferenceName]` properties

Each preference the browser supports will be exposed as a property on the `navigator.preferences` object.

This method allows a site to override a given user preference. This method will:
- Resolve when the preference has been successfully overridden.
- Be rejected if the operation is not successful. It'll reject with a [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) value of:
- `NotSupportedError`: If the preference is not supported by the browser.
- `ValidationError`: If the provided value is not valid for the given preference.
- `OperationError`: If the operation failed for any other reason.
Feature detection for a given preference is as simple as:

```js
await navigator.preference.setOverride('prefers-contrast', 'more');
const colorSchemeSupported = 'colorScheme' in navigator.preferences;
```

### The `navigator.preference.clearOverride` method

This method allows a site to clear an override for a given user preference. This method will:
- Resolve when the preference has been successfully cleared.
- Be rejected if the operation is not successful. It'll reject with a [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) value of:
- `NotSupportedError`: If the preference is not supported by the browser.
- `OperationError`: If the operation failed for any other reason.
Each property will return null if not overridden or a string value indicating the preference.

```js
await navigator.preference.clearOverride('prefers-contrast');
const colorScheme = navigator.preferences.colorScheme; // "light" | "dark" | null
```

### The `navigator.preference.get` method
To clear an override and return the preference to the browser default, the property can be set to null.

```js
navigator.preferences.colorScheme = null;
```

This method allows a site to get the current value of a given user preference. This method will:
- Resolve with an object containing the current value of the preference, along with whether this is a site override.
- Be rejected if the operation is not successful. It'll reject with a [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) value of:
- `NotSupportedError`: If the preference is not supported by the browser.
- `OperationError`: If the operation failed for any other reason.
To set a preference override, the property can be set to a valid value for the preference.
If an invalid value is set then a [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) will be thrown with a `ValidationError` code.

```js
const preferenceResult = await navigator.preference.get('prefers-contrast');
console.log(preferenceResult.value); // 'more'
console.log(preferenceResult.isOverride); // true
preferenceResult.addEventListener('change', () => {
console.log(preferenceResult.value); // 'less'
});
navigator.preferences.colorScheme = 'dark';
```

### The `navigator.preference.getSupported` method
### The `navigator.preferences.getSupported` method

This method allows a site to get the preferences supported by the browser. This is useful for sites that want to dynamically generate UI for overriding preferences.

This method allows a site to get the preferences supported by the browser.
It also allows sites to determine if a preference value is supported before attempting to set it.

```js
const preferenceSupportData = await navigator.preference.getSupported();
console.log(preferenceSupportData); // [ { name: 'prefers-contrast', values: ['more', 'less', 'no-preference'] }, ... ]
const preferenceSupportData = await navigator.preferences.getSupported();
console.log(preferenceSupportData); // [ { name: 'contrast', values: ['more', 'less', 'no-preference'] }, ... ]
```

## Privacy and Security Considerations
Expand All @@ -156,6 +143,16 @@ console.log(preferenceSupportData); // [ { name: 'prefers-contrast', values: ['m

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

### Iframes

TODO: Need to work out the exact specifics with regards to iframes both same-origin and cross-origin.

Same-origin iframes should probably be updated with the parent frame's preference overrides but in an opaque manner.

e.g. 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` as `dark`.

Whereas, cross-origin iframes should probably not be updated with the parent frame's preference overrides. This is an unfortunate limitation but is probably necessary to prevent new forms of data exfiltration.

## Alternative Solutions

### Use a custom media query
Expand All @@ -176,17 +173,13 @@ This also doesn't fix the (relatively minor) issue of preference syncing across

## Open Questions

- Do we need a clearOverride method? Could we just use setOverride with a value of `null` or `undefined`?
- Do we need a clearAllOverrides method?
- Do we need a way to get the preference value or is using `matchMedia` sufficient? (I think we need at least a getOverride method)
- Do we need an API method to indicate the accepted values for a given preference?
- Do we need a user activation requirement for set and clear? (e.g. clicking a button)
- This would remove the ability to automatically sync preferences.
- Do we need a permission grant? Or at least integrate with permissions policy?
- Do we need configuration for the scope of these overrides?
- Do we need configuration for choosing session vs permanent override?
- Do we need a way to get the "computed" preference value or is using `matchMedia` sufficient?
- The ergonomics of `matchMedia` particularly aren't great when you want to listen to updates.
- Do we need a user activation requirement for setting the values? (e.g. clicking a button)
- Do we need configuration for the scope of these overrides? (I think this is out of scope)
- Do we need configuration for choosing session vs permanent override? (I think this is out of scope)

## Acknowledgements

Special thanks to [Ryan Christian](https://github.com/rschristian) for his help in reviewing this explainer and providing feedback.
Special thanks to [Ryan Christian](https://github.com/rschristian) for his help in reviewing the original explainer and providing feedback.

0 comments on commit 9618734

Please sign in to comment.