Skip to content

Commit 0ad4e56

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 0ad4e56

File tree

2 files changed

+216
-78
lines changed

2 files changed

+216
-78
lines changed

dom.bs

+117-39
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)
@@ -727,17 +729,13 @@ inherits from the {{Event}} interface.
727729
{{Event/preventDefault()}} method.
728730

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

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

742740
<dt><code><var>event</var> . {{Event/isTrusted}}</code>
743741
<dd>Returns true if <var>event</var> was
@@ -799,6 +797,7 @@ flags that are all initially unset:
799797
<li><dfn export for=Event>canceled flag</dfn>
800798
<li><dfn export for=Event>initialized flag</dfn>
801799
<li><dfn export for=Event>dispatch flag</dfn>
800+
<li><dfn export for=Event>in passive listener flag</dfn>
802801
</ul>
803802

804803
The
@@ -816,8 +815,13 @@ must return the values they were initialized to.
816815

817816
The
818817
<dfn method for=Event>preventDefault()</dfn>
819-
method must set the <a>canceled flag</a> if the
820-
{{Event/cancelable}} attribute value is true.
818+
method must set the <a>canceled flag</a> if the {{Event/cancelable}} attribute value is true and
819+
the <a>in passive listener flag</a> is unset.
820+
821+
<p class="note no-backref">
822+
This means there are scenarios where invoking {{preventDefault()}} has no effect. User agents are
823+
encouraged to log the precise cause in a developer console, to aid debugging.
824+
</p>
821825

822826
The
823827
<dfn attribute for=Event>defaultPrevented</dfn>
@@ -972,14 +976,19 @@ for historical reasons.
972976
<pre class=idl>
973977
[Exposed=(Window,Worker)]
974978
interface EventTarget {
975-
void addEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
976-
void removeEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
979+
void addEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
980+
void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
977981
boolean dispatchEvent(Event event);
978982
};
979983

980984
callback interface EventListener {
981985
void handleEvent(Event event);
982986
};
987+
988+
dictionary EventListenerOptions {
989+
boolean capture;
990+
boolean passive;
991+
};
983992
</pre>
984993

985994
{{EventTarget}} is an object to which an
@@ -991,60 +1000,98 @@ occurred. Each {{EventTarget}} has an associated list of
9911000
<p>An <dfn export id=concept-event-listener>event listener</dfn> can be used to observe a specific
9921001
<a>event</a>.
9931002

994-
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, and <b>capture</b>. An
995-
<a>event listener</a> also has an associated <b>removed flag</b>, which is initially unset.
1003+
<p>An <a>event listener</a> consists of a <b>type</b>, <b>callback</b>, <b>capture</b> and
1004+
<b>passive</b>. An <a>event listener</a> also has an associated <b>removed flag</b>, which is
1005+
initially unset.
9961006

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

