-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
The way event handlers are spec'd right now implies that once an event listener running event handler processing algorithm is added (under the "When an event handler H of an EventTarget object T is first set to a non-null value" steps), it will never get removed. However, this doesn't seem to reflect implementations' actual behavior.
Exhibit A.1:
<button id="test">Start Demo</button>
<script>
var button = document.getElementById('test');
button.onclick = function () { alert('ONE'); }; // 1
button.addEventListener('click', function () { alert('TWO') }, false); // 2
button.onclick = null; // 3
button.onclick = function () { alert('ONE'); }; // 4
</script>Exhibit A.2:
<button id="test">Start Demo</button>
<script>
var button = document.getElementById('test');
button.setAttribute("onclick", "alert('ONE');"); // 1
button.addEventListener('click', function () { alert('TWO') }, false); // 2
button.removeAttribute("onclick"); // 3
button.setAttribute("onclick", "alert('ONE');"); // 4
</script>In both of these cases, one would expect step 1 to register an event listener running the event handler processing algorithm to be registered as the first event listener. That is, after 1 and 2 if the button were to be clicked, "ONE TWO" would result.
After 3, the event handler for the click event would be set to null, but per current spec the event handler processing algorithm should still be the first event listener in the list. After 4, the event handler is restored, so "ONE TWO" would be alerted in order just like after 2.
However, in all four implementations tested (Chrome, Edge, Firefox, Safari) the order is "TWO ONE" for these two test cases.
Exhibit B:
<button id="test">Start Demo</button>
<script>
var button = document.getElementById('test');
button.setAttribute("onclick", "alert('ONE');"); // 1
button.addEventListener('click', function () { alert('TWO') }, false); // 2
button.onclick = null; // 3
button.setAttribute("onclick", "alert('ONE');"); // 4
</script>Similar to above, the spec behavior would be "ONE TWO". However, Chrome, Edge, and Safari all alert "TWO ONE", while Firefox alerts "TWO" only (presumably because of the fact that the second setAttribute didn't actually change the value of the attribute, so Firefox doesn't bother re-compiling the attribute).
These three test cases show that in all implementations step 3 actually removes the event listener, contrary to what the spec claims. We need to fix that.
In particular, Blink's implementation of IDL attribute setter, where the new value is newValue, can be summarized as:
-
If newValue is null, then:
-
If an event handler processing algorithm event listener has been registered, then remove that listener.
-
Return.
-
-
Otherwise, if an event handler processing algorithm event listener has been registered, replace the current event handler with newValue, then return.
-
Otherwise, add an event listener with the event handler processing algorithm as its operation. (Basically the "When an event handler H of an
EventTargetobject T is first set to a non-null value" steps).
The steps are pretty similar in WebKit.
Compare with current spec:
-
Set the current event handler to newValue.
-
If newValue is null, then return.
-
Otherwise, if an event handler processing algorithm event listener has been registered, then return.
-
Otherwise, add an event listener with the event handler processing algorithm as its operation. (Basically the "When an event handler H of an
EventTargetobject T is first set to a non-null value" steps).
Another interesting way to think about this problem is through the lens of document.open(), which as part of its operation removes all event listeners of all nodes, including the ones associated with IDL or content attribute event handlers (in fact that's the only way to remove them, since removeEventListener() can never get to the spec-created event listener). Under the current spec, once the event listener associated with the event handler is removed, it can never be brought back, since the algorithm that adds the listener is only executed "When an event handler H of an EventTarget object T is first set to a non-null value." This doesn't exactly sound like what the developer would expect.