Skip to content

Commit

Permalink
Add EventListenerOptions and passive event listener feature
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RByers committed Nov 30, 2015
1 parent 9202ada commit 7e05d12
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 73 deletions.
115 changes: 90 additions & 25 deletions dom.bs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,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
urlPrefix: infrastructure.html
text: in parallel
urlPrefix: https://w3c.github.io/webcomponents/spec/shadow/
type: dfn; urlPrefix: #dfn-
text: shadow root
Expand Down Expand Up @@ -612,7 +614,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)
Expand Down Expand Up @@ -727,16 +729,15 @@ inherits from the {{Event}} interface.

<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,
{{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.
{{Event/preventDefault()}} was used successfully to indicate cancellation.

<dt><code><var>event</var> . {{Event/isTrusted}}</code>
<dd>Returns true if <var>event</var> was
Expand Down Expand Up @@ -798,6 +799,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>passive flag</dfn>
</ul>

The
Expand All @@ -816,7 +818,14 @@ 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.
{{Event/cancelable}} attribute value is true and
the <a>passive flag</a> is unset.

<p class="note no-backref">
User agents are encouraged to generate a console warning or other debugging
aid to help authors identify places where calls to {{preventDefault()}}
have no effect.
</p>

The
<dfn attribute for=Event>defaultPrevented</dfn>
Expand Down Expand Up @@ -971,14 +980,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 = false;
boolean passive = false;
};
</pre>

{{EventTarget}} is an object to which an
Expand All @@ -990,34 +1004,43 @@ 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
<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
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 it determines the value of the <b>capture</b> option.

When set to true,
the <var>capture</var> option 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}}.

When set to true, the <var>passive</var> option 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>, and <b>capture</b> values.
<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>capture</var> = false])</code>
<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
Expand All @@ -1026,24 +1049,40 @@ seen from the definition above, an <a>event listener</a> is a more broad concept
</dl>

<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>If <var>options</var> is of type boolean, let <var>capture</var> be
<var>options</var> and let <var>passive</var> be false. Otherwise let
<var>capture</var> and <var>passive</var> be the corresponding values in the
<var>options</var> {{EventListenerOptions}} dictionary.

<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>name</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>If <var>options</var> is of type boolean, let <var>capture</var> be
<var>options</var> and let <var>passive</var> be false. Otherwise let
<var>capture</var> and <var>passive</var> be the corresponding values in the
<var>options</var> {{EventListenerOptions}} dictionary.

<li>If there is an <a>event listener</a> in the associated list of
<a>event listeners</a> whose <b>type</b> is <var>name</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:
Expand All @@ -1057,6 +1096,25 @@ invoked, must run these steps:
<li><p><a>Dispatch</a> the <var>event</var> and return the value that returns.
</ol>

<h3 id=observing-event-listeners>Observing event listeners</h3>
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.

Unfortunately, some event APIs have been designed such that implementing them
efficiently requires observing <a>event listeners</a>. For example, sensor APIs which
enable an underlying device sensor, and touch APIs 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).
Ideally, any new event types are defined such that they don't need this
property (use <a href="https://lists.w3.org/Archives/Public/public-script-coord/">public-scrip-coord@w3.org</a>
for discussion).


<h3 id=dispatching-events>Dispatching events</h3>

Expand Down Expand Up @@ -1138,9 +1196,15 @@ 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>If <var>listener</var>'s <b>passive</b> is true, set <var>event</var>'s <a>passive 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>.
<a>callback this value</a>.

<li>Clear <var>event</var>'s <a>passive flag</a>.

<li>If the call to {{EventListener/handleEvent()}} threw any exception, <a>report the exception</a>.
</ol>
</ol>

Expand Down Expand Up @@ -9097,6 +9161,7 @@ Peter Sharpe,
Philip Jägenstedt,
Philippe Le Hégaret,
Rafael Weinstein,
Rick Byers,
Rick Waldron,
Robbert Broersma,
Robin Berjon,
Expand Down
Loading

0 comments on commit 7e05d12

Please sign in to comment.