Skip to content

Commit d17e7f8

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 14e7c6e commit d17e7f8

File tree

2 files changed

+207
-64
lines changed

2 files changed

+207
-64
lines changed

dom.bs

+108-25
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ urlPrefix: https://html.spec.whatwg.org/multipage/
104104
text: effective script origin
105105
text: origin alias; url: #concept-origin-alias
106106
text: Unicode serialization of an origin; url: #unicode-serialisation-of-an-origin
107+
urlPrefix: infrastructure.html
108+
text: in parallel
107109
urlPrefix: https://w3c.github.io/webcomponents/spec/shadow/
108110
type: dfn; urlPrefix: #dfn-
109111
text: shadow root
@@ -613,7 +615,7 @@ Lets look at an example of how <a>events</a> work in a <a>tree</a>:
613615
function test(e) {
614616
debug(e.target, e.currentTarget, e.eventPhase)
615617
}
616-
document.addEventListener("hey", test, true)
618+
document.addEventListener("hey", test, {capture: true})
617619
document.body.addEventListener("hey", test)
618620
var ev = new Event("hey", {bubbles:true})
619621
document.getElementById("x").dispatchEvent(ev)
@@ -728,16 +730,15 @@ inherits from the {{Event}} interface.
728730

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

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

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

804806
The
@@ -817,7 +819,14 @@ must return the values they were initialized to.
817819
The
818820
<dfn method for=Event>preventDefault()</dfn>
819821
method must set the <a>canceled flag</a> if the
820-
{{Event/cancelable}} attribute value is true.
822+
{{Event/cancelable}} attribute value is true and
823+
the <a>in passive listener flag</a> is unset.
824+
825+
<p class="note no-backref">
826+
This means there are scenarios where invoking {{preventDefault()}} has no effect.
827+
User agents are encouraged to log the precise cause in a developer console,
828+
to aid debugging
829+
</p>
821830

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

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

985999
{{EventTarget}} is an object to which an
@@ -991,60 +1005,97 @@ occurred. Each {{EventTarget}} has an associated list of
9911005
<p>An <dfn export id=concept-event-listener>event listener</dfn> can be used to observe a specific
9921006
<a>event</a>.
9931007

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

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

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

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

1016-
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code>
1040+
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code>
10171041
<dd>Remove the <a>event listener</a>
10181042
in <var>target</var>'s list of
10191043
<a>event listeners</a> with the same
10201044
<var>type</var>, <var>callback</var>, and
1021-
<var>capture</var>.
1045+
<var>options</var>.
10221046

10231047
<dt><code><var>target</var> . <a method lt="dispatchEvent()">dispatchEvent</a>(<var>event</var>)</code>
10241048
<dd><a>Dispatches</a> a synthetic event <var>event</var> to <var>target</var> and returns
10251049
true if either <var>event</var>'s {{Event/cancelable}} attribute value is false or its
10261050
{{Event/preventDefault()}} method was not invoked, and false otherwise.
10271051
</dl>
10281052

1053+
<p>To <dfn export for=Event id=concept-flatten-options>flatten</dfn> <var>options</var> run these steps:
1054+
1055+
<ol>
1056+
<li>Let <var>capture</var> and <var>passive</var> be false.
1057+
1058+
<li>If <var>options</var> is a boolean, set <var>capture</var> to
1059+
<var>options</var>.
1060+
1061+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/capture}}</code> is
1062+
present in <var>options</var> with value true, then set <var>capture</var> to true.
1063+
1064+
<li>If <var>options</var> is a dictionary and <code>{{EventListenerOptions/passive}}</code> is
1065+
present in <var>options</var> with value true, then set <var>passive</var> to true.
1066+
1067+
<li>Return <var>capture</var> and <var>passive</var>.
1068+
</ol>
1069+
10291070
<p>The
1030-
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn>
1071+
<dfn method for=EventTarget><code>addEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
10311072
method, when invoked, must run these steps:
10321073

10331074
<ol>
10341075
<li><p>If <var>callback</var> is null, terminate these steps.
10351076

1077+
<li>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> <var>options</var>.
1078+
10361079
<li><p>Append an <a>event listener</a> to the associated list of <a>event listeners</a> with
1037-
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, and <b>capture</b>
1038-
set to <var>capture</var>, unless there already is an <a>event listener</a> in that list with the
1039-
same <b>type</b>, <b>callback</b>, and <b>capture</b>.
1080+
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, <b>capture</b>
1081+
set to <var>capture</var>, and <b>passive</b> set to <var>passive</var> unless there
1082+
already is an <a>event listener</a> in that list with the same <b>type</b>,
1083+
<b>callback</b>, <b>capture</b>, and <b>passive</b>.
10401084
</ol>
10411085

10421086
<p>The
1043-
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>capture</var>)</code></dfn>
1044-
method, when invoked, must, if there is an <a>event listener</a> in the associated list of
1045-
<a>event listeners</a> whose <b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>,
1046-
and <b>capture</b> is <var>capture</var>, set that <a>event listener</a>'s <b>removed flag</b> and
1047-
remove it from the associated list of <a>event listeners</a>.
1087+
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
1088+
method, when invoked, must, run these steps
1089+
1090+
<ol>
1091+
<li>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> <var>options</var>.
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>
10481099

10491100
<p>The <dfn method for=EventTarget><code>dispatchEvent(<var>event</var>)</code></dfn> method, when
10501101
invoked, must run these steps:
@@ -1059,6 +1110,32 @@ invoked, must run these steps:
10591110
</ol>
10601111

10611112

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 APIs are defined such that they do not need this
1135+
property (use <a href="https://lists.w3.org/Archives/Public/public-script-coord/">[email protected]</a>
1136+
for discussion).
1137+
1138+
10621139
<h3 id=dispatching-events>Dispatching events</h3>
10631140

10641141
<p>To <dfn export for=Event id=concept-event-dispatch>dispatch</dfn> an <var>event</var> to a
@@ -1139,9 +1216,14 @@ invoked, must run these steps:
11391216
<var>listener</var>'s <b>capture</b> is true, terminate these substeps (and run them for the next
11401217
<a>event listener</a>).
11411218

1219+
<li>If <var>listener</var>'s <b>passive</b> is true, set <var>event</var>'s <a>in passive listener flag</a>.
1220+
11421221
<li><p>Call <var>listener</var>'s <b>callback</b>'s {{EventListener/handleEvent()}}, with
11431222
<var>event</var> as argument and <var>event</var>'s {{Event/currentTarget}} attribute value as
11441223
<a>callback this value</a>. If this throws any exception, <a>report the exception</a>.
1224+
1225+
<li>Clear <var>event</var>'s <a>in passive listener flag</a>.
1226+
11451227
</ol>
11461228
</ol>
11471229

@@ -9073,6 +9155,7 @@ Peter Sharpe,
90739155
Philip Jägenstedt,
90749156
Philippe Le Hégaret,
90759157
Rafael Weinstein,
9158+
Rick Byers,
90769159
Rick Waldron,
90779160
Robbert Broersma,
90789161
Robin Berjon,

0 commit comments

Comments
 (0)