Skip to content

Commit

Permalink
Align spec with implementation experience
Browse files Browse the repository at this point in the history
* The constructor needs to _consume_ transient user activation, not just check it.
* We need a reentrancy flag for the cancel event.
* We need to not fire close if destroy() was called during the cancel event handler.
  • Loading branch information
domenic authored Oct 27, 2021
1 parent e49767e commit 058c696
Showing 1 changed file with 21 additions and 11 deletions.
32 changes: 21 additions & 11 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,20 @@ A <dfn export>close watcher</dfn> is a [=struct=] with the following [=struct/it
</div>

<div algorithm>
We <dfn lt="can create a developer-controlled close watcher|cannot create a developer-controlled close watcher">can create a developer-controlled close watcher</dfn> for a {{Document}} |document| if the following steps return true:
To <dfn lt="check if we can create a developer-controlled close watcher">check if we can create a developer-controlled close watcher</dfn> for a {{Window}} |window|:

1. Let |document| be |window|'s [=associated Document=].
1. If |document| is not [=Document/fully active=], then return false.
1. If |document|'s [=relevant global object=] has [=transient activation=], then return true.
1. Let |needsUserActivation| be false.
1. [=list/For each=] |closeWatcher| in |document|'s [=close watcher stack=]:
1. If |closeWatcher|'s [=close watcher/is still valid=] steps return true, and |closeWatcher|'s [=close watcher/blocks further developer-controlled close watchers=] is true, then return false.
1. Return true.
1. If |closeWatcher|'s [=close watcher/is still valid=] steps return true, and |closeWatcher|'s [=close watcher/blocks further developer-controlled close watchers=] is true, then set |needsUserActivation| to true and [=iteration/break=].
1. Let |canCreate| be false.
1. If |needsUserActivation| is false, then set |canCreate| to true.
1. Otherwise, if |window| has [=transient activation=], then:
1. [=Consume user activation=] given |window|.
1. Set |canCreate| to true.
1. If |canCreate| is true, then set |window|'s [=timestamp of last activation used for close watchers=] to |window|'s <a spec="HTML">last activation timestamp</a>.
1. Return |canCreate|.
</div>

<h3 id="close-watcher-api">Close watcher API</h3>
Expand Down Expand Up @@ -195,19 +202,19 @@ interface CloseWatcher : EventTarget {
</dd>
</dl>

Each {{CloseWatcher}} has an <dfn for="CloseWatcher">is active</dfn>, which is a boolean.
Each {{CloseWatcher}} has an <dfn for="CloseWatcher">is active</dfn>, which is a boolean, and an <dfn for="CloseWatcher">firing cancel event</dfn>, which is a boolean.

<div algorithm>
The <dfn constructor for="CloseWatcher" lt="CloseWatcher()">new CloseWatcher()</dfn> constructor steps are:

1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not [=Document/fully active=], then throw an "{{InvalidStateError}}" {{DOMException}}.
1. If we [=cannot create a developer-controlled close watcher=] for [=this=]'s [=relevant global object=]'s [=associated document=], then throw a "{{NotAllowedError}}" {{DOMException}}.
1. If the result of [=checking if we can create a developer-controlled close watcher=] for [=this=]'s [=relevant global object=] is false, then throw a "{{NotAllowedError}}" {{DOMException}}.
1. Set [=this=]'s [=CloseWatcher/is active=] to true.
1. Set [=this=]'s [=CloseWatcher/firing cancel event=] to false.
1. [=stack/Push=] a new [=close watcher=] on [=this=]'s [=relevant global object=]'s [=associated document=]'s [=close watcher stack=], with its [=struct/items=] set as follows:
* [=close watcher/close action=] being to [=CloseWatcher/signal close=] on [=this=]
* [=close watcher/is still valid=] steps being to return [=this=]'s [=CloseWatcher/is active=]
* [=close watcher/blocks further developer-controlled close watchers=] being true
1. Set [=this=]'s [=relevant global object=]'s [=timestamp of last activation used for close watchers=] to [=this=]'s [=relevant global object=]'s <a spec="HTML">last activation timestamp</a>.
</div>

<p algorithm>
Expand All @@ -224,12 +231,15 @@ Objects implementing the {{CloseWatcher}} interface must support the <dfn attrib
To <dfn for="CloseWatcher">signal close</dfn> on a {{CloseWatcher}} |closeWatcher|:

1. If |closeWatcher|'s [=CloseWatcher/is active=] is false, then return.
1. If |closeWatcher|'s [=CloseWatcher/firing cancel event=] is true, then return.
1. Let |window| be |closeWatcher|'s [=relevant global object=].
1. If |window|'s [=associated Document=] is [=Document/fully active=], and |window|'s [=timestamp of last activation used for close watchers=] does not equal |window|'s <a spec="HTML">last activation timestamp</a>, then:
1. Let |shouldContinue| be the result of [=firing an event=] named {{CloseWatcher/cancel}} at |closeWatcher|, with the {{Event/cancelable}} attribute initialized to true.
1. Set |window|'s [=timestamp of last activation used for close watchers=] to |window|'s <a spec="HTML">last activation timestamp</a>.
1. Set |closeWatcher|'s [=CloseWatcher/firing cancel event=] to true.
1. Let |shouldContinue| be the result of [=firing an event=] named {{CloseWatcher/cancel}} at |closeWatcher|, with the {{Event/cancelable}} attribute initialized to true.
1. Set |closeWatcher|'s [=CloseWatcher/firing cancel event=] to false.
1. If |shouldContinue| is false, then return.
1. If |window|'s [=associated Document=] is [=Document/fully active=], then [=fire an event=] named {{CloseWatcher/close}} at |closeWatcher|.
1. If |closeWatcher|'s [=CloseWatcher/is active=] is true, and |window|'s [=associated Document=] is [=Document/fully active=], then [=fire an event=] named {{CloseWatcher/close}} at |closeWatcher|.
1. Set |closeWatcher|'s [=CloseWatcher/is active=] to false.
</div>

Expand All @@ -248,12 +258,12 @@ Update <cite>HTML</cite>'s <a href="https://html.spec.whatwg.org/multipage/inter
<div algorithm="showModal patch">
In the {{HTMLDialogElement/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/node document=], then [=stack/push=] a new [=close watcher=] on |subject|'s [=Node/node document=]'s [=close watcher stack=], with its [=struct/items=] set as follows:
1. If the result of [=checking if we can create a developer-controlled close watcher=] given |subject|'s [=relevant global object=] is true, then [=stack/push=] a new [=close watcher=] on |subject|'s [=Node/node document=]'s [=close watcher stack=], with its [=struct/items=] set as follows:
* [=close watcher/close action=] being to [=cancel the dialog=] |subject|
* [=close watcher/is still valid=] steps being to return true if |subject|'s [=Node/node document=] is <a spec="HTML" lt="blocked by a modal dialog">blocked by the modal dialog</a> |subject|, and return false otherwise
* [=close watcher/blocks further developer-controlled close watchers=] being true

<p class="note">If we [=cannot create a developer-controlled close watcher=], then this modal dialog will not respond to [=close signals=]. The {{HTMLDialogElement/showModal()}} method proceeds without any exception or other indication of this, although the browser could [=report a warning to the console=].
<p class="note">If we cannot create a developer-controlled close watcher, then this modal dialog will not respond to [=close signals=]. The {{HTMLDialogElement/showModal()}} method proceeds without any exception or other indication of this, although the browser could [=report a warning to the console=].
</div>

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 [[#close-signals]].)
Expand Down

0 comments on commit 058c696

Please sign in to comment.