10001010
<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>
1011+
<dt><code><var>target</var> . <a method lt="addEventListener()">addEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code>
10021012
<dd>
10031013
Appends an <a>event listener</a> for <a>events</a> whose {{Event/type}} attribute value
10041014
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
1007-
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}}
1009-
attribute value is {{Event/CAPTURING_PHASE}}. Either way, <b>callback</b> will be
1010-
invoked if <a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/AT_TARGET}}.
1011-
1012-
The <a>event listener</a> is appended to <var>target</var>'s list of
1013-
<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.
1015-
1016-
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>capture</var> = false])</code>
1015+
be invoked when the <a>event</a> is <a>dispatched</a>.
1016+
1017+
The <var>options</var> argument sets listener-specific options. For compatibility this can be just
1018+
a boolean, in which case the method behaves exactly as if the value was specified as
1019+
<var>options</var>' <code>capture</code> member.
1020+
1021+
When set to true, <var>options</var>' <code>capture</code> member prevents <b>callback</b> from
1022+
being invoked when the <a>event</a>'s {{Event/eventPhase}} attribute value is
1023+
{{Event/BUBBLING_PHASE}}. When false (or not present), <b>callback</b> will not be invoked when
1024+
<a>event</a>'s {{Event/eventPhase}} attribute value is {{Event/CAPTURING_PHASE}}. Either way,
1025+
<b>callback</b> will be invoked if <a>event</a>'s {{Event/eventPhase}} attribute value is
1026+
{{Event/AT_TARGET}}.
1027+
1028+
When set to true, <var>options</var>' <code>passive</code> member indicates that the
1029+
<b>callback</b> will not cancel the event by invoking {{preventDefault()}}. This is used to enable
1030+
performance optimizations described in [[#observing-event-listeners]].
1031+
1032+
The <a>event listener</a> is appended to <var>target</var>'s list of <a>event listeners</a> and is
1033+
not appended if it is a duplicate, i.e., having the same <b>type</b>, <b>callback</b>,
1034+
<b>capture</b> and <b>passive</b> values.
1035+
1036+
<dt><code><var>target</var> . <a method lt="removeEventListener()">removeEventListener</a>(<var>type</var>, <var>callback</var> [, <var>options</var>])</code>
10171037
<dd>Remove the <a>event listener</a>
10181038
in <var>target</var>'s list of
10191039
<a>event listeners</a> with the same
10201040
<var>type</var>, <var>callback</var>, and
1021-
<var>capture</var>.
1041+
<var>options</var>.
10221042

10231043
<dt><code><var>target</var> . <a method lt="dispatchEvent()">dispatchEvent</a>(<var>event</var>)</code>
10241044
<dd><a>Dispatches</a> a synthetic event <var>event</var> to <var>target</var> and returns
10251045
true if either <var>event</var>'s {{Event/cancelable}} attribute value is false or its
10261046
{{Event/preventDefault()}} method was not invoked, and false otherwise.
10271047
</dl>
10281048

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

10331070
<ol>
10341071
<li><p>If <var>callback</var> is null, terminate these steps.
10351072

1073+
<li>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a>
1074+
<var>options</var>.
1075+
10361076
<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>.
1077+
<b>type</b> set to <var>type</var>, <b>callback</b> set to <var>callback</var>, <b>capture</b>
1078+
set to <var>capture</var>, and <b>passive</b> set to <var>passive</var> unless there already is an
1079+
<a>event listener</a> in that list with the same <b>type</b>, <b>callback</b>, <b>capture</b>, and
1080+
<b>passive</b>.
10401081
</ol>
10411082

10421083
<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>.
1084+
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
1085+
method, when invoked, must, run these steps
1086+
1087+
<ol>
1088+
<li>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> <var>options</var>.
1089+
1090+
<li>If there is an <a>event listener</a> in the associated list of <a>event listeners</a> whose
1091+
<b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>, <b>capture</b> is
1092+
<var>capture</var>, and <b>passive</b> is <var>passive</var> then set that <a>event listener</a>'s
1093+
<b>removed flag</b> and remove it from the associated list of <a>event listeners</a>.
1094+
</ol>
10481095

10491096
<p>The <dfn method for=EventTarget><code>dispatchEvent(<var>event</var>)</code></dfn> method, when
10501097
invoked, must run these steps:
@@ -1059,6 +1106,30 @@ invoked, must run these steps:
10591106
</ol>
10601107

10611108

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

10641135
<p>To <dfn export for=Event id=concept-event-dispatch>dispatch</dfn> an <var>event</var> to a
@@ -1139,9 +1210,15 @@ invoked, must run these steps:
11391210
<var>listener</var>'s <b>capture</b> is true, terminate these substeps (and run them for the next
11401211
<a>event listener</a>).
11411212

1213+
<li>If <var>listener</var>'s <b>passive</b> is true, set <var>event</var>'s <a>in passive
1214+
listener flag</a>.
1215+
11421216
<li><p>Call <var>listener</var>'s <b>callback</b>'s {{EventListener/handleEvent()}}, with
11431217
<var>event</var> as argument and <var>event</var>'s {{Event/currentTarget}} attribute value as
11441218
<a>callback this value</a>. If this throws any exception, <a>report the exception</a>.
1219+
1220+
<li>Clear <var>event</var>'s <a>in passive listener flag</a>.
1221+
11451222
</ol>
11461223
</ol>
11471224

@@ -9073,6 +9150,7 @@ Peter Sharpe,
90739150
Philip Jägenstedt,
90749151
Philippe Le Hégaret,
90759152
Rafael Weinstein,
9153+
Rick Byers,
90769154
Rick Waldron,
90779155
Robbert Broersma,
90789156
Robin Berjon,

0 commit comments

Comments
 (0)