1. Scope of this document
This section is non-normative.
This document’s goal is to specify an API that will help developers to handle permissions on the Web platform. Web APIs have different ways to deal with permissions. The [notifications] API allows developers to request a permission and check the permission status explicitly. Others might only expose the status to web pages when they try to use the API, like the [geolocation-API] which fails if the permission was not granted without allowing the developer to check beforehand.
Being able to know whether an API call is going to prompt is useful in order to provide a good user experience. Unfortunately, more often than not, those prompts can’t be controlled by developers.
The API specified in this document is meant to provide the tools so that web applications can improve their user experience when permissions are involved.
The solution described in this document is meant to be extensible but isn’t meant to be applicable to all the current and future permissions available in the web platform. If you are working on a specification that has a permission model that wouldn’t fit in the model described in this document, please contact the editors or file an issue. We would love to hear about it.
2. Privacy considerations
This section is non-normative.
Permission states can be used as an element of fingerprinting by websites. Usually websites could already have access to the information but often through actually using the API which could lead to a permission request UI if the permission was not already granted. Thus, even though this API doesn’t expose new fingerprinting data to websites, it makes it easier for them to have discreet access to it. Therefore, implementations are encouraged to have an option for users to block (globally or selectively) the querying of permission states.
3. Permission descriptor
dictionary PermissionDescriptor#dictdef-permissiondescriptorReferenced in:3. Permission descriptor 4. Permission Registry (2)4.3. Push 4.4. Midi 4.5. Media Devices 6. Status of a permission (2)8. Permissions interface (2) (3)9. Common permission algorithms (2) { required PermissionName name#dom-permissiondescriptor-nameReferenced in:6. Status of a permission (2); };
A permission is defined by a name and other properties that depend on
the name. The simplest permissions require only a name, but some others
will require more information, in which case, they should have an associated PermissionDescriptor dictionary that inherits from PermissionDescriptor
.
4. Permission Registry
enum PermissionName#enumdef-permissionnameReferenced in:3.
Permission descriptor
4.
Permission Registry
5.
Permission Store
6.
Status of a permission
{
"geolocation",
"notifications",
"push",
"midi",
"camera",
"microphone",
"speaker",
"device-info",
"background-sync",
};
Each enumeration value in the PermissionName
enum identifies a permission#permissionReferenced in:4.
Permission Registry (2)6.
Status of a permission (2) (3)8.
Permissions interface , which consists of the following
algorithms and types:
- An associated PermissionDescriptor#associated-permissiondescriptorReferenced in:3. Permission descriptor 4. Permission Registry (2)4.3. Push 4.4. Midi 4.5. Media Devices 8. Permissions interface (2) (3) (4) (5) (6)
-
PermissionDescriptor
or one of its subtypes. If unspecified, this defaults toPermissionDescriptor
. - A permission storage type#permission-storage-typeReferenced in:4. Permission Registry (2) (3)4.5. Media Devices
-
PermissionStorage
or one of its subtypes. If unspecified, this defaults toPermissionStorage
. - A permission result type#permission-result-typeReferenced in:4. Permission Registry (2) (3) (4)4.5. Media Devices 6. Status of a permission
-
PermissionResult
or one of its subtypes. If unspecified, this defaults toPermissionResult
. - A permission query algorithm#permission-query-algorithmReferenced in:4.5. Media Devices 6. Status of a permission
- Takes an instance of the associated PermissionDescriptor type,
an instance of the permission storage type that’s currently
stored for this permission, and a new or existing instance of
the permission result type, and updates the permission
result type instance with the query result. Used by
Permissions
'query()
method and the PermissionResult update steps. If unspecified, this defaults to the boolean permission query algorithm. - A permission request algorithm#permission-request-algorithmReferenced in:4.5. Media Devices 8. Permissions interface
- Takes the previously-stored instance of the permission storage
type, an instance of the associated PermissionDescriptor,
and a newly-created instance of the permission result type.
Shows the user any necessary prompt to try to increase permissions,
and updates the instances of the permission storage type and permission result type to match. May return a
Promise
if the request can fail exceptionally. (Merely being denied permission is not exceptional.) Used byPermissions
'request()
method, which handles reading and writing the permission store. If unspecified, this defaults to the boolean permission request algorithm. - A permission revocation algorithm#permission-revocation-algorithmReferenced in:4.5. Media Devices 8. Permissions interface
- Takes no arguments. Updates any other parts of the implementation
that need to be kept in sync after an entry is removed from the
permission store. Triggered by
Permissions
'revoke()
method. If unspecified, this defaults to doing nothing.
A boolean permission#boolean-permissionReferenced in:4.1. Geolocation 4.2. Notifications is a permission with all types and algorithms defaulted.
4.1. Geolocation
The "geolocation"#dom-permissionname-geolocationReferenced in:4. Permission Registry 10. Examples permission is the permission associated with the usage of the [geolocation-API]. It is a boolean permission.
4.2. Notifications
The "notifications"#dom-permissionname-notificationsReferenced in:4. Permission Registry 10. Examples (2) permission is the permission associated with the usage of the [notifications] API. It is a boolean permission.
4.3. Push
The "push"#dom-permissionname-pushReferenced in:4. Permission Registry permission is the permission associated with the usage of the [push-api].
- associated PermissionDescriptor
-
dictionary PushPermissionDescriptor : PermissionDescriptor { boolean userVisibleOnly = false; };
4.4. Midi
The "midi"#dom-permissionname-midiReferenced in:4. Permission Registry permission is the permission associated with the usage of [webmidi].
- associated PermissionDescriptor
-
dictionary MidiPermissionDescriptor : PermissionDescriptor { boolean sysex = false; };
4.5. Media Devices
The "camera"#dom-permissionname-cameraReferenced in:4. Permission Registry 4.5. Media Devices 10. Examples , "microphone"#dom-permissionname-microphoneReferenced in:4. Permission Registry , and "speaker"#dom-permissionname-speakerReferenced in:4. Permission Registry permissions are associated with permission to use media devices as specified in [GETUSERMEDIA] and [audio-output].
- associated PermissionDescriptor
-
dictionary DevicePermissionDescriptor : PermissionDescriptor { DOMString deviceId#dom-devicepermissiondescriptor-deviceidReferenced in:4.5. Media Devices (2) (3); };
A permission covers access to the device given in the associated descriptor.
If the descriptor does not have a
deviceId
, its semantic is that it queries for access to all devices of that class. Thus, if a query for the"camera"
permission with nodeviceId
returns"granted"
, the client knows that there will never be a permission prompt for a camera, and if"denied"
is returned, it knows that no getUserMedia request for a camera will succeed.If a permission state is present for access to some, but not all, cameras, a query without the
deviceId
will return"prompt"
. - permission storage type
- TODO
- permission result type
- TODO
- permission query algorithm
- TODO
- permission request algorithm
- TODO
- permission revocation algorithm
- TODO: Stop playing/recording data?
The "device-info"#dom-permissionname-device-infoReferenced in:4. Permission Registry permission controls access to names and capabilities of input and output devices.
A successful call to the getUserMedia
function of [GETUSERMEDIA] MUST cause permission to be granted for the returned
devices, and MAY cause other permissions to be granted.
Stopping a MediaStreamTrack MAY cause permission to be revoked for the associated device.
4.6. Background Sync
The "background-sync"#dom-permissionname-background-syncReferenced in:4. Permission Registry permission is the permission associated with the usage of [web-background-sync].
5. Permission Store #permission-storeReferenced in:4. Permission Registry
User agents MAY use a form of storage to
keep track of web site permissions. When they do, they MUST have a permission storage identifier#permission-storage-identifierReferenced in:5.
Permission Store (2) (3) (4) (5) (6) which is linked to a PermissionStorage
instance or one of its subtypes.
To get a permission storage identifier#get-a-permission-storage-identifierReferenced in:6.
Status of a permission 8.
Permissions interface (2) for a PermissionName
name and an environment settings
object settings, the UA MUST return a tuple consisting
of:
- name
- settings’ origin
- optional UA-specific data like whether settings’ browsing context has a parent browsing context, or settings’ top-level browsing context’s origin
dictionary PermissionStorage#dictdef-permissionstorageReferenced in:4.
Permission Registry
(2)5.
Permission Store
(2) (3) (4) (5) (6) (7)9.
Common permission algorithms
{
// PermissionStorage is just an explanatory device.
// Instances are never received from or passed to Javascript code.
required PermissionState state;
};
The steps to retrieve a permission storage entry#retrieve-a-permission-storage-entryReferenced in:6. Status of a permission of a permission storage identifier are as follows:
- If the user agent has a
PermissionStorage
associated with the permission storage identifier in its permission store, it MUST return thePermissionStorage
. - Otherwise, it MUST return
undefined
.
The steps to create a permission storage entry#create-a-permission-storage-entryReferenced in:8. Permissions interface for a permission storage identifier are as follows:
- If the user agent has a
PermissionStorage
associated with the permission storage identifier in its permission store, it MUST overwrite it to the givenPermissionStorage
. - Otherwise, it MUST write the new
PermissionStorage
to its permission store.
The steps to delete a permission storage entry#delete-a-permission-storage-entryReferenced in:8. Permissions interface of a permission storage identifier are as follows:
- If the user agent has a
PermissionStorage
associated with the permission storage identifier in its permission store, it MUST remove it.
6. Status of a permission
enum PermissionState#enumdef-permissionstateReferenced in:5.
Permission Store
6.
Status of a permission
{
"granted",
"denied",
"prompt",
};
The "granted"#dom-permissionstate-grantedReferenced in:4.5. Media Devices 6. Status of a permission state represents that the caller will be able to successfuly access the feature without having the user agent asking the user’s permission.
The "denied"#dom-permissionstate-deniedReferenced in:4.5. Media Devices 6. Status of a permission state represents that the caller will not be able to access the feature.
The "prompt"#dom-permissionstate-promptReferenced in:4.5. Media Devices 6. Status of a permission (2) state represents that the user agent will be asking the user’s permission if the caller tries to access the feature. The user might grant, deny or dismiss the request.
The steps to retrieve the permission storage#retrieve-the-permission-storageReferenced in:6.
Status of a permission 8.
Permissions interface for a given PermissionName
name are as follows:
- Get a permission storage identifier for name and the current environment settings object, and let identifier be the result.
- Run the steps to retrieve a permission storage entry of identifier.
- If the result of those steps are not
undefined
, return it and abort these steps. - Otherwise, the user agent MUST return a default value based
on user agent’s defined heuristics. For example,
{state:
can be a default value, but it can also be based on frequency of visits."prompt"
}
[Exposed=(Window,Worker)]
interface PermissionResult#permissionresultReferenced in:4.
Permission Registry
(2)6.
Status of a permission
(2) (3)8.
Permissions interface
(2) (3)9.
Common permission algorithms
(2) : EventTarget {
readonly attribute PermissionState state;
attribute EventHandler onchange;
};
PermissionResult
instances are created with the following
internal slots:
- [[permission]]#dom-permissionresult-permission-slotReferenced in:6. Status of a permission (2) (3)
- A permission.
[[permission]]
is always the permission named
.[[query]]
.name
- [[query]]#dom-permissionresult-query-slotReferenced in:6. Status of a permission (2) (3) (4)
- A
PermissionDescriptor
.
The steps to update the state#update-the-stateReferenced in:6.
Status of a permission 8.
Permissions interface (2) of a PermissionResult
instance result are as
follows:
- Run the steps to retrieve the permission storage for
result@
, and let storage be the result.[[query]]
.name
- Run
result@
’s permission query algorithm, passing[[permission]]
result@
, storage, and result.[[query]]
The steps to create a PermissionResult#create-a-permissionresultReferenced in:8.
Permissions interface (2) (3) for a given PermissionDescriptor
permissionDesc are as follow:
- Let permission be the permission named by
permissionDesc.name
. -
Let result be a new instance of permission’s permission result type, with the internal slots filled as:
Slot Value [[permission]]
permission [[query]]
permissionDesc - Return result.
The state#dom-permissionresult-stateReferenced in:6. Status of a permission attribute MUST return the latest value that was set on the current instance.
The onchange#dom-permissionresult-onchangeReferenced in:6.
Status of a permission attribute is an event handler whose corresponding event handler event
type is change
.
Whenever the user agent is aware that the state of a PermissionResult
instance result has changed,
it MUST asynchronously run the following steps:
- Run the steps to update the state of result.
- Queue a task on the permission task source to fire an event named
change
at result.
7. Navigator and WorkerNavigator extension
A Permissions
instance is exposed on the navigator
object for Window
and Worker
contexts.
[Exposed=(Window)] partial interface Navigator { readonly attribute Permissions permissions; };
[Exposed=(Worker)] partial interface WorkerNavigator { readonly attribute Permissions permissions; };
8. Permissions interface
[Exposed=(Window,Worker)]
interface Permissions#permissionsReferenced in:4.
Permission Registry
(2) (3)7.
Navigator and WorkerNavigator extension
(2) (3) {
Promise<PermissionResult> query(PermissionDescriptor permissionDesc);
Promise<PermissionResult> request(PermissionDescriptor permissionDesc);
Promise<PermissionResult> revoke(PermissionDescriptor permissionDesc);
};
When the query()#dom-permissions-queryReferenced in:4. Permission Registry 8. Permissions interface method is invoked, the user agent MUST run the following query a permission algorithm, passing the parameter permissionDesc:
- If permissionDesc.name has an associated PermissionDescriptor, convert the underlying ECMAScript object to the associated PermissionDescriptor dictionary as described in [WEBIDL], then:
- Let promise be a newly-created
Promise
. - Return promise and continue the following steps asynchronously.
- Run the steps to create a PermissionResult for permissionDesc, and let result be the result.
- Run the steps to update the state on result.
- Resolve promise with result.
Promise
.all()
. An example can be
found in the Examples section. When the request()#dom-permissions-requestReferenced in:4. Permission Registry 8. Permissions interface method is invoked, the user agent MUST run the following request a permission algorithm, passing the parameter permissionDesc:
- If permissionDesc.name has an associated PermissionDescriptor, convert the underlying ECMAScript object to the associated PermissionDescriptor dictionary as described in [WEBIDL], then:
- Let promise be a newly-created
Promise
. - Return promise and continue the following steps asynchronously.
- Let permission be the permission named by
permissionDesc.name
. - Run the steps to create a PermissionResult for permissionDesc, and let result be the result.
- Run the steps to retrieve the permission storage for permission, and let storage be the result.
- Let requestPromise be the result of promise-calling permission’s permission request algorithm with storage, permissionDesc, and result as arguments.
- Resolve promise with the result of transforming requestPromise with a fulfillment handler that runs the following steps.
- Get a permission storage identifier for
permissionDesc.name
and the current environment settings object, and create a permission storage entry mapping this identifier to storage. - Return result.
When the revoke()#dom-permissions-revokeReferenced in:4. Permission Registry 8. Permissions interface method is invoked, the user agent MUST run the following revoke a permission algorithm, passing the parameter permissionDesc:
- If permissionDesc.name has an associated PermissionDescriptor, convert the underlying ECMAScript object to the associated PermissionDescriptor dictionary as described in [WEBIDL], then:
- Let promise be a newly-created
Promise
. - Return promise and continue the following steps asynchronously.
- Get a permission storage identifier for
permission.name
and the current environment settings object, and let identifier be the result. - Run the steps to delete a permission storage entry using identifier.
- Run permissionDesc.name’s permission revocation algorithm.
- Run the steps to create a PermissionResult for permissionDesc, and let result be the result.
- Run the steps to update the state on result.
- Resolve promise with result.
9. Common permission algorithms
The boolean permission query algorithm#boolean-permission-query-algorithmReferenced in:4.
Permission Registry , given a PermissionDescriptor
permissionDesc, a PermissionStorage
storage, and a PermissionResult
result, runs the following steps:
- Set
result.state
tostorage.state
The boolean permission request algorithm#boolean-permission-request-algorithmReferenced in:4.
Permission Registry , given a PermissionDescriptor
permission and a PermissionResult
result, runs the following steps:
- TODO
10. Examples
This example uses the Permissions API to decide whether local news should be shown using the Geolocation API or with a button offering to add the feature.
<script> navigator.permissions.query({name:'geolocation'}).then(function(result) { if (result.state == 'granted') { showLocalNewsWithGeolocation(); } else if (result.state == 'prompt') { showButtonToEnableLocalNews(); } // Don’t do anything if the permission was denied. }); </script>
This example is using the "notifications"
permission for a
chat application to show a notification button depending on the
permission state.
<script> function updateNotificationButton(state) { document.getElementById('chat-notification-button').disabled = (state == 'denied'); } navigator.permissions.query({name:'notifications'}).then(function(result) { updateNotificationButton(result.state); result.addEventListener('change', function() { updateNotificationButton(this.state); }); }); </script>
This example is checking whether the page has the "geolocation"
and the "notifications"
permissions
using
. Promise
.all
<script> Promise.all([navigator.permissions.query({name:'geolocation'}), navigator.permissions.query({name:'notifications'})]) .then(function(result) { console.log('Geolocation permission state is ' + result[0].state); console.log('Notifications permission state is ' + result[1].state); }); </script>
This example is checking whether the page has the "camera"
permission for the first device in the list. Error checking omitted.
<script> navigator.mediaDevices.enumerateDevices() .then(function(devices) { return navigator.permissions.query({name:'camera', deviceId: devices[0].deviceId}) }) .then(function(result) { console.log('Camera permission to first camera is ' + result.state); } .catch(err => console.log('Bad things happened: ' + err.name)); </script>
Acknowledgments
The editors would like to thank Adrienne Porter Felt, Anne van Kesteren, Domenic Denicola, Jake Archibald and Wendy Seltzer for their help with the API design and editorial work.