Skip to content
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

Allow removing event listeners by group #469

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 121 additions & 47 deletions dom.bs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ urlPrefix: https://w3c.github.io/ServiceWorker/#; spec: SERVICE-WORKERS
text: script resource; for: service worker
text: has ever been evaluated flag; for: service worker
urlPrefix: https://tc39.github.io/ecma262/#; spec: ECMASCRIPT
text: Construct; url: sec-construct; type: abstract-op
text: Realm; url: realm; type: dfn
</pre>

Expand Down Expand Up @@ -314,7 +313,7 @@ run these steps:
Throughout the web platform <a>events</a> are <a>dispatched</a> to objects to signal an
occurrence, such as network activity or user interaction. These objects implement the
{{EventTarget}} interface and can therefore add <a>event listeners</a> to observe
<a>events</a> by calling {{EventTarget/addEventListener()}}:
<a>events</a> by calling {{EventTarget/addEventListener(type, callback, options)}}:

<pre class=lang-javascript>
obj.addEventListener("load", imgFetched)
Expand All @@ -325,10 +324,23 @@ function imgFetched(ev) {
}
</pre>

<a>Event listeners</a> can be removed
by utilizing the
{{EventTarget/removeEventListener()}}
method, passing the same arguments.
<a>Event listeners</a> can be removed by utilizing the
{{EventTarget/removeEventListener(type, callback, options)}} method, passing the same arguments.

Once can also provide a <em>group</em> when adding an <a>event listener</a>. This can be either a
string or a JavaScript symbol value. Then later, the same group value can be provided to
{{EventTarget/removeEventListener(type, options)}} or {{EventTarget/removeEventListener(options)}},
to remove all event listeners in that group:

<pre class=lang-javascript>
oven.addEventListener("turnon", () => { &hellip; }, { group: "brownies" })
oven.addEventListener("heat", () => { &hellip; }, { group: ["brownies", "adjustments"] })
oven.addEventListener("turnoff", () => { &hellip; }, { group: "brownies" })

oven.removeEventListener({ group: "brownies" })
</pre>

<hr>

<a>Events</a> are objects too and implement the
{{Event}} interface (or a derived interface). In the example above
Expand Down Expand Up @@ -879,10 +891,16 @@ for historical reasons.
[Exposed=(Window,Worker)]
interface EventTarget {
void addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options);

void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
void removeEventListener(DOMString type, RemoveEventListenerGroupOptions options);
void removeEventListener(RemoveEventListenerGroupOptions options);

boolean dispatchEvent(Event event);
};

typedef (DOMString or symbol) EventGroup;

callback interface EventListener {
void handleEvent(Event event);
};
Expand All @@ -891,9 +909,14 @@ dictionary EventListenerOptions {
boolean capture = false;
};

dictionary RemoveEventListenerGroupOptions {
required EventGroup group;
};

dictionary AddEventListenerOptions : EventListenerOptions {
boolean passive = false;
boolean once = false;
(EventGroup or sequence&lt;EventGroup>) group;
};
</pre>

Expand All @@ -913,6 +936,7 @@ when something has occurred.
<li><b>capture</b> (a boolean, initially false)
<li><b>passive</b> (a boolean, initially false)
<li><b>once</b> (a boolean, initially false)
<li><b>groups</b> (a <a>list</a> of {{EventGroup}}s, initially empty)
<li><b>removed</b> (a boolean for bookkeeping purposes, initially false)
</ul>

