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 ab07d7b
Showing 1 changed file with 50 additions and 57 deletions.
107 changes: 50 additions & 57 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 Down Expand Up @@ -31,13 +31,12 @@ It is intended for this override to apply permanently and be scoped per 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 @@ -59,7 +58,7 @@ Like with the previous use case, if a site wanted to sync a user's animation pre

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

Expand All @@ -71,26 +70,25 @@ With the **Web Preference API**, sites could simply use the `color-scheme` prope

## 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 ab07d7b

Please sign in to comment.