-
Notifications
You must be signed in to change notification settings - Fork 307
Add EventListenerOptions and passive event listeners #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 | ||
|
@@ -816,8 +815,13 @@ 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. | ||
method 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. | ||
</p> | ||
|
||
The | ||
<dfn attribute for=Event>defaultPrevented</dfn> | ||
|
@@ -972,14 +976,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 +1000,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>Let <var>capture</var> and <var>passive</var> be false. | ||
|
||
<li>If <var>options</var> is a boolean, set <var>capture</var> to | ||
<var>options</var>. | ||
|
||
<li>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>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>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>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>Let <var>capture</var> and <var>passive</var> be the result of <a>flattening</a> <var>options</var>. | ||
|
||
<li>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 +1106,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 +1210,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>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>Clear <var>event</var>'s <a>in passive listener flag</a>. | ||
|
||
</ol> | ||
</ol> | ||
|
||
|
@@ -9073,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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these four steps are repeated below I feel like we should introduce an abstract operation that returns two values for capture and passive given a dictionary or boolean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I attempted that here and I agree it looks cleaner, thanks! I couldn't find another example of returning multiple values, is what I've done precise enough? Any suggestion for a term better than "normalize" (which could perhaps be confused with Node.normalize)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"flatten" perhaps? What you did seems great. Returning multiple values in English feels quite natural and I've done it before without confusing anybody.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Sure "flatten" sounds better to me - done.