Expand Down Expand Up @@ -952,32 +976,44 @@ are not to be used for anything else. [[!HTML]]

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.
<var>options</var>'s {{EventListenerOptions/capture}} 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
When set to true, <var>options</var>'s {{EventListenerOptions/capture}} 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 {{Event/preventDefault()}}. This is used to
enable performance optimizations described in [[#observing-event-listeners]].
When set to true, <var>options</var>' {{AddEventListenerOptions/passive}} member indicates that
the <b>callback</b> will not cancel the event by invoking {{Event/preventDefault()}}. This is used
to enable performance optimizations described in [[#observing-event-listeners]].

When set to true, <var>options</var>'s {{AddEventListenerOptions/once}} member indicates that the
<b>callback</b> will only be invoked once after which the event listener will be removed.

When set to true, <var>options</var>'s <code>once</code> member indicates that the <b>callback</b>
will only be invoked once after which the event listener will be removed.
When provided, <var>options</var>'s {{AddEventListenerOptions/group}} member is stored so that
later calls to {{EventTarget/removeEventListener(type, options)}} or
{{EventTarget/removeEventListener(options)}} can remove all event listeners that provided the same
group value. It can be either a string or a symbol. It can also be an iterable containing strings
or symbols, to attach multiple group values to the listener, any one of which can be used for
later removal.

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 for=EventTarget 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>options</var>.
<dt><code><var>target</var> . <a method for=EventTarget lt="removeEventListener(type, callback, options)">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>options</var>.

<dt><code><var>target</var> . <a method for=EventTarget lt="removeEventListener(type, options)">removeEventListener</a>(<var>type</var>, { <var>group</var> })</code>
<dd>Remove all <a>event listeners</a> in <var>target</var>'s list of <a>event listeners</a> with
the same <var>type</var> and <var>group</var>.

<dt><code><var>target</var> . <a method for=EventTarget lt=removeEventListener(options)>removeEventListener</a>({ <var>group</var> })</code>
<dd>Remove all <a>event listeners</a> in <var>target</var>'s list of <a>event listeners</a> with
the same <var>group</var> (regardless of their type).

<dt><code><var>target</var> . <a method for=EventTarget 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 @@ -993,8 +1029,8 @@ steps:

<li><p>If <var>options</var> is a boolean, set <var>capture</var> to <var>options</var>.

<li><p>If <var>options</var> is a dictionary, then set <var>capture</var> to <var>options</var>'s
<code>{{EventListenerOptions/capture}}</code>.
<li><p>If <var>options</var> is a dictionary, then set <var>capture</var> to
<var>options</var>["{{EventListenerOptions/capture}}"].

<li><p>Return <var>capture</var>.
</ol>
Expand All @@ -1005,59 +1041,97 @@ steps:
<ol>
<li><p>Let <var>capture</var> be the result of <a>flattening</a> <var>options</var>.

<li><p>Let <var>once</var> and <var>passive</var> be false.
<li><p>Let <var>once</var> and <var>passive</var>, and let <var>groups</var> be an empty
<a>list</a>.

<li><p>If <var>options</var> is a dictionary, then set <var>passive</var> to <var>options</var>'s
<code>{{AddEventListenerOptions/passive}}</code> and <var>once</var> to <var>options</var>'s
<code>{{AddEventListenerOptions/once}}</code>.
<li>
<p>If <var>options</var> is a dictionary, then:
<ol>
<li><p>Set <var>passive</var> to <var>options</var>["{{AddEventListenerOptions/passive}}"] and
<var>once</var> to <var>options</var>["{{AddEventListenerOptions/once}}"].

<li><p>Return <var>capture</var>, <var>passive</var>, and <var>once</var>.
<li><p>If {{AddEventListenerOptions/group}} is <a>present</a> in <var>options</var> and is an
{{EventGroup}}, set <var>groups</var> to a <a>list</a> containing the single item given by
<var>options</var>["{{AddEventListenerOptions/group}}"].

<li><p>Otherwise, if {{AddEventListenerOptions/group}} is <a>present</a> in <var>options</var>,
set <var>groups</var> to <var>options</var>["{{AddEventListenerOptions/group}}"].
</ol>

<li><p>Return the <a>tuple</a> (<var>capture</var>, <var>passive</var>, <var>once</var>,
<var>groups</var>).
</ol>

<p>To <dfn for=EventTarget>perform a service worker check</dfn> on an {{EventTarget}}
<var>eventTarget</var>, <a>throw</a> a {{TypeError}} if <var>eventTarget</var>'s
<a>relevant global object</a> is a {{ServiceWorkerGlobalScope}} object and its associated
<a>service worker</a>'s <a for="service worker">script resource</a>'s
<a for="service worker">has ever been evaluated flag</a> is set. [[!SERVICE-WORKERS]]

<p class="note no-backref">To optimize storing the event types allowed for the service worker and to
avoid non-deterministic changes to the event listeners, invocation of certain {{EventTarget}}
methods is allowed only during the very first evaluation of the service worker script.

<p>To <dfn for=EventTarget lt="remove an event listener">remove</dfn> an <a>event listener</a>
<var>listener</var> from an {{EventTarget}} <var>eventTarget</var>, set <var>listener</var>'s
<b>removed</b> to true and <a for=list>remove</a> it from <var>eventTarget</var>'s associated list
of <a>event listeners</a>.

<p>The
<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 <a>context object</a>'s <a>relevant global object</a> is a {{ServiceWorkerGlobalScope}}
object and its associated <a>service worker</a>'s <a for="service worker">script resource</a>'s
<a for="service worker">has ever been evaluated flag</a> is set, then <a>throw</a> a
<code>TypeError</code>.
[[!SERVICE-WORKERS]]

<p class="note no-backref">To optimize storing the event types allowed for the service worker and
to avoid non-deterministic changes to the event listeners, invocation of the method is allowed
only during the very first evaluation of the service worker script.
<li><p><a>Perform a service worker check</a> on <a>context object</a>.

<li><p>If <var>callback</var> is null, then return.

<li><p>Let <var>capture</var>, <var>passive</var>, and <var>once</var> be the result of
<a lt="flatten more">flattening more</a> <var>options</var>.
<li><p>Let (<var>capture</var>, <var>passive</var>, <var>once</var>, <var>groups</var>) be the
result of <a lt="flatten more">flattening more</a> <var>options</var>.

<li><p>If <a>context object</a>'s associated list of <a>event listener</a> does not contain an
<a>event listener</a> whose <b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>,
and <b>capture</b> is <var>capture</var>, then append a new <a>event listener</a> to it, whose
<b>type</b> is <var>type</var>, <b>callback</b> is <var>callback</var>, <b>capture</b> is
<var>capture</var>, <b>passive</b> is <var>passive</var>, and <b>once</b> is <var>once</var>.
<var>capture</var>, <b>passive</b> is <var>passive</var>, <b>once</b> is <var>once</var>, and
<b>groups</b> is <var>groups</var>.
</ol>

<p>The
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>callback</var>, <var>options</var>)</code></dfn>
method, when invoked, must, run these steps
method overload, when invoked, must run these steps:

<ol>
<li><p>If <a>context object</a>'s <a>relevant global object</a> is a {{ServiceWorkerGlobalScope}}
object and its associated <a>service worker</a>'s <a for="service worker">script resource</a>'s
<a for="service worker">has ever been evaluated flag</a> is set, then <a>throw</a> a
<code>TypeError</code>. [[!SERVICE-WORKERS]]
<li><p><a>Perform a service worker check</a> on <a>context object</a>.

<li><p>Let <var>capture</var> be the result of <a>flattening</a> <var>options</var>.

<li><p>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>, then set that <a>event listener</a>'s <b>removed</b> to true and remove it from
the associated list of <a>event listeners</a>.
<var>capture</var>, then <a lt="remove an event listener">remove</a> it.
</ol>

<p>The
<dfn method for=EventTarget><code>removeEventListener(<var>type</var>, <var>options</var>)</code></dfn>
method overload, when invoked, must run these steps:

<ol>
<li><p><a>Perform a service worker check</a> on <a>context object</a>.

<li><p><a lt="remove an event listener">Remove</a> all <a>event listeners</a> whose <b>type</b> is
<var>type</var> and <b>groups</b> <a for=list>contains</a>
<var>options</var>["{{RemoveEventListenerGroupOptions/group}}"].
</ol>

<p>The
<dfn method for=EventTarget><code>removeEventListener(<var>options</var>)</code></dfn>
method overload, when invoked, must run these steps:

<ol>
<li><p><a>Perform a service worker check</a> on <a>context object</a>.

<li><p><a lt="remove an event listener">Remove</a> all <a>event listeners</a> whose <b>groups</b>
<a for=list>contains</a> <var>options</var>["{{RemoveEventListenerGroupOptions/group}}"].
</ol>

<p>The <dfn method for=EventTarget><code>dispatchEvent(<var>event</var>)</code></dfn> method, when
Expand Down