CARVIEW |
Permissions
Interacting with Permissions for Powerful Features
More details about this document
- This version:
- https://www.w3.org/TR/2022/WD-permissions-20220225/
- Latest published version:
- https://www.w3.org/TR/permissions/
- Latest editor's draft:
- https://w3c.github.io/permissions/
- History:
- https://www.w3.org/standards/history/permissions
- Commit history
- Editors:
- Marcos Cáceres (W3C)
- Mike Taylor (Google LLC)
- Former editors:
- Mounir Lamouri (Google LLC)
- Jeffrey Yasskin (Google LLC)
- Feedback:
- GitHub w3c/permissions (pull requests, new issue, open issues)
Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply.
Abstract
This specification defines common infrastructure that other specifications can use to interact with browser permissions. These permissions represent a user's choice to allow or deny access to "powerful features" of the platform. For developers, the specification standardizes an API to query the permission state of a powerful feature, and be notified if a permission to use a powerful feature changes state.
Status of This Document
This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This is a work in progress.
This document was published by the Web Application Security Working Group as a Working Draft using the Recommendation track.
Publication as a Working Draft does not imply endorsement by W3C and its Members.
This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the 1 August 2017 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
This document is governed by the 2 November 2021 W3C Process Document.
This section is non-normative.
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);
}
This section specifies a model for permissions to use powerful features on the Web platform.
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 state.
Express permission refers to the user granting the web application the ability to use a powerful feature.
Conceptually, a permission for a powerful feature can be in one of the following states:
- Denied:
- The user, or the user agent on the user's behalf, has denied access to this powerful feature. The caller will can't use the feature.
- Granted:
- The user, or the user agent on the user's behalf, has given express permission to use a powerful feature. The caller will can use the feature possibly without having the user agent asking the user's permission.
- Prompt:
- The user has not given express permission to use the feature (i.e., it's the same a 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 denied.
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 granted before it reverts back to its default state. A 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 prompt), which is the 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 lifetime has expired.
A powerful feature is a web platform feature (usually an API) for which a user gives express permission before the feature can be used. Except for a few notable exceptions (e.g., the Notifications API Standard), most powerful features are also policy-controlled features. For powerful features that are also policy-controlled features, [Permissions-Policy] controls whether a document is allowed to use a given feature. That is, a powerful feature can only request express permission from a user if the document has permission delegated to it via the corresponding policy-controlled feature (see example below). Subsequent access to the feature is determined by the user having granted permission, or by satisfying some criteria that is equivalent to a 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 realm has the user's permission to use via the environment settings object.
Each powerful feature can define zero or more additional aspects. An aspect is defined as WebIDL dictionary that
inherits from PermissionDescriptor
and serves as an WebIDL
interface's permission descriptor type.
When a conforming specification specifies a powerful feature it:
- MUST give the powerful feature a name in the form of a ascii lowercase string.
- MAY define a permission descriptor type that inherits from
PermissionDescriptor
. - MAY define zero or more aspects.
- MAY override the algorithms and types given below if the defaults are not suitable for a particular powerful feature.
- MUST register the powerful feature in the Registry of Powerful Features.
Registering the newly specified powerful features in the Registry of Powerful Features gives this Working Group an opportunity to provide feedback and check that integration with this specification is done effectively.
- A permission descriptor type:
-
PermissionDescriptor
or one of its subtypes. If unspecified, this defaults toPermissionDescriptor
.The feature can define a partial order on descriptor instances. If descriptorA is stronger than descriptorB, then if descriptorA's permission state is "
granted
", descriptorB's permission state must also be "granted
", and if descriptorB's permission state is "denied
", descriptorA's permission state must also be "denied
".{name: "midi", sysex: true}
("midi-with-sysex") is 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:
-
Note
Some powerful features have more information associated with them than just a
PermissionState
. For example,getUserMedia
()
needs to determine which cameras the user has granted the current realm record permission to access. Each of these features defines an extra permission data type. If aDOMString
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:- If settings wasn't passed, set it to the current settings object.
- 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.
- Return the instance of name's extra permission data type that matches the UA's impression of the user's intent, taking into account any extra permission data constraints for name.
If specified, the 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 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 toPermissionStatus
. - A permission query algorithm:
-
Takes an instance of the permission descriptor type 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
(permissionDesc)
method and thePermissionStatus
update steps. If unspecified, this defaults to the default permission query algorithm.The default permission query algorithm, given a
PermissionDescriptor
permissionDesc and aPermissionStatus
status, runs the following steps:- Set
status.state
to permissionDesc's permission state.
- Set
- 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 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 lifetime:
-
Specifications that define one or more powerful features SHOULD suggest a 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 lifetime is specified, the user agent provides one.
When the permission lifetime expires for an origin:
- Set the permission back to its default permission state (e.g. setting it back to "prompt").
- 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 permission revocation algorithm.
Note: Determining the lifetime of a permission - Default permission state:
-
An
PermissionState
value that serves as a permission's default state of a powerful feature.If not specified, the permission's default state is "
prompt
".
A default powerful feature is a powerful feature with all of the above types and algorithms defaulted.
To get the current
permission state, given a name name and an optional
environment settings object settings. This algorithm returns one of the
PermissionState
enum values:
- Let descriptor be a newly-created
PermissionDescriptor
whosename
is initialized with name. - 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. It returns a PermissionState
enum value:
- If settings wasn't passed, set it to the current settings object.
- If settings is a non-secure context, return "
denied
". - Let feature be descriptor's
name
. - If there exists a policy-controlled feature for feature and settings has an
associated
Document
run the following step:- Let document be settings's associated
Document
. - If document is not allowed to use feature, return
"
denied
".
- Let document be settings's associated
- 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.
- Return the
PermissionState
enum value that presents permission state of feature, taking into account any permission state constraints for descriptor'sname
.
Safari is the only known UA that returns different results from this algorithm for different settings objects with the same origin. We should test which of the several possible settings objects it uses.
As a shorthand, a DOMString
name's permission state is the permission
state of a PermissionDescriptor
with its name
member set
to name.
To request permission to use a descriptor, the UA must perform the
following steps. This algorithm returns either "granted
" or
"denied
".
- Let current state be the descriptor's permission state.
- If current state is not "
prompt
", return current state and abort these steps. - Ask the user for express permission for the calling algorithm to use the powerful feature described by descriptor.
- If the user gives express permission to use the powerful feature, return
"
granted
"; otherwise return "denied
". The user's interaction may provide new information about the user's intent for this realm and other realms with the same origin.NoteThis 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 DOMString
name, is the same
as requesting permission to use a PermissionDescriptor
with its
name
member set to name.
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
"denied
" or one of the options.
- If descriptor's permission state is "
denied
", return "denied
" and abort these steps. - If descriptor's permission state is "
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. - 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.
- If the user chose an option, return it; otherwise return
"
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 realms with the same origin.NoteThis 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
DOMString
name, is the same as prompting the user to choose from those
options associated with a PermissionDescriptor
with its name
member set to name.
When the UA learns that the user no longer intends to grant permission for a realm to use a feature, react to the user revoking permission by:
- Queue a task on the Realm's settings object's responsible event loop to run that feature's permission revocation algorithm.
WebIDL[Exposed=(Window)]
partial interface Navigator {
[SameObject] readonly attribute Permissions permissions
;
};
[Exposed=(Worker)]
partial interface WorkerNavigator {
[SameObject] readonly attribute Permissions permissions
;
};
WebIDL[Exposed=(Window,Worker)]
interface Permissions
{
Promise<PermissionStatus
> query
(object permissionDesc);
};
dictionary PermissionDescriptor
{
required DOMString name
;
};
When the query()
method is invoked, the user agent MUST run the
following query a permission algorithm, passing the parameter
permissionDesc:
- If this's relevant global object is a
Window
object, then:- If the current settings object's associated
Document
is not fully active, return a promise rejected with a "InvalidStateError
"DOMException
.
- If the current settings object's associated
- If permissionDesc's
name
member is not supported, return a promise rejected with aTypeError
.Note: Why is this not an enum? - Let rootDesc be the object permissionDesc refers to, converted to an IDL
value of type
PermissionDescriptor
. - If the conversion throws an exception, return a promise rejected with that exception.
- Let typedDescriptor be the object permissionDesc refers to, converted to an
IDL value of rootDesc's
name
's permission descriptor type. - If the conversion throws an exception, return a promise rejected with that exception.
- Let promise be a new promise.
- Return promise and continue in parallel:
- Let status be create a
PermissionStatus
with typedDescriptor. - Let query be status's
[[query]]
internal slot. - Run query's
name
's permission query algorithm, passing query and status. - Resolve promise with status.
- Let status be create a
WebIDL[Exposed=(Window,Worker)]
interface PermissionStatus
: EventTarget {
readonly attribute PermissionState
state
;
readonly attribute DOMString 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 permission descriptor type.
The "granted
", "denied
", and "prompt
" enum values
represent the concepts of granted, denied, and
prompt respectively.
To create a PermissionStatus
for a given
PermissionDescriptor
permissionDesc:
- Let name be permissionDesc's
name
. - Assert: The feature identified by name is supported by the user agent.
- Let status be a new instance of the permission result type identified by name:
- Return status.
The name
attribute returns the value it was initialized to.
The state
attribute returns the latest value that was set on the current
instance.
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:
- If this's relevant global object is a
Window
object, then:- Let document be status's relevant global object's associated Document.
- If document is null or document is not fully active, terminate this algorithm.
- Let query be status's
[[query]]
internal slot. - Run query's
name
's permission query algorithm, passing query and status. -
Queue a task on the permissions task source to
fire an event named
change
at status.
A PermissionStatus
object MUST NOT be garbage collected if it has an event listener whose type is change
.
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY, MUST, MUST NOT, OPTIONAL, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
Two classes of product can claim conformance to this specification: user agents and other specifications (i.e., a technical report that specifies a powerful feature in a manner that conforms to the requirements of this specification).
This section is non-normative.
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 Registry of Powerful Features.
On the other hand, the Permissions Policy specification allows developers to
selectively enable and disable powerful features through a "permissions policy" (be it a HTTP header or a the allow
attribute). The APIs and features
in scope for the Permissions Policy specification go beyond those identified in the
Registry of Powerful Features (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 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 name, and they are usually named the same thing (e.g., "geolocation" of the Geolocation API, 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 granted is by the user giving express permission or by some user agent policy.
For the purposes of user-agent automation and application testing, this document defines the following extension commands for the [WebDriver] specification. It is OPTIONAL for a user agent to support extension commands commands.
WebIDLdictionary PermissionSetParameters
{
required PermissionDescriptor
descriptor
;
required PermissionState
state
;
boolean oneRealm
= false;
};
HTTP Method | URI Template |
---|---|
POST | /session/{session id}/permissions |
The Set Permission extension command simulates user modification of a
PermissionDescriptor
's permission state.
The remote end steps are:
- Let parameters be the parameters argument, converted to an IDL value of
type
PermissionSetParameters
. If this throws an exception, return a invalid argument error. - Let rootDesc be parameters.
descriptor
. - Let typedDescriptor be the object rootDesc refers to, converted to an IDL
value of
rootDesc.
's permission descriptor type. If this throws an exception, return a invalid argument error.name
- If parameters.
state
is an inappropriate permission state for any implementation-defined reason, return a invalid argument error.NoteFor example, user agents that define the "midi" powerful feature as "always on" may choose to reject command to set the permission state to "
denied
" at this step. - Let settings be the current settings object.
- If parameters.
oneRealm
is true, let targets be a list whose sole member is settings. - Otherwise, let targets be a list containing all environment settings objects whose origin is the same as the origin of settings.
- Let tasks be an empty list.
- For each environment settings object target in targets:
-
Queue a task task on the permissions task source of target's
relevant settings object's global object's
browsing context to perform the following step:
- Interpret parameters.
state
as if it were the result of an invocation of permission state for typedDescriptor with the argument target made at this moment.
- Interpret parameters.
- Append task to tasks.
-
Queue a task task on the permissions task source of target's
relevant settings object's global object's
browsing context to perform the following step:
- Wait for all tasks in tasks to have executed.
- Return success with data
null
.
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 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 state of powerful features associated with a realm or origin.
There are no documented security considerations at this time. Readers are instead encouraged to read section C. Privacy considerations.
WebIDL[Exposed=(Window)]
partial interface Navigator {
[SameObject] readonly attribute Permissions permissions
;
};
[Exposed=(Worker)]
partial interface WorkerNavigator {
[SameObject] readonly attribute Permissions permissions
;
};
[Exposed=(Window,Worker)]
interface Permissions
{
Promise<PermissionStatus
> query
(object permissionDesc);
};
dictionary PermissionDescriptor
{
required DOMString name
;
};
[Exposed=(Window,Worker)]
interface PermissionStatus
: EventTarget {
readonly attribute PermissionState
state
;
readonly attribute DOMString name
;
attribute EventHandler onchange
;
};
enum PermissionState
{
"granted
",
"denied
",
"prompt
",
};
dictionary PermissionSetParameters
{
required PermissionDescriptor
descriptor
;
required PermissionState
state
;
boolean oneRealm
= false;
};
This section is non-normative.
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.
- [dom]
- DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
- [ecmascript]
- ECMAScript Language Specification. Ecma International. URL: https://tc39.es/ecma262/multipage/
- [HTML]
- HTML Standard. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
- [infra]
- Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
- [Notifications]
- Notifications API Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://notifications.spec.whatwg.org/
- [Permissions-Policy]
- Permissions Policy. Ian Clelland. W3C. 16 July 2020. W3C Working Draft. URL: https://www.w3.org/TR/permissions-policy-1/
- [powerful-feature-registry]
- Registry of Powerful Features. unofficial. URL: https://w3c.github.io/powerful-features-registry/
- [RFC2119]
- Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
- [RFC8174]
- Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc8174
- [WebDriver]
- WebDriver. Simon Stewart; David Burns. W3C. 5 June 2018. W3C Recommendation. URL: https://www.w3.org/TR/webdriver1/
- [WEBIDL]
- Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/
- [appmanifest]
- Web Application Manifest. Marcos Caceres; Kenneth Christiansen; Matt Giuca; Aaron Gustafson; Daniel Murphy; Anssi Kostiainen. W3C. 17 February 2022. W3C Working Draft. URL: https://www.w3.org/TR/appmanifest/
- [Geolocation]
- Geolocation API. Marcos Caceres; Reilly Grant. W3C. 21 February 2022. W3C Candidate Recommendation. URL: https://www.w3.org/TR/geolocation/
- [GETUSERMEDIA]
- Media Capture and Streams. Cullen Jennings; Bernard Aboba; Jan-Ivar Bruaroey; Henrik Boström; youenn fablet. W3C. 10 February 2022. W3C Candidate Recommendation. URL: https://www.w3.org/TR/mediacapture-streams/
- [push-api]
- Push API. Peter Beverloo; Martin Thomson. W3C. 3 November 2021. W3C Working Draft. URL: https://www.w3.org/TR/push-api/
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
- § 1. Examples of usage
- § 2. Model
- § 2.1 Permissions (2) (3) (4) (5) (6) (7)
- § 2.2 Powerful features (2)
- § 2.2.1 Aspects (2)
- § 3. Specifying a powerful feature (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12)
- § 4.2 Requesting permission to use a powerful feature
- § 4.4 Reacting to users revoking permission
- § 5.3.1 Creating instances
- § A. Relationship to the Permissions Policy specification (2) (3)
- § B.1 Set Permission
- § C. Privacy considerations
Referenced in:
Referenced in:
Referenced in:
Referenced in:
- § 3. Specifying a powerful feature (2) (3) (4) (5) (6) (7) (8)
- § 4.1 Reading the current permission state (2) (3)
- § 4.2 Requesting permission to use a powerful feature
- § 4.3 Prompt the user to choose (2)
- § A. Relationship to the Permissions Policy specification
- § B.1 Set Permission (2) (3) (4)
- § C. Privacy considerations
Referenced in:
- § 2.2.1 Aspects (2)
- § 3. Specifying a powerful feature (2) (3) (4)
- § 4.1 Reading the current permission state (2)
- § 4.2 Requesting permission to use a powerful feature
- § 4.3 Prompt the user to choose
- § 5.2.1 query() method
- § 5.3.1 Creating instances
- § B. Automated testing
- § B.1 Set Permission
- § E. IDL Index
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: