Close Watcher API

Draft Community Group Report ,

Editor:
Domenic Denicola ( Google )
Participate:
GitHub WICG/close-watcher ( new issue , open issues )
Commits:
GitHub spec.bs commits
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.


Abstract

The close watcher API provides a platform-agnostic way of handling close signals.

Status of this document

This specification was published by the Web Platform Incubator Community Group . It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups .

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.

Some example close signals are:

Whenever the user agent receives a potential close signal targeted at a Document document , it must perform the following close signal steps :

  1. 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 fires fullscreenchange .

  2. 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 keydown and keyup event sequence when the user sends a close signal by using a dimiss gesture.

    If multiple such events are fired, the user agent must pick one for the purposes of the following steps.

    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 keydown and keyup events, then it would likely pick the keydown event for the next steps, to better match behavior of desktop platforms without assistive technology in play.

  3. If such an event was fired, and its canceled flag is set, then return.

  4. 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.

  5. If document is not fully active , then return.

  6. Let closedSomething be the result of signaling close on document .

  7. If closedSomething was true, then return.

  8. 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 Document Window has a close watcher stack , a 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 :

To signal create a close watcher given a Document Window document : window , a list of steps cancelAction , and a list of steps closeAction :
  1. While Assert : document window ’s close watcher stack associated Document is not empty: fully active .

  2. Let closeWatcher stack be the result of popping from document window ’s close watcher stack .

  3. If Let closeWatcher needsUserActivation ’s be true if stack contains a close watcher whose is still valid active steps return true, is true; otherwise false.

  4. Let canCreate be false.

  5. If needsUserActivation is false, then set canCreate to true.

  6. Otherwise, if window has transient activation , then:

    1. Perform Consume user activation given closeWatcher window .

    2. Set canCreate to true.

  7. If canCreate is false, then return null.

  8. Set window ’s timestamp of last activation used for close action watchers to window ’s last activation timestamp .

  9. Return true. Let closeWatcher be a new close watcher whose window is window , cancel action is cancelAction , and close action is closeAction .

  10. Push closeWatcher onto stack .

  11. Return false. closeWatcher .

To check if we can create a developer-controlled close watcher for a Window close watcher window closeWatcher :
  1. Let document be If window closeWatcher ’s associated Document . is active is false, then return.

  2. If document closeWatcher ’s is not fully active , running cancel action is true, then return false. return.

  3. Let needsUserActivation window be false. For each closeWatcher in document ’s close watcher stack : window .

  4. If closeWatcher window ’s is still valid associated Document steps return true, is fully active , and closeWatcher window ’s blocks further developer-controlled timestamp of last activation used for close watchers is true, then set does not equal needsUserActivation window ’s last activation timestamp , then:

    1. Set window ’s timestamp of last activation used for close watchers to true and break window ’s last activation timestamp .

    2. Set closeWatcher ’s is running cancel action to true.

    3. Let canCreate shouldContinue be the result of running closeWatcher ’s cancel action .

    4. Set closeWatcher ’s is running cancel action to false.

    5. If needsUserActivation shouldContinue is false, then set return.

  5. If canCreate closeWatcher to true. ’s is active is false, then return.

  6. Otherwise, if If window has transient activation ’s associated Document is not fully active , then: then return.

  7. Consume user activation Remove given closeWatcher from window . ’s close watcher stack .

  8. Set canCreate closeWatcher ’s is active to true. false.

  9. Run closeWatcher ’s close action .

To destroy a close watcher closeWatcher :
  1. If canCreate closeWatcher ’s is true, active is false, then set return.

  2. Remove window closeWatcher from closeWatcher ’s timestamp of last activation used for window 's close watchers watcher stack .

  3. Set closeWatcher ’s is active to false.

To signal close given a Window window :
  1. If window ’s close watcher stack is empty, then return false.

  2. Let closeWatcher be the last activation timestamp item in window ’s close watcher stack .

  3. Assert: closeWatcher ’s is active is true.

  4. Return Close canCreate closeWatcher .

  5. 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 = new CloseWatcher ()
watcher = new CloseWatcher ({ signal })

Attempts to create a new CloseWatcher instance.

If the signal option is provided, watcher can be destroyed (as if by watcher . destroy() ) by aborting the given AbortSignal .

