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) (4) (5) (6)9. Common permission algorithms (2) { required PermissionName name#dom-permissiondescriptor-nameReferenced in:6. Status of a permission (2); };
A permission is described by a name and other properties that depend on the
name. The simplest permissions require only a name, but some others have
more detailed structure that requires more information to describe it. In
that case, they should define a customized permission descriptor
type 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:
- A permission descriptor type#permission-descriptor-typeReferenced 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
-
PermissionStatus
or one of its subtypes. If unspecified, this defaults toPermissionStatus
. - A permission query algorithm#permission-query-algorithmReferenced in:4.5. Media Devices 6. Status of a permission
- Takes an instance of the permission descriptor 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 PermissionStatus 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 permission descriptor type,
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].
- permission descriptor type
-
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].
- permission descriptor type
-
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].
- permission descriptor type
-
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 PermissionStatus#permissionstatusReferenced 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;
};
PermissionStatus
instances are created with the following
internal slots:
- [[permission]]#dom-permissionstatus-permission-slotReferenced in:6. Status of a permission (2) (3)
- A permission.
[[permission]]
is always the permission named
.[[query]]
.name
- [[query]]#dom-permissionstatus-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 PermissionStatus
instance status are as
follows:
- Run the steps to retrieve the permission storage for
status@
, and let storage be the result.[[query]]
.name
- Run
status@
’s permission query algorithm, passing[[permission]]
status@
, storage, and status.[[query]]
The steps to create a PermissionStatus#create-a-permissionstatusReferenced in:8.
Permissions interface (2) (3) for a given PermissionDescriptor
permissionDesc are as follow:
- Let permission be the permission named by
permissionDesc.name
. -
Let status be a new instance of permission’s permission result type, with the internal slots filled as:
Slot Value [[permission]]
permission [[query]]
permissionDesc - Return status.
The state#dom-permissionstatus-stateReferenced in:6. Status of a permission attribute MUST return the latest value that was set on the current instance.
The onchange#dom-permissionstatus-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 PermissionStatus
instance status has changed,
it MUST asynchronously run the following steps:
- Run the steps to update the state of status.
- Queue a task on the permission task source to fire an event named
change
at status.
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<PermissionStatus> query(PermissionDescriptor permissionDesc);
Promise<PermissionStatus> request(PermissionDescriptor permissionDesc);
Promise<PermissionStatus> 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 a permission descriptor type other thanPermissionDescriptor
, convert the underlying ECMAScript object to the permission descriptor type 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 PermissionStatus for permissionDesc, and let status be the result.
- Run the steps to update the state on status.
- Resolve promise with status.
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 a permission descriptor type other thanPermissionDescriptor
, convert the underlying ECMAScript object to the permission descriptor type 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 PermissionStatus for permissionDesc, and let status be the result.
- Run the steps to retrieve the permission storage for permission, and let storage be the result.
- Let result be the result of promise-calling permission’s permission request algorithm with storage, permissionDesc, and status as arguments.
- Resolve promise with the result of transforming result 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 status.
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 a permission descriptor type other thanPermissionDescriptor
, convert the underlying ECMAScript object to the permission descriptor type 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 PermissionStatus for permissionDesc, and let status be the result.
- Run the steps to update the state on status.
- Resolve promise with status.
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 PermissionStatus
status, runs the following steps:
- Set
status.state
tostorage.state
The boolean permission request algorithm#boolean-permission-request-algorithmReferenced in:4.
Permission Registry , given a PermissionDescriptor
permission and a PermissionStatus
status, 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.