-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add EventListenerOptions and passive event listener feature
This introduces an EventListenerOptions dictionary which can be used to explicitly specify options for addEventListener() and removeEventListener(). This also introduces a "passive" option, which disables the ability for a listener to cancel the event. See https://github.com/RByers/EventListenerOptions/blob/gh-pages/explainer.md for a high-level overview, and https://github.com/RByers/EventListenerOptions/issues?q=is%3Aissue for most of the debate that went into the design. PR: #82
- Loading branch information
Showing
2 changed files
with
219 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,8 @@ urlPrefix: https://html.spec.whatwg.org/multipage/ | |
text: effective script origin | ||
text: origin alias; url: #concept-origin-alias | ||
text: Unicode serialization of an origin; url: #unicode-serialisation-of-an-origin | ||
urlPrefix: infrastructure.html | ||
text: in parallel | ||
urlPrefix: https://w3c.github.io/webcomponents/spec/shadow/ | ||
type: dfn; urlPrefix: #dfn- | ||
text: shadow root | ||
|
@@ -613,7 +615,7 @@ Lets look at an example of how <a>events</a> work in a <a>tree</a>: | |
function test(e) { | ||
debug(e.target, e.currentTarget, e.eventPhase) | ||
} | ||
document.addEventListener("hey", test, true) | ||
document.addEventListener("hey", test, {capture: true}) | ||
document.body.addEventListener("hey", test) | ||
var ev = new Event("hey", {bubbles:true}) | ||
document.getElementById("x").dispatchEvent(ev) | ||
|
@@ -727,17 +729,13 @@ inherits from the {{Event}} interface. | |
{{Event/preventDefault()}} method. | ||
|
||
<dt><code><var>event</var> . <a method for=Event lt="preventDefault()">preventDefault</a>()</code> | ||
<dd>If invoked when the | ||
{{Event/cancelable}} attribute value is true, | ||
signals to the operation that caused <var>event</var> to be | ||
<a>dispatched</a> that it needs to be | ||
canceled. | ||
<dd>If invoked when the {{Event/cancelable}} attribute value is true, and while executing a | ||
listener for the <var>event</var> with {{EventListenerOptions/passive}} set to false, signals to | ||
the operation that caused <var>event</var> to be <a>dispatched</a> that it needs to be canceled. | ||
|
||
<dt><code><var>event</var> . {{Event/defaultPrevented}}</code> | ||
<dd>Returns true if | ||
{{Event/preventDefault()}} was invoked | ||
while the {{Event/cancelable}} attribute | ||
value is true, and false otherwise. | ||
<dd>Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancellation, | ||
and false otherwise. | ||
|
||
<dt><code><var>event</var> . {{Event/isTrusted}}</code> | ||
<dd>Returns true if <var>event</var> was | ||
|
@@ -799,6 +797,7 @@ flags that are all initially unset: | |
<li><dfn export for=Event>canceled flag</dfn> | ||
<li><dfn export for=Event>initialized flag</dfn> | ||
<li><dfn export for=Event>dispatch flag</dfn> | ||
<li><dfn export for=Event>in passive listener flag</dfn> | ||
</ul> | ||
|
||
The | ||
|
@@ -814,10 +813,13 @@ The <dfn attribute for=Event>bubbles</dfn> and | |
<dfn attribute for=Event>cancelable</dfn> attributes | ||
must return the values they were initialized to. | ||
|
||
The | ||
<dfn method for=Event>preventDefault()</dfn> | ||
method must set the <a>canceled flag</a> if the | ||
{{Event/cancelable}} attribute value is true. | ||
The <dfn method for=Event><code>preventDefault()</code></dfn> method, when invoked, must set the | ||
<a>canceled flag</a> if the {{Event/cancelable}} attribute value is true and the | ||
<a>in passive listener flag</a> is unset. | ||
|
||
<p class="note no-backref">This means there are scenarios where invoking {{preventDefault()}} has no | ||
effect. User agents are encouraged to log the precise cause in a developer console, to aid | ||
debugging. | ||
|
||
The | ||
<dfn attribute for=Event>defaultPrevented</dfn> | ||
|
@@ -972,14 +974,19 @@ for historical reasons. | |
<pre class=idl> | ||
[Exposed=(Window,Worker)] | ||
interface EventTarget { | ||
void addEventListener(DOMString type, EventListener? callback, optional boolean capture = false); | ||
void removeEventListener(DOMString type, EventListener? callback, optional boolean capture = false); | ||
void addEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options); | ||
void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options); | ||
boolean dispatchEvent(Event event); | ||
}; | ||
|
||
callback interface EventListener { | ||
void handleEvent(Event event); | ||
}; | ||
|
||
dictionary EventListenerOptions { | ||
boolean capture; | ||
boolean passive; | ||
}; | ||
</pre> | ||
|
||
{{EventTarget}} is an object to which an | ||
|
@@ -991,60 +998,98 @@ occurred. Each {{EventTarget}} has an associated list of | |
<p>An <dfn export id=concept-event-listener>event listener</dfn> can be used to observe a specific | ||
<a>event</a>. | ||
|
||
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, and <b>capture</b>. An | ||
<a>event listener</a> also has an associated <b>removed flag</b>, which is initially unset. | ||
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, <b>capture</b> and | ||
<b>passive</b>. An <a>event listener</a> also has an associated <b>removed flag</b>, which is | ||
initially unset. | ||
|
||
<p class="note no-backref">The callback is named {{EventListener}} for historical reasons. As can be | ||
seen from the definition above, an <a>event listener</a> is a more broad concept. | ||
|
||
<dl class=domintro> | ||
<dt><code><var>target</var> . <a method lt="addEventListener()">addEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code> | ||
<dt><code><var>target</var> . <a method lt="addEventListener()">addEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code> | ||
<dd> | ||
Appends an <a>event listener</a> for <a>events</a> whose {{Event/type}} attribute value | ||
is <var>type</var>. The <var>callback</var> argument sets the <b>callback</b> that will | ||
be invoked when the <a>event</a> is <a>dispatched</a>. When set to true, | ||
the <var>capture</var> argument prevents <b>callback</b> from being invoked when | ||
the <a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/BUBBLING_PHASE}}. | ||
When false, <b>callback</b> will not be invoked when <a>event</a>'s {{Event/eventPhase}} | ||
attribute value is {{Event/CAPTURING_PHASE}}. Either way, <b>callback</b> will be | ||
invoked if <a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/AT_TARGET}}. | ||
|
||
The <a>event listener</a> is appended to <var>target</var>'s list of | ||
<a>event listeners</a> and is not appended if it is a duplicate, i.e., having the same | ||
<b>type</b>, <b>callback</b>, and <b>capture</b> values. | ||
|
||
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code> | ||
be invoked when the <a>event</a> is <a>dispatched</a>. | ||
|
||
The <var>options</var> argument sets listener-specific options. For compatibility this can be just | ||
a boolean, in which case the method behaves exactly as if the value was specified as | ||
<var>options</var>' <code>capture</code> member. | ||
|
||
When set to true, <var>options</var>' <code>capture</code> member prevents <b>callback</b> from | ||
being invoked when the <a>event</a>'s {{Event/eventPhase}} attribute value is | ||
{{Event/BUBBLING_PHASE}}. When false (or not present), <b>callback</b> will not be invoked when | ||
<a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/CAPTURING_PHASE}}. Either way, | ||
<b>callback</b> will be invoked if <a>event</a>'s {{Event/eventPhase}} attribute value is | ||
{{Event/AT_TARGET}}. | ||
|
||
When set to true, <var>options</var>' <code>passive</code> member indicates that the | ||
<b>callback</b> will not cancel the event by invoking {{preventDefault()}}. This is used to enable | ||
performance optimizations described in [[#observing-event-listeners]]. | ||
|
||
The <a>event listener</a> is appended to <var>target</var>'s list of <a>event listeners</a> and is | ||
not appended if it is a duplicate, i.e., having the same <b>type</b>, <b>callback</b>, | ||
<b>capture</b> and <b>passive</b> values. | ||
|
||
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code> | ||
<dd>Remove the <a>event listener</a> | ||
in <var>target</var>'s list of | ||
<a>event listeners</a> with the same | ||
<var>type</var>, <var>callback</var>, and | ||
<var>capture</var>. | ||
<var>options</var>. | ||
|
||
<dt><code><var>target</var> . <a method lt="dispatchEvent()">dispatchEvent</a>(<var>event</var>)</code> | ||
<dd><a>Dispatches</a> a synthetic event <var>event</var> to <var>target</var> and returns | ||
true if either <var>event</var>'s {{Event/cancelable}} attribute value is false or its | ||
{{Event/preventDefault()}} method was not invoked, and false otherwise. | ||
</dl> | ||
|
||
<p>To <dfn export for=Event id=concept-flatten-options>flatten</dfn> <var>options</var> run these steps: | ||
|
||
<ol> | ||
<li><p>Let <var>capture</var> and <var>passive</var> be false. | ||
|
||
<li><p>If <var>options</var> is a boolean, set <var>capture</var> to <var>options</var>. | ||
|
||
<li><p>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/capture}}</code> is | ||
present in <var>options</var> with value true, then set <var>capture</var> to true. | ||
|
||
<li><p>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/passive}}</code> is | ||
present in <var>options</var> with value true, then set <var>passive</var> to true. | ||
|
||
<li><p>Return <var>capture</var> and <var>passive</var>. | ||
</ol> | ||
|
||
<p>The | ||
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn> | ||
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn> | ||
method, when invoked, must run these steps: | ||
|
||
<ol> | ||
<li><p>If <var>callback</var> is null, terminate these steps. | ||
|
||
<li><p>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> | ||
<var>options</var>. | ||
|
||
<li><p>Append an <a>event listener</a> to the associated list of <a>event listeners</a> with | ||
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, and <b>capture</b> | ||
set to <var>capture</var>, unless there already is an <a>event listener</a> in that list with the | ||
same <b>type</b>, <b>callback</b>, and <b>capture</b>. | ||
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, <b>capture</b> | ||
set to <var>capture</var>, and <b>passive</b> set to <var>passive</var> unless there already is an | ||
<a>event listener</a> in that list with the same <b>type</b>, <b>callback</b>, <b>capture</b>, and | ||
<b>passive</b>. | ||
</ol> | ||
|
||
<p>The | ||
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn> | ||
method, when invoked, must, if there is an <a>event listener</a> in the associated list of | ||
<a>event listeners</a> whose <b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>, | ||
and <b>capture</b> is <var>capture</var>, set that <a>event listener</a>'s <b>removed flag</b> and | ||
remove it from the associated list of <a>event listeners</a>. | ||
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn> | ||
method, when invoked, must, run these steps | ||
|
||
<ol> | ||
<li><p>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> | ||
<var>options</var>. | ||
|
||
<li><p>If there is an <a>event listener</a> in the associated list of <a>event listeners</a> whose | ||
<b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>, <b>capture</b> is | ||
<var>capture</var>, and <b>passive</b> is <var>passive</var> then set that <a>event listener</a>'s | ||
<b>removed flag</b> and remove it from the associated list of <a>event listeners</a>. | ||
</ol> | ||
|
||
<p>The <dfn method for=EventTarget><code>dispatchEvent(<var>event</var>)</code></dfn> method, when | ||
invoked, must run these steps: | ||
|
@@ -1059,6 +1104,30 @@ invoked, must run these steps: | |
</ol> | ||
|
||
|
||
<h3 id=observing-event-listeners>Observing event listeners</h3> | ||
|
||
<p>In general, developers do not expect the presence of an <a>event listener</a> to be observable. | ||
The impact of an <a>event listener</a> is determined by its <b>callback</b>. That is, a developer | ||
adding a no-op <a>event listener</a> would not expect it to have any side effects. | ||
|
||
<p>Unfortunately, some event APIs have been designed such that implementing them efficiently | ||
requires observing <a>event listeners</a>. This can make the presence of listeners observable in | ||
that even empty listeners can have a dramatic performance impact on the behavior of the application. | ||
For example, touch and wheel events which can be used to block asynchronous scrolling. In some cases | ||
this problem can be mitigated by specifying the event to be {{Event/cancelable}} only when there is | ||
at least one non-{{EventListenerOptions/passive}} listener. For example, | ||
non-{{EventListenerOptions/passive}} {{TouchEvent}} listeners must block scrolling, but if all | ||
listeners are {{EventListenerOptions/passive}} then scrolling can be allowed to start | ||
<a>in parallel</a> by making the {{TouchEvent}} uncancelable (so that calls to | ||
{{Event/preventDefault()}} are ignored). So code dispatching an event is able to observe the absence | ||
of non-{{EventListenerOptions/passive}} listeners, and use that to clear the {{Event/cancelable}} | ||
property of the event being dispatched. | ||
|
||
<p>Ideally, any new event APIs are defined such that they do not need this property (use | ||
<a href="https://lists.w3.org/Archives/Public/public-script-coord/">[email protected]</a> | ||
for discussion). | ||
|
||
|
||
<h3 id=dispatching-events>Dispatching events</h3> | ||
|
||
<p>To <dfn export for=Event id=concept-event-dispatch>dispatch</dfn> an <var>event</var> to a | ||
|
@@ -1139,9 +1208,14 @@ invoked, must run these steps: | |
<var>listener</var>'s <b>capture</b> is true, terminate these substeps (and run them for the next | ||
<a>event listener</a>). | ||
|
||
<li><p>If <var>listener</var>'s <b>passive</b> is true, set <var>event</var>'s | ||
<a>in passive listener flag</a>. | ||
|
||
<li><p>Call <var>listener</var>'s <b>callback</b>'s {{EventListener/handleEvent()}}, with | ||
<var>event</var> as argument and <var>event</var>'s {{Event/currentTarget}} attribute value as | ||
<a>callback this value</a>. If this throws any exception, <a>report the exception</a>. | ||
|
||
<li><p>Clear <var>event</var>'s <a>in passive listener flag</a>. | ||
</ol> | ||
</ol> | ||
|
||
|
@@ -9076,6 +9150,7 @@ Peter Sharpe, | |
Philip Jägenstedt, | ||
Philippe Le Hégaret, | ||
Rafael Weinstein, | ||
Rick Byers, | ||
Rick Waldron, | ||
Robbert Broersma, | ||
Robin Berjon, | ||
|
Oops, something went wrong.