diff --git a/README.md b/README.md index 0f87ee1..81c6fc4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Web Preference API (name TBD) +# Web Preferences API (name TBD) Authors: [Luke Warlow](https://github.com/lukewarlow) @@ -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) @@ -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 @@ -71,9 +70,9 @@ 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 { @@ -81,16 +80,15 @@ interface Navigator { } interface PreferenceManager { - setOverride(name: string, value: string): Promise; - clearOverride(name: string): Promise; - get(name: string): Promise; - getSupported(): Promise; -} + // 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; } interface PreferenceSupportData { @@ -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 @@ -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 @@ -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.