If a CloseWatcher is already active, and the Window does not have transient user activation , then this will instead throw a " NotAllowedError " DOMException .

watcher . destroy ()

Deactivates this CloseWatcher instance, so that it will no longer receive close events and so that new CloseWatcher instances 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 CloseWatcher instance, by firing a close event and deactivating the close watcher as if destroy() was called.

This is a helper utility that can be used to consolidate closing logic into the close event handler, by having all non- close signal closing affordances call close() .

Each CloseWatcher has an is active , which is a boolean, and an firing cancel event internal close watcher , which is a boolean. close watcher .

The new CloseWatcher( options ) constructor steps are:
  1. If this 's relevant global object 's associated Document is not fully active , then throw an " InvalidStateError " DOMException .

  2. If Let closeWatcher be the result of checking if we can create creating a developer-controlled close watcher for given this 's relevant global object , with:

  3. If closeWatcher is false, null, then throw a " NotAllowedError " DOMException .

  4. Set this 's is active to true. Set this 's firing cancel event to false. If options [" signal "] exists , then:

    1. If options [" signal "] is aborted , then set this 's is active destroy to false and return. closeWatcher .

    2. Add the following abort steps to options [" signal "]:

      1. Set this 's is active Destroy to false. closeWatcher .

  5. Push a new close watcher on Set this 's relevant global object 's associated document 's internal close watcher stack , with its items set as follows: close action being to signal close on this is still valid steps being to return this 's is active blocks further developer-controlled close watchers being true closeWatcher .

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 .

To signal close on a CloseWatcher closeWatcher : If closeWatcher ’s is active is false, then return. If closeWatcher ’s firing cancel event is true, then return. Let window be closeWatcher ’s relevant global object . If window ’s associated Document is fully active , and window ’s timestamp of last activation used for close watchers does not equal window ’s last activation timestamp , then: Set window ’s timestamp of last activation used for close watchers to window ’s last activation timestamp . Set closeWatcher ’s firing cancel event to true. Let shouldContinue be the result of firing an event named cancel at closeWatcher , with the cancelable attribute initialized to true. Set closeWatcher ’s firing cancel event to false. If shouldContinue is false, then return. If closeWatcher ’s is active is true, and window ’s associated Document is fully active , then fire an event named close at closeWatcher . Set closeWatcher ’s is active to false.

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.

In the showModal() steps, after adding subject to the top layer , append the following step:
  1. If Set subject ’s close watcher to the result of checking if we can create creating a developer-controlled close watcher given subject ’s relevant global object is true, then push a new close watcher on subject ’s node document 's close watcher stack , with its items set as follows: with:

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 dialog with no return value. element is removed from a Document" steps to do the same.

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.

Index

Terms defined by this specification

https://dom.spec.whatwg.org/#concept-node-document Referenced in: 2.2. The dialog element (2) https://infra.spec.whatwg.org/#iteration-break https://infra.spec.whatwg.org/#map-exists https://infra.spec.whatwg.org/#list-iterate https://infra.spec.whatwg.org/#stack-pop

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard . Living Standard. URL: https://dom.spec.whatwg.org/
[FULLSCREEN]
Philip Jägenstedt. Fullscreen API Standard . Living Standard. URL: https://fullscreen.spec.whatwg.org/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2 . URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard . Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard . Living Standard. URL: https://infra.spec.whatwg.org/
[UI-EVENTS]
Gary Kacmarcik; Travis Leithead. UI Events . URL: https://w3c.github.io/uievents/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard . Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[CONSOLE]
Dominic Farolino; Robert Kowalski; Terin Stock. Console Standard . Living Standard. URL: https://console.spec.whatwg.org/

IDL Index

[Exposed=Window]
interface CloseWatcher : EventTarget {
  constructor(optional CloseWatcherOptions options = {});
  undefined destroy();
  undefined close();
  attribute EventHandler oncancel;
  attribute EventHandler onclose;
};
dictionary CloseWatcherOptions {
  AbortSignal signal;
};
#closewatcher-is-active Referenced in: 1.2. Close watcher API (2) (3) (4) (5) (6) (7) (8) #closewatcher-signal-close Referenced in: 1.2. Close watcher API (2)