Skip to content

Commit c01304d

Browse files
committed
Add EventListenerOptions and passive event listener feature
This introduces an EventListenerOptions dictionary which can be used to explicitly specify options to 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.
1 parent cfaf3fa commit c01304d

File tree

2 files changed

+230
-310
lines changed

2 files changed

+230
-310
lines changed

dom.bs

+111-26
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ urlPrefix: https://html.spec.whatwg.org/multipage/
103103
text: effective script origin
104104
text: origin alias; url: #concept-origin-alias
105105
text: Unicode serialization of an origin
106+
urlPrefix: infrastructure.html
107+
text: in parallel
106108
urlPrefix: https://w3c.github.io/webcomponents/spec/shadow/
107109
type: dfn; urlPrefix: #dfn-
108110
text: shadow root
@@ -612,7 +614,7 @@ Lets look at an example of how <a>events</a> work in a <a>tree</a>:
612614
function test(e) {
613615
debug(e.target, e.currentTarget, e.eventPhase)
614616
}
615-
document.addEventListener("hey", test, true)
617+
document.addEventListener("hey", test, {capture: true})
616618
document.body.addEventListener("hey", test)
617619
var ev = new Event("hey", {bubbles:true})
618620
document.getElementById("x").dispatchEvent(ev)
@@ -727,16 +729,15 @@ inherits from the {{Event}} interface.
727729

728730
<dt><code><var>event</var> . <a method for=Event lt="preventDefault()">preventDefault</a>()</code>
729731
<dd>If invoked when the
730-
{{Event/cancelable}} attribute value is true,
732+
{{Event/cancelable}} attribute value is true, and while executing a listener
733+
for the <var>event</var> with {{EventListenerOptions/passive}} set to false,
731734
signals to the operation that caused <var>event</var> to be
732735
<a>dispatched</a> that it needs to be
733736
canceled.
734737

735738
<dt><code><var>event</var> . {{Event/defaultPrevented}}</code>
736739
<dd>Returns true if
737-
{{Event/preventDefault()}} was invoked
738-
while the {{Event/cancelable}} attribute
739-
value is true, and false otherwise.
740+
{{Event/preventDefault()}} was used successfully to indicate cancellation.
740741

741742
<dt><code><var>event</var> . {{Event/isTrusted}}</code>
742743
<dd>Returns true if <var>event</var> was
@@ -798,6 +799,7 @@ flags that are all initially unset:
798799
<li><dfn export for=Event>canceled flag</dfn>
799800
<li><dfn export for=Event>initialized flag</dfn>
800801
<li><dfn export for=Event>dispatch flag</dfn>
802+
<li><dfn export for=Event>in passive listener flag</dfn>
801803
</ul>
802804

803805
The
@@ -816,7 +818,14 @@ must return the values they were initialized to.
816818
The
817819
<dfn method for=Event>preventDefault()</dfn>
818820
method must set the <a>canceled flag</a> if the
819-
{{Event/cancelable}} attribute value is true.
821+
{{Event/cancelable}} attribute value is true and
822+
the <a>in passive listener flag</a> is unset.
823+
824+
<p class="note no-backref">
825+
User agents are encouraged to generate a console warning or other debugging
826+
aid to help authors identify places where calls to {{preventDefault()}}
827+
have no effect.
828+
</p>
820829

821830
The
822831
<dfn attribute for=Event>defaultPrevented</dfn>
@@ -971,14 +980,19 @@ for historical reasons.
971980
<pre class=idl>
972981
[Exposed=(Window,Worker)]
973982
interface EventTarget {
974-
void addEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
975-
void removeEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
983+
void addEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
984+
void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
976985
boolean dispatchEvent(Event event);
977986
};
978987

979988
callback interface EventListener {
980989
void handleEvent(Event event);
981990
};
991+
992+
dictionary EventListenerOptions {
993+
boolean capture;
994+
boolean passive;
995+
};
982996
</pre>
983997

984998
{{EventTarget}} is an object to which an
@@ -990,34 +1004,44 @@ occurred. Each {{EventTarget}} has an associated list of
9901004
<p>An <dfn export id=concept-event-listener>event listener</dfn> can be used to observe a specific
9911005
<a>event</a>.
9921006

993-
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, and <b>capture</b>. An
1007+
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, <b>capture</b> and <b>passive</b>. An
9941008
<a>event listener</a> also has an associated <b>removed flag</b>, which is initially unset.
9951009

9961010
<p class="note no-backref">The callback is named {{EventListener}} for historical reasons. As can be
9971011
seen from the definition above, an <a>event listener</a> is a more broad concept.
9981012

