1. Introduction
User agents expose powerful features to web sites, which are features that are important to some use cases, but can be easily abused. The arguably canonical example of such a powerful feature is camera access, which is essential to many use cases like online meetups, but unsolicited camera activation would be a major privacy issue. To handle this, user agents use permissions to ask the user whether they wish for a particular access to be allowed or not.
These permission requests began as a fairly direct passthrough: A site would ask for some capability and the user agent immediately prompts the user to make a decision for the request. Meanwhile, spam and abuse have forced user agents to take a more opinionated approach to protect users' security, privacy, and attention. The status quo is that users get a multitude of permission requests, where it’s oftentimes unclear to users what the consequences of these requests might be.
This spec introduces a new mechanism that requests access to powerful features through an in-page element, with built-in protections against abuse. This wants to tie permission requests to the actual context in which they will be used, thus reducing "permission spam" and at the same time providing implementations with a better signal of user intent.
2.
The
permission
element.
- Categories :
-
Flow
content
.
- Phrasing content .
- Interactive content .
- Palpable content .
- Phrasing content .
- Contexts in which this element can be used :
- Where phrasing content is expected.
- Content model :
- Nothing .
- Content attributes :
-
Global
attributes
type
— Type of permission this element applies to.isValid
— query whether the element can currently be activated.invalidReason
— Return a string representation of why the element currently cannot be activated.ondismiss
— notifies when the user has dismissed the permission prompt.onresolve
— notifies when a permission prompt has been answered by the user (positively or negatively).onvalidationstatuschange
— notifies when the validation status changes. - Accessibility considerations :
- DOM interface :
-
[
Exposed =Window ]interface
:HTMLPermissionElement HTMLElement { [HTMLConstructor ]
(); [constructor CEReactions ,Reflect ]attribute DOMString
;type readonly attribute boolean isValid ;readonly attribute PermissionElementBlockerReason invalidReason ;attribute EventHandler
;onresolve attribute EventHandler
;ondismiss attribute EventHandler
; };onvalidationstatuschange
Add accessibility considerations.
Check attribute & event handler & invalid reason names against current proposal(s).
The
type
attribute
controls
the
behavior
of
the
permission
element
when
it
is
activated.
Is
is
an
enumerated
attribute
,
whose
values
are
the
names
of
powerful
features
.
It
has
neither
a
missing
value
default
state
nor
a
invalid
value
default
state.
The
isValid
attribute
reflects
whether
a
the
permission
element
is
not
currently
blocked.
The
invalidReason
attribute
is
an
enumerated
attribute
that
reflects
the
internal
state
of
the
permission
element.
It’s
value
set
are
PermissionElementBlockerReason
The
global
lang
attribute
is
observed
by
the
permission
element
to
select
localized
text.
The
following
are
the
event
handlers
(and
their
corresponding
event
handler
event
types
)
that
must
be
supported
on
permission
elements
event
handler
IDL
attributes
:
onresolve | Event |
---|---|
ondismiss | Event |
onvalidationstatuschange | Event |
onvalidationstatuschange is probably not a simple Event.
2.1.
permission
element
internal
state
The
permission
element
represents
a
user-requestable
permission
,
which
the
user
can
activate
to
enable
(or
disable)
a
particular
permission
or
set
of
permissions.
It
is
core
to
the
permission
element
that
these
requests
are
triggered
by
the
user,
and
not
by
the
page’s
script.
To
enforce
this,
the
element
checks
whether
the
activation
event
is
trusted
.
Additionally
it
watches
a
number
of
conditions,
like
whether
the
element
is
(partially)
occluded,
or
if
it
has
recently
been
moved.
The
element
maintains
an
internal
[[BlockerList]]
to
keep
track
of
this.
The
permission
element
has
the
following
internal
slots:
-
The
[[BlockerList]]
is a list of records, containing a blocker timestamp and a blocker reason . The blocker reason is aPermissionElementBlockerReason
, but not the empty string. -
[[IntersectionObserver]]
is a reference to anIntersectionObserver
. -
[[Types]]
is null or an ordered set of powerful features . Null represents the uninitialized state, which allows the value to be modified. The empty list «[]» is the state in which no permission applies, and which will no longer allow modification. Note that thetype
property reflects this internal state. -
[[IntersectionRect]]
is aDOMRectReadOnly
that stores the most recently seen intersection, i.e. the position of thepermission
relative to the viewport .
2.2.
permission
-supporting
state
at
the
navigable
In
order
to
support
the
permission
element,
the
navigable
maintains
an
ordered
set
of
permission
elements,
[[PermissionElements]]
.
This
ordered
set
is
used
to
evaluate
the
blockers
of
type
unsuccesful_registration
.
2.3.
permission
element
interesting
behaviours
The
permission
element
has
a
few
surprising
behaviours,
to
support
its
security
properties:
2.3.1.
The
type
property
The
permission
type
cannot
be
modified.
Modifying
the
permission
type
at
will
may
lead
to
user
confusion,
and
hence
we’d
like
to
prevent
it.
Since,
however,
a
page
may
create
a
permission
element
dynamically
we
still
need
to
offer
an
API
to
modify
it.
To
do
do,
we
distinguish
between
a
freshly
initialized
and
an
empty
or
invalid
(no
permission)
state,
where
the
former
allows
setting
the
type
and
the
latter
does
not.
Example:
// Changing a valid type: var pepc= document. createElement( "permission" ); pepc. type= "camera" ; // Okay. pepc. type; // "camera". pepc. type= "geolocation" ; // Not okay. Would have been okay as initial assignment. pepc. type; // "camera". Reflects the internal state, which has not changed. // Setting an invalid type: pepc= document. createElement( "permission" ); pepc. type= "icecream" ; // Ice cream is not a powerful browser feature. Not okay. pepc. type; // "". Reflects the internal state. pepc. type= "camera" ; // Still Not okay, because type as already been set. // Would have been okay as initial assignment. pepc. type; // "". Reflects the internal state, which has not changed.
type
getter
steps
are:
-
If
[[Types]]
is null: Return""
. -
Return a string, containing the concatenation of all powerful feature names in
[[Types]]
, seperated by " ".
type
setter
steps
are:
-
If
[[Types]]
is not null: Return. -
Set
[[Types]]
to «[]». -
Parse the input as a string of powerful feature names, seperated by whitespace.
-
If any errors occured, return.
-
Check if the set of powerful features is supported for the
HTMLPermissionElement
by the user agent . If not, return. -
Append each powerful feature name to the
[[Types]]
ordered set .
Note: The supported sets of powerful features is implementation-defined .
2.3.2. Activation blockers
The
key
goal
of
the
permission
element
is
to
reflect
a
user’s
conscious
choice,
and
we
need
to
make
sure
the
user
cannot
easily
be
tricked
into
activating
it.
To
do
so,
the
permission
maintains
a
list
of
blocker
reasons,
which
may
-
permanently
or
temporarily
-
prevent
the
element
from
being
activated.
enum {
PermissionElementBlockerReason , // No blocker reason.
"" ,
"type_invalid" ,
"illegal_subframe" ,
"unsuccesful_registration" ,
"recently_attached" ,
"intersection_changed" ,
"intersection_out_of_viewport_or_clipped" ,
"intersection_occluded_or_distorted" };
"style_invalid"
The permission element keeps track of "blockers", reasons why the element (currently) cannot be activated. These blockers come with three lifetimes: Permanent, temporary, and expiring.
- Permanent blocker
-
Once an element has a permanent blocker, it will be disabled permanently. There are used for issues that the website owner is expected to fix. An example is a
permission
element inside afencedframe
. - Temporary blocker
-
This is a blocker that will only be valid until the blocking condition no no longer occurs. An example is a
permission
element that is not currently in view. All temporary blockers turn into expiring blockers once the condition no longer applies. - Expiring blocker
-
This is a blocker that is only valid for a fixed period of time. This is used to block abuse scenarios like "click jacking". An example is a
permission
element that has recently been moved.
Blocker name | Blocker type | Example condition | Order hint |
---|---|---|---|
type_invalid
| permanent |
When
an
unsupported
permission
type
has
been
set.
| 1 |
illegal_subframe
| permanent |
When
the
permission
element
is
used
inside
a
fencedframe
.
| 2 |
unsuccesful_registration
| temporary |
When
too
many
other
permission
elements
for
the
same
powerful
feature
have
been
inserted
into
the
same
document.
| 3 |
recently_attached
| expiring |
When
the
permission
element
has
just
been
attached
to
the
DOM.
| 4 |
intersection_changed
| expiring |
When
the
permission
element
is
being
moved.
| 6 |
intersection_out_of_viewport_or_clipped
| temporary |
When
the
permission
element
is
not
or
not
fully
in
the
viewport
.
| 7 |
intersection_occluded_or_distorted
| temporary |
When
the
permission
element
is
fully
in
the
viewport
,
but
still
not
fully
visible
(e.g.
because
it’s
partly
behind
other
content).
| 8 |
style_invalid
| temporary | 9 |
PermissionElementBlockerReason
reason
and
an
optional
flag
expires
:
-
Assert : reason is not
""
. (The empty string inPermissionElementBlockerReason
signals no blocker is present. Why would you add a non-blocking blockern empty string?) -
Let timestamp be None.
-
If expires , then let timestamp be current high resolution time plus the blocker delay .
-
Append an entry to the internal
[[BlockerList]]
with reason and timestamp .
PermissionElementBlockerReason
reason
:
-
Assert : reason is listed as "expiring" in the blocker reason table .
-
Add a blocker with reason and true.
PermissionElementBlockerReason
reason
:
-
Assert : reason is listed as "temporary" in the blocker reason table .
-
Add a blocker with reason and false.
PermissionElementBlockerReason
reason
:
-
Assert : reason is listed as "permanent" in the blocker reason table .
-
Add a blocker with reason and false.
PermissionElementBlockerReason
reason
from
an
element
:
-
Assert : reason is listed as "temporary" in the blocker reason table .
-
For each entry in element ’s
[[BlockerList]]
:-
If entry ’s reason equals reason , then remove entry from element ’s
[[BlockerList]]
.
-
-
Add a blocker with reason and true.
HTMLPermissionElement
element
’s
blocker
:
-
Let blockers be the result of sorting element ’s
[[BlockerList]]
with the blocker ordering algorithm. -
If blockers is not empty and blockers [0] is blocking , then return blockers [0].
-
Return nothing.
-
Let really large number be 99.
-
Assert : No order hint in the blocker reason table is equal to or greater than really large number .
-
If a is blocking , then let a hint be the order hint of a ’s reason in the blocker reason table , otherwise let a hint be really large number .
-
If b is blocking , then let b hint be the order hint of b ’s reason in the blocker reason table , otherwise let b hint be really large number .
-
Return whether a hint is less than or equal to b hint .
HTMLPermissionElement
’s
blocker
list’s
entry
is
blocking
if:
-
entry has no blocker timestamp ,
-
or entry has a blocker timestamp , and the blocker timestamp is greater or equal to the current high resolution time .
NOTE:
The
spec
maintains
blockers
as
a
list
[[BlockerList]]
,
which
may
potentially
grow
indefinitely
(since
some
blocker
types
simply
expire,
but
are
not
removed).
This
structure
is
chosen
for
the
simplicity
of
explanation,
rather
than
for
efficiency.
The
details
of
this
blocker
structure
are
not
observable
except
for
a
handful
of
algorithms
defined
here,
which
should
open
plenty
of
opportunities
for
implementations
to
handle
this
more
efficiently.
2.4.
permission
element
algorithms
HTMLPermissionElement
constructor
steps
are:
-
Initialize the internal
[[Types]]
slot to null. -
Initialize the internal
[[BlockerList]]
to «[]».
HTMLPermissionElement
insertion
steps
are:
-
Initialize the internal
[[BlockerList]]
to «[]». -
Initialize the internal
[[IntersectionRect]]
with undefined. -
Initialize the internal
[[IntersectionObserver]]
with the result of constructing a newIntersectionObserver
, with IntersectionObserver callback . -
Call
[[IntersectionObserver]]
.observe( this ). -
If
[[Types]]
is empty , then add a permanent blocker with reasontype_invalid
. -
If this is not type permissible , then add a temporary blocker with
unsuccesful_registration
. -
Add an expiring blocker with reason
recently_attached
. -
If the traversable navigable of the node navigable of this is a fenced navigable , then add a permanent blocker with
illegal_subframe
.
HTMLPermissionElement
removing
steps
are:
-
Remove this from node navigable ’s
[[PermissionElements]]
.
isValid
getter
steps
are:
-
Return whether element ’s blocker is Nothing.
invalidReason
getter
steps
are:
permission
element
’s
activation
behavior
given
event
is:
-
If event .
isTrusted
is false, then return. -
If element .
isValid
is false, then return. -
Request permission to use the powerful features named in element ’s
[[Types]]
.
IntersectionObserverCallback
and
runs
the
following
steps:
-
Assert : The
IntersectionObserver
’sroot
is the document -
Let entries be the value of the first callback parameter, the list of
intersection observer entries
. -
Let entry be entries ’s last item .
-
If entry .
isVisible
, then: -
Otherwise:
-
If entry .
intersectionRatio
>= 1, then:-
Let reason be
intersection_occluded_or_distorted
.
-
-
Otherwise:
-
Let reason be
intersection_out_of_viewport_or_clipped
.
-
-
Add a temporary blocker with reason .
-
-
If
[[IntersectionRect]]
does not equal entry .intersectionRect
then add an expiring blocker withintersection_changed
. -
Set
[[IntersectionRect]]
to entry .intersectionRect
-
Assert : element ’s node navigable ’s
[[PermissionElements]]
contains element . -
Let count be 0.
-
For each current in element ’s node navigable ’s
[[PermissionElements]]
:-
If current is element , then break .
-
If the intersection of element .
[[Types]]
with current .[[Types]]
is not empty , then increment count by 1.
-
-
Return whether count is less than 2.
-
For each current in document ’s
[[PermissionElements]]
:-
If current is type permissible , then remove blockers with
unsuccesful_registration
from current .
-
3.
Rendering
the
permission
Element
The
permission
element
is
a
non-devolvable
widget
and
is
chiefly
rendered
like
a
button
.
The
button
label
is
largely
expected
to
be
determined
by
the
browser,
rather
than
the
page,
and
reflects
the
powerful
features
listed
in
[[Types]]
,
expressed
as
text
and
icons.
The
page
can
influence
the
permission
element’s
styling,
but
with
constraints
to
prevent
abuse
(e.g.
minimum
and
maximum
sizes
for
fonts
and
the
label
itself).
The
page
can
also
select
a
locale
for
the
text
via
the
lang
attribute.
3.1. Presentation
There isn’t much precedence for describing the user agent UI in detail. It may be better to leave more freedom to user agents.
The
permission
element
contains
browser-chosen
content,
text
and
maybe
an
icon.
Activating
it
will
prompt
the
user
to
choose
.
This
provides
two
bits
of
user
interface
that
a
user
can
interact
with.
The
user
agent
is
largely
free
to
determine
these
—
rendering
of
the
permission
element
and
the
subsequent
permission
prompt
—
in
whichever
way
it
thinks
best
convey’s
the
element’s
intent.
UI
options
for
the
permission
element’s
presentation
include:
-
Name the powerful features listed in
[[Types]]
, in the language indicated by the language of the element. Note that this would always be the language indicated by thelang
attribute, if present. -
An icon indicating the powerful feature type or types.
-
The current permission state of the powerful feature in questions. For example, if the permission is already
granted
, thepermission
element might be labeled as "geolocation already in use". -
A modal prompt with a "scrim". (I.e., darkening out the page behind the prompt.) This would normally quite disruptive. But here our goal is to ensure a user means to make this choice.
User agents are encouraged to name or describe the powerful features in a way that’s consistent with similar usage in program or the platform it is running on.
Very non-normative examples might be:
-
<permission lang="de" types="geolocation">
: "Standort verwenden". -
<permission types="microphone">
(in an English language page): "Use microphone. 🎤". -
Upon activiating
<permission types="microphone">
, when the corresponding permission state isdenied
, modify the text to "Continue blocking".
3.2. Styling
A
permission
element
constrains
the
styling
that
can
be
applied
to
it.
These
constraints
come
in
three
flavours:
-
If the condition isn’t met, the
permission
element in deactivated. -
A user-agent defined stylesheet enforces certain styling.
-
The user-agent enforces bounds on additional styles, where the bounds cannot be easily expressed in CSS. For example, if the style bounds are expressed relative to the computed style of the element.
3.2.1. Conditions that Deactivate the Element
If
one
of
these
conditions
is
not
met,
then
a
temporary
blocker
is
added
with
type
style_invalid
.
'color', 'background-color' |
Set
by
default
to
the
user
agent’s
default
button
colors.
The
contrast
ratio
between
the
2
colors
needs
to
be
at
least
3.
Alpha
has
to
be
1.
|
---|---|
'font-size' |
If
specified
value
is
expressed
as
<relative-size>
:
|
3.2.2. User-Agent Defined Stylesheet
A
permission
element
is
expected
to
render
with
the
following
styles:
@namespace "http://www.w3.org/1999/xhtml" ; permission{ opacity : 1.0 ; line-height : normal; whitespace : nowrap; user-select : none; appearance : auto; }
3.2.3. Additional User-Agent Defined Style Bounds
A
permission
element
defines
several
bounds
on
styles.
For
example,
we
want
the
font
size
are
constraints
on
the
The
style
bounds
are
explained
below.
For
notational
convenience,
we
imagine
that
the
computed
value
of
an
element
could
be
accessed
in
CSS
rules
with
computed
,
just
like
the
inherited
value
of
an
element
can
via
the
inherit
keyword.
Then
the
following
sheet
expresses
the
style
bounds:
@namespace "http://www.w3.org/1999/xhtml" ; permission{ outline-offset : clamp ( 0 , computed, none); /* No negative outline-offsets. */ font-weight:clamp ( 200 , computed, none); /* No font-weights below 200. */ word-spacing:clamp ( 0 , computed, 0.5 em ); /* Word-spacing between 0..0.5em */ letter-spacing:clamp ( -0.05 em , commputed, 0.2 em ); /* Letter spacing between -0.05..0.2em */ min-height:clamp ( 1 em , computed, none); max-height : clamp ( none, computed, 3 em ); min-width : clamp ( none, computed, calc ( fit-content)); margin : clamp ( 4 px , computed, none); font-style : if ( computed ="normal" or computed ="italic" , computed, "normal" ); display : if( computed ="inline-block" or computed ="none" , computed, "inline-block" ); }
Additionally, some rules apply based on conditions not easily expressible as CSS.
If
height
is
auto
,
then
apply:
@namespace "http://www.w3.org/1999/xhtml" ; permission{ padding-top : clamp ( 1 em , computed, none); padding-bottom : calc ( padding-top); }
If
width
is
auto
,
then
apply:
@namespace "http://www.w3.org/1999/xhtml" ; permission{ padding-left : clamp ( none, computed, 5 em ); padding-right : calc ( padding-left); }
Apply
the
followinf
following
sheet,
if
the
element
does
not
have
all
of
the
following:
-
A border width of at least
1px
, -
a color to background-color contrast ratio of at least 3,
-
and alpha of 1.
@namespace "http://www.w3.org/1999/xhtml" ; permission{ max-width : clamp ( none, computed, calc ( 3 * fit-content)); }