Interacting with Permissions for Powerful Features

This specification defines common infrastructure that other specifications can use to interact with browser permissions that allow or deny access to powerful features on the web platform. For developers, the specification defines an API to query the permission state of a powerful feature, or be notified if a permission for a powerful feature changes state.

This is a work in progress.

Some features in this specification are supported by only one user agent, and as such, are marked as at risk.

Examples of usage

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.

        const { state } = await navigator.permissions.query({
          name: "geolocation"
        });
        switch (state) {
          case "granted":
            showLocalNewsWithGeolocation();
            break;
          case "prompt":
            showButtonToEnableLocalNews();
            break;
          case "denied":
            showNationalNews();
            break;
        }
      

This example simultaneously checks the state of the `"geolocation"` and `"notifications"` [=powerful features=]:

        const queryPromises = ["geolocation", "notifications"].map(
          name => navigator.permissions.query({ name })
        );
        for await (const status of queryPromises) {
          console.log(`${status.name}: ${status.state}`);
        }
      

This example is checking the permission state of the available cameras.

        const devices = await navigator.mediaDevices.enumerateDevices();

        // filter on video inputs, and map to query object
        const queries = devices
          .filter(({ kind }) => kind === "videoinput")
          .map(({ deviceId }) => ({ name: "camera", deviceId }));

        const promises = queries.map((queryObj) =>
          navigator.permissions.query(queryObj)
        );

        try {
          const results = await Promise.all(promises);
          // log the state of each camera
          results.forEach(({ state }, i) => console.log("Camera", i, state));
        } catch (error) {
          console.error(error);
        }
      

Model

This section specifies a model for [=permissions=] to use [=powerful features=] on the Web platform.

Permissions

A permission represents a user's decision as to whether a web application can use a [=powerful feature=]. The decision is represented as a permission [=permission/state=].

Express permission refers to an act by the user, e.g. via user interface or host device platform features, through which the user [=permission/grants=] [=permission=] for use of a feature by a web application.

Conceptually, a [=permission=] for a [=powerful feature=] can be in one of the following states:

