1. Close signals
(This section could be introduced as a new subsection of [HTML] 's User interaction section.)
In an implementation-defined (and likely device-specific) manner, a user can send a close signal to the user agent. This indicates that the user wishes to close something which is currently being shown on the screen, such as a popup, menu, dialog, picker, or display mode.
-
The Esc key on desktop platforms
-
The back button on Android
-
The two-finger scrub "z" gesture on iOS when using VoiceOver (or, more generally, any assistive technology dismiss gesture)
-
The square button on a DualShock (PlayStation) controller
Whenever
the
user
agent
receives
a
potential
close
signal
targeted
at
a
Document
document
,
it
must
perform
the
following
close
signal
steps
:
-
If document ’s fullscreen element is non-null, then fully exit fullscreen and return.
This does not fire any relevant event, such as
keydown; it only firesfullscreenchange. -
Fire any relevant event, per UI Events or other relevant specifications. [UI-EVENTS]
As an example of a relevant event that is outside of the model given in UI Events , current thinking is that assistive technology would synthesize an Esc
If multiple such events are fired, the user agent must pick one for the purposes of the following steps.keydownandkeyupevent sequence when the user sends a close signal by using a dimiss gesture.For example, it is typical on desktop platforms for pressing down on the Esc key to be a close signal . So, if assistive technology is synthesizing both
keydownandkeyupevents, then it would likely pick thekeydownevent for the next steps, to better match behavior of desktop platforms without assistive technology in play. -
If such an event was fired, and its canceled flag is set, then return.
-
If such an event was fired, then perform the following steps within the same task as that event was fired in, immediately after firing the event. Otherwise, queue a global task on the user interaction task source given document ’s relevant global object to perform the following steps.
-
If document is not fully active , then return.
-
Let closedSomething be the result of signaling close on document .
-
If closedSomething was true, then return.
-
Otherwise, there was nothing watching for a close signal. The user agent may instead interpret this interaction as some other action, instead of as a close signal.
On
a
desktop
platform
where
Esc
is
the
close
signal,
the
user
agent
will
first
fire
an
appropriately-initialized
keydown
event.
If
the
web
developer
intercepts
this
event
and
calls
preventDefault()
,
then
nothing
further
happens.
But
if
the
event
is
fired
without
being
canceled,
then
the
user
agent
proceeds
to
signal
close
.
On
Android
where
the
back
button
is
a
potential
close
signal,
no
event
is
involved,
so
when
the
user
agent
determines
that
the
back
button
represents
a
close
signal,
it
queues
a
task
to
signal
close
.
If
there
is
a
still-valid
close
watcher
on
the
close
watcher
stack
,
then
that
will
get
triggered;
otherwise,
the
user
agent
will
interpret
the
back
button
press
as
a
request
to
traverse
the
history
by
a
delta
of
−1.
1.1. Close watcher infrastructure
Each
has
a
close
watcher
stack
,
a
Document
Window
stack
list
of
close
watchers
,
initially
empty.
It isn’t quite a proper stack , as items can get removed from the middle of it if a close watcher is destroyed or closed via web developer code. User-initiated close signals always act on the top of the close watcher stack , however.
Each
Window
has
a
timestamp
of
last
activation
used
for
close
watchers
.
This
is
either
a
DOMHighResTimeStamp
value,
positive
infinity,
or
negative
infinity
(i.e.
the
same
value
space
as
the
last
activation
timestamp
).
It
is
initially
positive
infinity.
This
value
is
used
to
ensure
that
a
given
user
activation
only
enables
a
single
CloseWatcher
cancel
or
dialog
cancel
event
to
be
fired,
per
user
activation.
This
is
different
than
requiring
transient
activation
to
fire
the
event,
because
we
want
to
allow
the
event
to
happen
arbitrarily
long
after
the
user
activation.
A close watcher is a struct with the following items :
-
A
closewindow , aWindow. A cancel action , a list of steps. These steps can never throw an
exception.exception, and return either true (to indicate the caller will proceed to the close action or false (to indicate the caller will bail out).-
An is still validA close action , a list of steps. These steps can never throw anexception, and return either true or false.exception. -
A blocks further developer-controlled close watchersAn is activeboolean.boolean, initially true.The -
A is
still valid steps are a spec convenience that allows us to push close watchers onto the stack without having to add hooks to appropriately clean them up every time they become invalidated. Doing so can be tricky as in addition to explicit teardown steps, there are often implicit ones, e.g. by removing a relevant element from the document.running cancel action boolean, initially false.
Document
Window
-
WhileAssert :documentwindow ’sclose watcher stackassociated Document isnot empty:fully active . -
Let
closeWatcherstack bethe result of popping fromdocumentwindow ’s close watcher stack . -
IfLetcloseWatcherneedsUserActivation’sbe true if stack contains a close watcher whose isstill validactivesteps return true,is true; otherwise false. Let canCreate be false.
If needsUserActivation is false, then set canCreate to true.
Otherwise, if window has transient activation , then:
-
PerformConsume user activation givencloseWatcherwindow . Set canCreate to true.
-
If canCreate is false, then return null.
Set window ’s timestamp of last activation used for close
actionwatchers to window ’s last activation timestamp .-
Return true.Let closeWatcher be a new close watcher whose window is window , cancel action is cancelAction , and close action is closeAction . Push closeWatcher onto stack .
-
Return
false.closeWatcher .
-
Let document beIfwindowcloseWatcher ’sassociated Document .is active is false, then return. -
If
documentcloseWatcher ’s isnot fully active ,running cancel action is true, thenreturn false.return. -
Let
needsUserActivationwindow befalse. For eachcloseWatcherin document’sclose watcher stack :window . -
If
closeWatcherwindow ’sis still validassociated Documentsteps return true,is fully active , andcloseWatcherwindow ’sblocks further developer-controlledtimestamp of last activation used for close watchersis true, then setdoes not equalneedsUserActivationwindow ’s last activation timestamp , then:Set window ’s timestamp of last activation used for close watchers to
true and breakwindow ’s last activation timestamp .-
Set closeWatcher ’s is running cancel action to true.
-
Let
canCreateshouldContinue be the result of running closeWatcher ’s cancel action . Set closeWatcher ’s is running cancel action to false.
-
If
needsUserActivationshouldContinue is false, thensetreturn.
If
canCreatecloseWatcherto true.’s is active is false, then return.-
Otherwise, ifIf windowhas transient activation’s associated Document is not fully active ,then:then return. -
Consume user activationRemovegivencloseWatcher from window.’s close watcher stack . -
Set
canCreatecloseWatcher ’s is active totrue.false. Run closeWatcher ’s close action .
-
If
canCreatecloseWatcher ’s istrue,active is false, thensetreturn. Remove
windowcloseWatcher from closeWatcher ’stimestamp of last activation used forwindow 's closewatcherswatcher stack .Set closeWatcher ’s is active to false.
Window
window
:If window ’s close watcher stack is empty, then return false.
Let closeWatcher be the last
activation timestampitem in window ’s close watcher stack .-
Assert: closeWatcher ’s is active is true.
-
ReturnClosecanCreatecloseWatcher . -
Return true.
1.2. Close watcher API
[Exposed =Window ]interface :CloseWatcher EventTarget {constructor (optional CloseWatcherOptions = {});options undefined destroy ();undefined close ();attribute EventHandler oncancel ;attribute EventHandler onclose ; };dictionary {CloseWatcherOptions AbortSignal ; };signal
-
watcher = newCloseWatcher()watcher = newCloseWatcher({signal}) -
Attempts to create a new
CloseWatcherinstance.If the
signaloption is provided, watcher can be destroyed (as if bywatcher .) by aborting the givendestroy()AbortSignal.If a
CloseWatcheris already active, and theWindowdoes not have transient user activation , then this will instead throw a "NotAllowedError"DOMException. -
watcher .destroy() -
Deactivates this
CloseWatcherinstance, so that it will no longer receivecloseevents and so that newCloseWatcherinstances can be constructed.This is intended to be called if the relevant UI element is closed in some other way than via a close signal , e.g. by pressing an explicit "Close" button.
-
watcher .close() -
Acts as if a close signal was sent targeting this
CloseWatcherinstance, by firing acloseevent and deactivating the close watcher as ifdestroy()was called.This is a helper utility that can be used to consolidate closing logic into the
closeevent handler, by having all non- close signal closing affordances callclose().
Each
CloseWatcher
has
an
is
active
,
which
is
a
boolean,
and
an
firing
cancel
event
internal
close
watcher
,
which
is
a
boolean.
close
watcher
.
new
CloseWatcher(
options
)
constructor
steps
are:
-
If this 's relevant global object 's associated Document is not fully active , then throw an "
InvalidStateError"DOMException. -
IfLet closeWatcher be the result ofchecking if we can createcreating adeveloper-controlledclose watcherforgiven this 's relevant global object , with:cancelAction being to return the result of firing an event named
cancelat this , with thecancelableattribute initialized to true.closeAction being to fire an event named
closeat this .
If closeWatcher is
false,null, then throw a "NotAllowedError"DOMException.-
Set this 's is active to true. Set this 's firing cancel event to false.If options ["signal"] exists , then: -
Push a new close watcher onSet this 'srelevant global object 's associated document 'sinternal close watcherstack , with its items set as follows: close action being to signal close on this is still validsteps beingtoreturn this 's is active blocks further developer-controlled close watchers being truecloseWatcher .
The
destroy()
method
steps
are
to
set
destroy
this
's
is
active
to
false.
internal
close
watcher
.
The
close()
method
steps
are
to
signal
close
on
this
's
internal
close
watcher
.
Objects
implementing
the
CloseWatcher
interface
must
support
the
oncancel
and
onclose
event
handler
IDL
attribute
,
whose
event
handler
event
types
are
respectively
cancel
and
close
.
2. Updates to other specifications
2.1. Fullscreen
Replace the sentence about "If the end user instructs..." in Fullscreen API § 4 UI with the following:
If the user initiates a close signal , this will trigger the fully exit fullscreen algorithm as part of the close signal steps . This takes precedence over any close watchers .
2.2.
The
dialog
element
Update
HTML
’s
The
dialog
element
section
as
follows:
[HTML]
Each
dialog
element
has
a
close
watcher
,
which
is
a
close
watcher
or
null,
initially
null.
showModal()
steps,
after
adding
subject
to
the
top
layer
,
append
the
following
step:
-
IfSet subject ’s close watcher to the result ofchecking if we can createcreating adeveloper-controlledclose watcher given subject ’s relevant global objectis true, then push a new close watcher on subject ’s node document 's close watcher stack,with its items set as follows:with:-
close actioncancelAction being tocancelreturn thedialogresult of firing an event namedcancelat subject , with thecancelableattribute initialized to true. -
is still validcloseActionstepsbeing toreturn true if subject ’s node document is blocked byClose themodaldialog subject, andwith no returnfalse otherwise blocks further developer-controlled close watchers being truevalue.
-
If
we
cannot
This
step
can
fail
to
create
a
developer-controlled
close
watcher,
then
i.e.
create
a
close
watcher
might
return
null
and
not
put
anything
on
the
close
watcher
stack
.
In
this
case
the
modal
dialog
will
not
respond
to
close
signals
.
The
showModal()
method
proceeds
without
any
exception
or
other
indication
of
this,
although
the
browser
could
report
a
warning
to
the
console
.
Replace
Remove
the
"Canceling
dialogs"
section
entirely
with
the
following
definition.
(The
previous
prose
about
providing
a
user
interface
to
cancel
such
dialogs,
and
the
task-queuing,
entirely.
It
is
now
handled
entirely
by
the
infrastructure
in
§ 1
Close
signals
.)
To
cancel
and
the
dialog
dialog
:
Let
window
be
dialog
’s
relevant
global
object
.
If
window
’s
timestamp
creation
of
last
activation
used
for
a
close
watchers
does
not
equal
window
’s
last
activation
timestamp
,
then:
watcher
.
Let
shouldContinue
to
Modify
the
result
of
firing
an
event
close
the
dialog
named
cancel
steps
to
destroy
at
dialog
,
with
the
cancelable
dialog
attribute
initialized
to
true.
Set
window
’s
timestamp
of
last
activation
used
for
's
close
watchers
watcher
and
set
it
to
window
’s
last
activation
timestamp
.
If
shouldContinue
null,
if
it
is
false,
then
return.
not
already
null.
Close
Similarly,
modify
the
"If
at
any
time
a
dialog
element
is
removed
from
a
Document"
steps
to
do
the
same.
dialog
with
no
return
value.
3. Security and privacy considerations
3.1. Security considerations
The
main
security
consideration
with
this
API
is
preventing
abusive
pages
from
hijacking
the
fallback
behavior
in
the
last
part
of
the
close
signal
steps
.
A
concrete
example
is
on
Android,
where
the
close
signal
is
the
software
back
button,
and
this
fallback
behavior
is
to
traverse
the
history
by
a
delta
of
−1.
If
developers
could
always
intercept
Android
back
button
presses
via
CloseWatcher
instances
and
dialog
elements,
then
they
could
effectively
break
the
back
button
by
never
letting
it
pass
through
to
the
fallback
behavior.
Much
of
the
complexity
of
this
specification
is
designed
around
preventing
such
abuse.
Without
it,
the
API
could
consist
of
a
single
event.
But
with
this
constraint,
we
need
an
API
surface
such
as
the
CloseWatcher()
constructor
which
can
be
gated
by
additional
checks,
as
well
as
the
close
watcher
stack
to
ensure
that
each
close
watcher
can
only
consume
a
single
close
signal
.
Concretely,
the
mechanism
of
checking
if
we
can
create
creating
a
developer-controlled
close
watcher
ensures
that
web
developers
can
only
create
CloseWatcher
instances,
or
call
preventDefault()
on
cancel
events,
by
consuming
user
activation
.
This
gives
similar
protections
to
what
browsers
have
in
place
today,
where
back
button
UI
skips
entries
that
were
added
without
user
activation.
We
do
allow
one
"free"
CloseWatcher
to
be
created,
without
consuming
user
activation,
to
handle
cases
like
session
inactivity
timeout
dialogs,
or
urgent
notifications
of
server-triggered
events.
The
end
result
is
that
this
specification
expands
the
number
of
Android
back
button
presses
that
a
maximally-abusive
page
could
require
to
escape
from
number
of
user
activations
+
1
to
number
of
user
activations
+
2.
(See
the
explainer
for
a
full
analysis.)
We
believe
this
tradeoff
is
worthwhile.
3.2. Privacy considerations
We believe the privacy impact of this API is minimal. The only information it gives about the user to the web developer is that a close signal has occurred, which is a very infrequent and coarse piece of user input.
In
all
cases
we’re
aware
of
today,
such
close
signals
are
already
detectable
by
web
developers
(e.g.,
by
using
keydown
listeners
on
desktop
or
popstate
listeners
on
Android).
In
theory,
by
correlating
these
existing
events
with
the
CloseWatcher
's
close
event,
a
web
developer
could
determine
some
information
about
the
platform.
(I.e.,
if
they
correlate
with
keydown
events,
the
user
is
likely
on
desktop,
or
at
least
on
a
keyboard-attached
mobile
device.)
This
is
similar
to
existing
techniques
which
detect
whether
touch
events
or
mouse
events
are
fired,
and
user
agents
which
want
to
emulate
a
different
platform
in
order
to
mask
the
user’s
choice
might
want
to
apply
similar
mitigation
techniques
for
close
watchers
as
they
do
for
other
platform-revealing
events.