9991013
<dl class=domintro>
1000-
<dt><code><var>target</var> . <a method lt="addEventListener()">addEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code>
1014+
<dt><code><var>target</var> . <a method lt="addEventListener()">addEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code>
10011015
<dd>
10021016
Appends an <a>event listener</a> for <a>events</a> whose {{Event/type}} attribute value
10031017
is <var>type</var>. The <var>callback</var> argument sets the <b>callback</b> that will
1004-
be invoked when the <a>event</a> is <a>dispatched</a>. When set to true,
1005-
the <var>capture</var> argument prevents <b>callback</b> from being invoked when
1018+
be invoked when the <a>event</a> is <a>dispatched</a>.
1019+
1020+
The <var>options</var> argument sets listener-specific options. For compatibility this can be
1021+
just a boolean, in which case the method behaves exactly as if the value was
1022+
specified as <var>options</var>' <code>capture</code> member.
1023+
1024+
When set to true, <var>options</var>' <code>capture</code> member prevents <b>callback</b>
1025+
from being invoked when
10061026
the <a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/BUBBLING_PHASE}}.
1007-
When false, <b>callback</b> will not be invoked when <a>event</a>'s {{Event/eventPhase}}
1027+
When false (or not present), <b>callback</b> will not be invoked when <a>event</a>'s {{Event/eventPhase}}
10081028
attribute value is {{Event/CAPTURING_PHASE}}. Either way, <b>callback</b> will be
10091029
invoked if <a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/AT_TARGET}}.
10101030

1031+
When set to true, <var>options</var>' <code>passive</code> member indicates that the <b>callback</b>
1032+
will not cancel the event by invoking {{preventDefault()}}.
1033+
This is used to enable performance optimizations described in [[#observing-event-listeners]].
1034+
10111035
The <a>event listener</a> is appended to <var>target</var>'s list of
10121036
<a>event listeners</a> and is not appended if it is a duplicate, i.e., having the same
1013-
<b>type</b>, <b>callback</b>, and <b>capture</b> values.
1037+
<b>type</b>, <b>callback</b>, <b>capture</b> and <b>passive</b> values.
10141038

1015-
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code>
1039+
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code>
10161040
<dd>Remove the <a>event listener</a>
10171041
in <var>target</var>'s list of
10181042
<a>event listeners</a> with the same
10191043
<var>type</var>, <var>callback</var>, and
1020-
<var>capture</var>.
1044+
<var>options</var>.
10211045

10221046
<dt><code><var>target</var> . <a method lt="dispatchEvent()">dispatchEvent</a>(<var>event</var>)</code>
10231047
<dd><a>Dispatches</a> a synthetic event <var>event</var> to <var>target</var> and returns
@@ -1026,24 +1050,52 @@ seen from the definition above, an <a>event listener</a> is a more broad concept
10261050
</dl>
10271051

10281052
<p>The
1029-
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn>
1053+
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
10301054
method, when invoked, must run these steps:
10311055

10321056
<ol>
10331057
<li><p>If <var>callback</var> is null, terminate these steps.
10341058

1059+
<li>Let <var>capture</var> and <var>passive</var> be false.
1060+
1061+
<li>If <var>options</var> is a boolean, set <var>capture</var> to
1062+
<var>options</var>.
1063+
1064+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/capture}}</code> is
1065+
present in <var>options</var> with value true, then set <var>capture</var> to true.
1066+
1067+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/passive}}</code> is
1068+
present in <var>options</var> with value true, then set <var>passive</var> to true.
1069+
10351070
<li><p>Append an <a>event listener</a> to the associated list of <a>event listeners</a> with
1036-
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, and <b>capture</b>
1037-
set to <var>capture</var>, unless there already is an <a>event listener</a> in that list with the
1038-
same <b>type</b>, <b>callback</b>, and <b>capture</b>.
1071+
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, <b>capture</b>
1072+
set to <var>capture</var>, and <b>passive</b> set to <var>passive</var> unless there
1073+
already is an <a>event listener</a> in that list with the same <b>type</b>,
1074+
<b>callback</b>, <b>capture</b>, and <b>passive</b>.
10391075
</ol>
10401076