Prompt:
The user has not given [=express permission=] to use the feature (i.e., it's the same a [=permission/denied=]). It also means that if caller attempts to use the feature, the [=user agent=] will either be prompting the user for permission or access to the feature will be [=permission/denied=].
Granted:
The user, or the user agent on the user's behalf, has given [=express permission=] to use a [=powerful feature=]. The caller will be able to use the feature possibly without having the [=user agent=] asking the user's permission.
Denied:
The user, or the user agent on the user's behalf, has denied access to this [=powerful feature=]. The caller will not be able to use the feature.

To ascertain new information about the user's intent, a user agent MAY collect information about a user's intentions. This information can come from explicit user action, aggregate behavior of both the relevant user and other users, or implicit signals this specification hasn't anticipated.

Every [=permission=] has a lifetime, which is the duration for which a particular permission remains [=permission/granted=] before it reverts back to its [=permission/default state=]. A [=permission/lifetime=] could be until a particular Realm is destroyed, until a particular [=top-level browsing context=] is destroyed, an amount of time, or infinite. The lifetime is negotiated between the end-user and the [=user agent=] when the user gives [=express permission=] to use a [=feature=]—usually via some permission UI or user-agent defined policy.

Every permission has a default state (usually [=permission/prompt=]), which is the [=permission/state=] that the permission is in when the user has not yet given [=express permission=] to use the [=feature=] or it has been reset because its [=permission/lifetime=] has expired.

Powerful features

A powerful feature is a web platform feature (usually an API) for which a user gives [=express permission=] before the feature can be used. Access to the feature is determined by the environment settings object by the user having [=permission/granted=] permission via UI, or by satisfying some criteria that is equivalent to a permission [=permission/grant=].

A [=powerful feature=] is identified by its name, which is a string literal (e.g., "geolocation").

The user agent is responsible for tracking what powerful features each [=global object/realm=] has the user's [=permission=] to use. Other specifications can use the operations defined in this section to retrieve the UA's notion of what permissions are granted or denied, and to ask the user to grant or deny more permissions.

Aspects

Each powerful feature can define zero or more additional aspects that websites can request permission to access.

To describe an [=powerful feature/aspect=], a specification MUST define a WebIDL [=dictionary=] that [=dictionary/inherits=] from {{PermissionDescriptor}}, and have that interface be its [=powerful feature/permission descriptor type=].

Reading the current permission state

To get the current permission state, given a [=powerful feature/name=] |name| and an optional [=environment settings object=] |settings|:

  1. Let |descriptor:PermissionDescriptor| be a newly-created {{PermissionDescriptor}} whose {{PermissionDescriptor/name}} is initialized with |name|.
  2. Return the [=permission state=] of |descriptor| and |settings|.

A |descriptor|'s permission state for an optional environment settings object |settings| is the result of the following algorithm, which returns one of {{PermissionState/"granted"}}, {{PermissionState/"prompt"}}, or {{PermissionState/"denied"}}:

  1. If |settings| wasn't passed, set it to the [=current settings object=].
  2. If |settings| is a non-secure context, return {{PermissionState/"denied"}}.
  3. If there exists a [=policy-controlled feature=] identified by |descriptor|'s {{PermissionDescriptor/name}} and |settings| has an associated `Document` named document, run the following step:
    1. If document is not allowed to use the feature identified by |descriptor|'s {{PermissionDescriptor/name}} return {{PermissionState/"denied"}}.
  4. If there was a previous invocation of this algorithm with the same |descriptor| and |settings|, returning |previousResult|, and the UA has not received new information about the user's intent since that invocation, return |previousResult|.
  5. Return whichever of the following options most accurately reflects the user's intent for the calling algorithm, taking into account any [=powerful feature/permission state constraints=] for |descriptor|'s {{PermissionDescriptor/name}}:
    succeed without prompting the user
    {{PermissionState/"granted"}}
    show the user a prompt to decide whether to succeed
    {{PermissionState/"prompt"}}
    fail without prompting the user
    {{PermissionState/"denied"}}

As a shorthand, a {{PermissionName}} |name|'s permission state is the permission state of a {{PermissionDescriptor}} with its {{PermissionDescriptor/name}} member set to |name|.

Requesting permission to use a powerful feature

Spec authors, please note that algorithms in this section can wait for user input; so they shouldn't be used from other algorithms running on the main thread.

To request permission to use a |descriptor|, the UA must perform the following steps. This algorithm returns either {{PermissionState/"granted"}} or {{PermissionState/"denied"}}.

  1. Let current state be the |descriptor|'s permission state.
  2. If current state is not {{PermissionState/"prompt"}}, return current state and abort these steps.
  3. Ask the user for express permission for the calling algorithm to use the powerful feature described by |descriptor|.
  4. If the user grants permission, return {{PermissionState/"granted"}}; otherwise return {{PermissionState/"denied"}}. The user's interaction may provide new information about the user's intent for this [=global object/realm=] and other [=global object/realms=] with the same origin.

    This is intentionally vague about the details of the permission UI and how the UA infers user intent. UAs should be able to explore lots of UI within this framework.

As a shorthand, requesting permission to use a {{PermissionName}} |name|, is the same as requesting permission to use a {{PermissionDescriptor}} with its {{PermissionDescriptor/name}} member set to |name|.

Prompt the user to choose

To prompt the user to choose one of several |options| associated with a |descriptor|, the UA must perform the following steps. This algorithm returns either {{PermissionState/"denied"}} or one of the options.

  1. If |descriptor|'s permission state is {{PermissionState/"denied"}}, return {{PermissionState/"denied"}} and abort these steps.
  2. If |descriptor|'s permission state is {{PermissionState/"granted"}}, the UA may return one of |options| and abort these steps. If the UA returns without prompting, then subsequent prompts for the user to choose from the same set of options with the same |descriptor| must return the same option, unless the UA receives new information about the user's intent.
  3. Ask the user to choose one of the options or deny permission, and wait for them to choose. If the calling algorithm specified extra information to include in the prompt, include it.
  4. If the user chose an option, return it; otherwise return {{PermissionState/"denied"}}. If the user's interaction indicates they intend this choice to apply to other realms, then treat this this as new information about the user's intent for other [=global object/realms=] with the same origin.

    This is intentionally vague about the details of the permission UI and how the UA infers user intent. UAs should be able to explore lots of UI within this framework.

As a shorthand, prompting the user to choose from options associated with a {{PermissionName}} |name|, is the same as prompting the user to choose from those options associated with a {{PermissionDescriptor}} with its {{PermissionDescriptor/name}} member set to |name|.

Reacting to users revoking permission

When the UA learns that the user no longer intends to grant permission for a [=global object/realm=] to use a feature, react to the user revoking permission by:

  1. Queue a task on the Realm's [=Realm/settings object=]'s [=environment settings object/responsible event loop=] to run that feature's [=powerful feature/permission revocation algorithm=].

Specifying a powerful feature

Please register newly specified [=powerful features=] in the [[[powerful-feature-registry]]]. Doing so also provides this Working Group an opportunity to provide feedback and check that integration with this specification is done effectively.

Each powerful feature has the following permission-related algorithms and types. When the defaults are not suitable for a particular [=powerful feature=], a specification MAY override below algorithms and types below.

A permission descriptor type:

{{PermissionDescriptor}} or one of its subtypes. If unspecified, this defaults to {{PermissionDescriptor}}.

The feature can define a partial order on descriptor instances. If |descriptorA| is stronger than |descriptorB|, then if |descriptorA|'s permission state is {{PermissionState/"granted"}}, |descriptorB|'s permission state must also be {{PermissionState/"granted"}}, and if |descriptorB|'s permission state is {{PermissionState/"denied"}}, |descriptorA|'s permission state must also be {{PermissionState/"denied"}}.

{name: "midi", sysex: true} ("midi-with-sysex") is [=PermissionDescriptor/stronger than=] {name: "midi", sysex: false} ("midi-without-sysex"), so if the user denies access to midi-without-sysex, the UA must also deny access to midi-with-sysex, and similarly if the user grants access to midi-with-sysex, the UA must also grant access to midi-without-sysex.

permission state constraints:
Constraints on the values that the UA can return as a descriptor's permission state. Defaults to no constraints beyond the user's intent.
extra permission data type:

Some powerful features have more information associated with them than just a {{PermissionState}}. For example, {{MediaDevices/getUserMedia()}} needs to determine which cameras the user has granted [=ECMAScript/the current realm record=] permission to access. Each of these features defines an [=powerful feature/extra permission data type=]. If a {{DOMString}} |name| names one of these features, then |name|'s extra permission data for an optional environment settings object |settings| is the result of the following algorithm:

  1. If |settings| wasn't passed, set it to the [=current settings object=].
  2. If there was a previous invocation of this algorithm with the same |name| and |settings|, returning |previousResult|, and the UA has not received new information about the user's intent since that invocation, return |previousResult|.
  3. Return the instance of |name|'s [=powerful feature/extra permission data type=] that matches the UA's impression of the user's intent, taking into account any [=powerful feature/extra permission data constraints=] for |name|.

If specified, the [=powerful feature/extra permission data=] algorithm is usable for this feature.

Optional extra permission data constraints:
Constraints on the values that the UA can return as a [=powerful feature=]'s [=powerful feature/extra permission data=]. Defaults to no constraints beyond the user's intent.
A permission result type:
{{PermissionStatus}} or one of its subtypes. If unspecified, this defaults to {{PermissionStatus}}.
A permission query algorithm:

Takes an instance of the [=powerful feature/permission descriptor type=] and a new or existing instance of the [=powerful feature/permission result type=], and updates the [=powerful feature/permission result type=] instance with the query result. Used by {{Permissions}}' {{Permissions/query(permissionDesc)}} method and the [=`PermissionStatus` update steps=]. If unspecified, this defaults to the default permission query algorithm.

The default permission query algorithm, given a {{PermissionDescriptor}} permissionDesc and a {{PermissionStatus}} |status|, runs the following steps:

  1. Set |status|.state to |permissionDesc|'s permission state.
A permission revocation algorithm:

Takes no arguments. Updates any other parts of the implementation that need to be kept in sync with changes in the results of permission states or [=powerful feature/extra permission data=], and then [=react to the user revoking permission=].

If unspecified, this defaults to running [=react to the user revoking permission=].

A permission [=permission/lifetime=]:

Specifications that define one or more [=powerful features=] SHOULD suggest a [=permission=] [=permission/lifetime=] that is best suited for the particular feature. Some guidance on determining the lifetime of a permission is noted below, with a strong emphasis on user privacy. If no [=permission/lifetime=] is specified, the user agent provides one.

When the permission [=permission/lifetime=] expires for an origin:

  1. Set the permission back to its default [=permission state=] (e.g. setting it back to "[=permission/prompt=]").
  2. For each |browsing context| associated with the origin (if any), [=queue a global task=] on the [=permissions task source=] with the |browsing context|'s [=global object=] to run the [=powerful feature/permission revocation algorithm=].
Default permission state:

An {{PermissionState}} value that serves as a [=permission=]'s [=permission/default state=] of a [=powerful feature=].

If not specified, the [=permission=]'s [=permission/default state=] is {{PermissionState/"prompt"}}.

A default powerful feature is a powerful feature with all of the above types and algorithms defaulted.

Permissions API

          [Exposed=(Window)]
          partial interface Navigator {
            [SameObject] readonly attribute Permissions permissions;
          };

          [Exposed=(Worker)]
          partial interface WorkerNavigator {
            [SameObject] readonly attribute Permissions permissions;
          };
        

`Permissions` interface

          [Exposed=(Window,Worker)]
          interface Permissions {
            Promise<PermissionStatus> query(object permissionDesc);
          };

          dictionary PermissionDescriptor {
            required PermissionName name;
          };
        

`query()` method

When the query() method is invoked, the user agent MUST run the following query a permission algorithm, passing the parameter permissionDesc:

  1. Let |rootDesc| be the object |permissionDesc| refers to, converted to an IDL value of type {{PermissionDescriptor}}.
  2. If the conversion [=exception/throws=] an [=exception=], return a promise rejected with that exception.
  3. Let |typedDescriptor| be the object |permissionDesc| refers to, converted to an IDL value of |rootDesc|'s {{PermissionDescriptor/name}}'s [=powerful feature/permission descriptor type=].
  4. If the conversion [=exception/throws=] an [=exception=], return a promise rejected with that exception.
  5. Let promise be [=a new promise=].
  6. Return promise and continue [=in parallel=]:
    1. Let |status| be create a `PermissionStatus` with |typedDescriptor|.
    2. Let |query| be |status|'s {{PermissionStatus/[[query]]}} internal slot.
    3. Run |query|'s {{PermissionDescriptor/name}}'s [=powerful feature/permission query algorithm=], passing |query| and |status|.
    4. [=Resolve=] promise with |status|.

`PermissionStatus` interface

          [Exposed=(Window,Worker)]
          interface PermissionStatus : EventTarget {
            readonly attribute PermissionState state;
            readonly attribute PermissionName name;
            attribute EventHandler onchange;
          };

          enum PermissionState {
            "granted",
            "denied",
            "prompt",
          };
        

{{PermissionStatus}} instances are created with a [[\query]] internal slot, which is an instance of a feature's [=powerful feature/permission descriptor type=].

The "granted", "denied", and "prompt" enum values represent the concepts of [=permission/granted=], [=permission/denied=], and [=permission/prompt=] respectively.

Creating instances

To create a `PermissionStatus` for a given {{PermissionDescriptor}} |permissionDesc|:

  1. Let |name:DOMString| be |permissionDesc|'s {{PermissionDescriptor/name}}.
  2. Assert: The [=feature=] identified by |name| is supported by the user agent.
  3. Let |status:PermissionStatus| be a new instance of the [=powerful feature/permission result type=] identified by |name|:
    1. Initialize |status|'s {{PermissionStatus/[[query]]}} internal slot to |permissionDesc|.
    2. Initialize |status|'s {{PermissionStatus/name}} to |name|.
  4. Return |status|.

`name` attribute

The name attribute returns the value it was initialized to.

`state` attribute

The state attribute returns the latest value that was set on the current instance.

`onchange` attribute

The onchange 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 asynchronously runs the `PermissionStatus` update steps:

  1. Let |query| be |status|'s {{PermissionStatus/[[query]]}} internal slot.
  2. Run |query|'s {{PermissionDescriptor/name}}'s [=powerful feature/permission query algorithm=], passing |query| and |status|.
  3. Queue a task on the permissions task source to fire an event named change at |status|.

Garbage collection

A {{PermissionStatus}} object MUST NOT be garbage collected if it has an [=event listener=] whose type is `change`.

Powerful features registry

        enum PermissionName {
          "accelerometer",
          "ambient-light-sensor",
          "background-fetch",
          "background-sync",
          "bluetooth",
          "camera",
          "display-capture",
          "geolocation",
          "gyroscope",
          "magnetometer",
          "microphone",
          "midi",
          "nfc",
          "notifications",
          "persistent-storage",
          "push",
          "screen-wake-lock",
          "speaker-selection",
          "xr-spatial-tracking",
        };
      

Each enumeration value in the {{PermissionName}} enum identifies a powerful feature.

The accelerometer enum value identifies the [[[?accelerometer]]] API [=powerful feature=].

The ambient-light-sensor enum value identifies the [[[?ambient-light]]] [=powerful feature=].

The background-fetch enum value identifies the [[[?background-fetch]]] [=powerful feature=].

The background-sync enum value identifies the [[[?web-background-sync]]] [=powerful feature=].

The bluetooth enum value identifies the [[[?web-bluetooth]]] [=powerful feature=].

The camera and microphone enum values identify the [[[?mediacapture-streams]]] [=powerful features=].

The display-capture enum value identifies the [[[?screen-capture]]] [=powerful feature=].

The geolocation enum value identifies the [[[?Geolocation]]] [=powerful feature=].

The gyroscope enum value identifies the [[[?gyroscope]]] API [=powerful feature=].

The magnetometer enum value identifies the [[[?magnetometer]]] API [=powerful feature=].

The midi enum value identifies the [[[?webmidi]]] [=powerful feature=].

The nfc enum value identifies the [[[?web-nfc]]] [=powerful feature=].

The notifications enum value identifies the [[[?notifications]]] [=powerful feature=].

The persistent-storage enum value identifies the [[[?storage]]] [=powerful feature=].

The push enum value identifies the [[[?push-api]]] [=powerful feature=].

The speaker-selection enum value identifies the [[[?audio-output]]] [=powerful feature=].

The xr-spatial-tracking enum value identifies the [[[?webxr]]] Device API [=powerful feature=].

Screen wake lock

The screen-wake-lock enum value identifies the [[[screen-wake-lock]]] [=powerful feature=]. It is a [=default powerful feature=].

This [=powerful feature=] only has a single implementation, and therefore, as per the W3C Process, it is [=at risk=].

Relationship to the Permissions Policy specification

Although technically this specification and the [[[Permissions-Policy]]] specification deal with "permissions", each specification serves a distinct purpose in the platform. Nevertheless, the two specifications do explicitly overlap.

On the one hand, this specification exclusively concerns itself with [=powerful features=] whose access is managed through a user-agent mediated permissions UI (i.e., permissions where the user gives express consent before that feature can be used, and where the user retains the ability to deny that permission at any time for any reason). These powerful features are registered in the [[[powerful-feature-registry]]].

On the other hand, the [[[Permissions-Policy]]] specification allows developers to selectively enable and disable [=powerful features=] through a "[=Document/permissions policy=]" (be it a HTTP header or a the [^iframe/allow^] attribute). The APIs and features in scope for the [[[Permissions-Policy]]] specification go beyond those identified in the [[[powerful-feature-registry]]] (e.g., "sync-xhr" and "gamepad"). In that sense, the Permissions Policy subsumes this specification in that [[[Permissions-Policy]]] governs whether a feature is available at all, independently of this specification.

A powerful feature that has been disabled by the [[[Permissions-Policy]]] specification always has its [=permission state=] reflected as "denied" by this specification. This occurs because [=getting the current permission state|reading the current permission=] relies on [[HTML]]'s "[=allowed to use=]" check, which itself calls into the [[[Permissions-Policy]]] specification. Important to note here is the sharing of permission names across both specifications. Both this specification and the [[[Permissions-Policy]]] specification rely on other specifications defining the names of the permission and [=powerful feature/name=], and they are usually named the same thing (e.g., "geolocation" of the [[[Geolocation]]], and so on).

Finally, it's not possible for a powerful feature to ever become "granted" through any means provided by the [[[Permissions-Policy]]] specification. The only way that a [=powerful feature=] can be [=permission/granted=] is by the user giving [=express permission=] or by some user agent policy.

Automated testing

Automated testing of this specification is performed using the API provided by the Permissions Automation document.

Privacy considerations

An adversary could use a permission state as an element in creating a "fingerprint" corresponding to an end-user. Although an adversary can already determine the state of a permission by actually using the API, that often leads to a UI prompt being presented to the end-user (if the permission was not already [=permission/granted=]). Even though this API doesn't expose new fingerprinting information to websites, it makes it easier for an adversary to have discreet access to this information.

A user agent SHOULD provide a means for the user to review, update, and reset the [=permission=] [=permission/state=] of [=powerful features=] associated with a realm or origin.

Security considerations

There are no documented security considerations at this time. Readers are instead encouraged to read section [[[#privacy-considerations]]].

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.