Close Watcher API

A Collection of Interesting Ideas,

Editor:
Domenic Denicola (Google)
Participate:
GitHub domenic/close-watcher (new issue, open issues)
Commits:
GitHub spec.bs commits

Abstract

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

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]

  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, 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 has a close watcher stack, a stack of close watchers, initially empty.

A close watcher is a struct with the following items:

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

To signal close given a Document document:
  1. While document’s close watcher stack is not empty:

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

    2. If closeWatcher’s is still valid steps return true, then:

      1. Perform closeWatcher’s close action.

      2. Return true.

  2. Return false.

We can create a developer-controlled close watcher for a Document document if the following steps return true:
  1. If document is not fully active, then return false.

  2. If document’s relevant global object has transient activation, then return true.

  3. For each closeWatcher in document’s close watcher stack:

    1. If closeWatcher’s is still valid steps return true, and closeWatcher’s blocks further developer-controlled close watchers is true, then return false.

  4. Return true.

1.2. Close watcher API

[Exposed=Window]
interface CloseWatcher : EventTarget {
  constructor();

  undefined destroy();
  undefined signalClose();

  attribute EventHandler onclose;
};
watcher = new CloseWatcher()

Attempts to create a new CloseWatcher instance.

If a CloseWatcher is already active, 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.signalClose()

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 signalClose().

Each CloseWatcher has an is active, which is a boolean.

The new CloseWatcher() constructor steps are:
  1. If we cannot create a developer-controlled close watcher for this's relevant global object's associated document, then throw a "NotAllowedError" DOMException.

  2. Set this's is active to true.

  3. Push a new close watcher on this's relevant global object's associated document's close watcher stack, with its items set as follows:

The destroy() method steps are to set this's is active to false.
The signalClose() method steps are to signal close on this.

Objects implementing the CloseWatcher interface must support the onclose event handler IDL attribute, whose event handler event type is close.

To signal close on a CloseWatcher closeWatcher:
  1. If closeWatcher’s is active is false, then return.

  2. Fire an event named close at closeWatcher.

  3. 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]

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

Replace 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, is now handled by the infrastructure in § 1 Close signals.)

To cancel the dialog dialog:
  1. Let close be the result of firing an event named cancel at dialog, with the cancelable attribute initialized to true.

  2. If close is true and dialog has an open content attribute, then close the dialog dialog with no return value.

Index

Terms defined by this specification

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/
[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; Doug Schepers. UI Events. URL: https://w3c.github.io/uievents/
[WebIDL]
Boris Zbarsky. Web IDL. URL: https://heycam.github.io/webidl/

IDL Index

[Exposed=Window]
interface CloseWatcher : EventTarget {
  constructor();

  undefined destroy();
  undefined signalClose();

  attribute EventHandler onclose;
};