10411077
<p>The
1042-
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn>
1043-
method, when invoked, must, if there is an <a>event listener</a> in the associated list of
1044-
<a>event listeners</a> whose <b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>,
1045-
and <b>capture</b> is <var>capture</var>, set that <a>event listener</a>'s <b>removed flag</b> and
1046-
remove it from the associated list of <a>event listeners</a>.
1078+
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
1079+
method, when invoked, must, run these steps
1080+
1081+
<ol>
1082+
<li>Let <var>capture</var> and <var>passive</var> be false.
1083+
1084+
<li>If <var>options</var> is a boolean, set <var>capture</var> to
1085+
<var>options</var>.
1086+
1087+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/capture}}</code> is
1088+
present in <var>options</var> with value true, then set <var>capture</var> to true.
1089+
1090+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/passive}}</code> is
1091+
present in <var>options</var> with value true, then set <var>passive</var> to true.
1092+
1093+
<li>If there is an <a>event listener</a> in the associated list of
1094+
<a>event listeners</a> whose <b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>,
1095+
<b>capture</b> is <var>capture</var>, and <b>passive</b> is <var>passive</var> then
1096+
set that <a>event listener</a>'s <b>removed flag</b> and remove it from the
1097+
associated list of <a>event listeners</a>.
1098+
</ol>
10471099

10481100
<p>The <dfn method for=EventTarget><code>dispatchEvent(<var>event</var>)</code></dfn> method, when
10491101
invoked, must run these steps:
@@ -1058,6 +1110,32 @@ invoked, must run these steps:
10581110
</ol>
10591111

10601112

1113+
<h3 id=observing-event-listeners>Observing event listeners</h3>
1114+
1115+
<p>In general, developers do not expect the presence of an <a>event listener</a> to be
1116+
observable. The impact of an <a>event listener</a> is determined by its <b>callback</b>.
1117+
That is, a developer adding a no-op <a>event listener</a> would not expect it to have
1118+
any side effects.
1119+
1120+
<p>Unfortunately, some event APIs have been designed such that implementing them
1121+
efficiently requires observing <a>event listeners</a>. This can make the presence
1122+
of listeners observable in that even empty listeners can have a dramatic performance impact
1123+
on the behavior of the application. For example, touch and wheel events which can be used to block
1124+
asynchronous scrolling. In some cases this problem can be mitigated by specifying
1125+
the event to be {{Event/cancelable}} only when there is at least one
1126+
non-{{EventListenerOptions/passive}} listener. For example, non-{{EventListenerOptions/passive}}
1127+
{{TouchEvent}} listeners must block scrolling, but if all listeners are {{EventListenerOptions/passive}} then
1128+
scrolling can be allowed to start <a>in parallel</a> by making the {{TouchEvent}}
1129+
uncancelable (so that calls to {{Event/preventDefault()}} are ignored). So code
1130+
dispatching an event is able to observe the absence of non-{{EventListenerOptions/passive}}
1131+
listeners, and use that to clear the {{Event/cancelable}} property of the event
1132+
being dispatched.
1133+
1134+
<p>Ideally, any new event types are defined such that they don't need this
1135+
property (use <a href="https://lists.w3.org/Archives/Public/public-script-coord/">[email protected]</a>
1136+
for discussion).
1137+
1138+
10611139
<h3 id=dispatching-events>Dispatching events</h3>
10621140

10631141
<p>To <dfn export for=Event id=concept-event-dispatch>dispatch</dfn> an <var>event</var> to a
@@ -1138,9 +1216,15 @@ invoked, must run these steps:
11381216
<var>listener</var>'s <b>capture</b> is true, terminate these substeps (and run them for the next
11391217
<a>event listener</a>).
11401218

1219+
<li>If <var>listener</var>'s <b>passive</b> is true, set <var>event</var>'s <a>in passive listener flag</a>.
1220+
11411221
<li><p>Call <var>listener</var>'s <b>callback</b>'s {{EventListener/handleEvent()}}, with
11421222
<var>event</var> as argument and <var>event</var>'s {{Event/currentTarget}} attribute value as
1143-
<a>callback this value</a>. If this throws any exception, <a>report the exception</a>.
1223+
<a>callback this value</a>.
1224+
1225+
<li>Clear <var>event</var>'s <a>in passive listener flag</a>.
1226+
1227+
<li>If the call to {{EventListener/handleEvent()}} threw any exception, <a>report the exception</a>.
11441228
</ol>
11451229
</ol>
11461230

@@ -9093,6 +9177,7 @@ Peter Sharpe,
90939177
Philip Jägenstedt,
90949178
Philippe Le Hégaret,
90959179
Rafael Weinstein,
9180+
Rick Byers,
90969181
Rick Waldron,
90979182
Robbert Broersma,
90989183
Robin Berjon,

0 commit comments

Comments
 (0)