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

removeEventListener doesn't handle currently dispatching events #84

Closed
Sebmaster opened this issue Sep 27, 2015 · 14 comments
Closed

removeEventListener doesn't handle currently dispatching events #84

Sebmaster opened this issue Sep 27, 2015 · 14 comments

Comments

@Sebmaster
Copy link

It seems like browsers remove an event listener even from the currently dispatched event listener list. Explaining it is kinda complicated, but here's a test which fails with an implementation that is (I hope) according to spec:

    var es = [];
    var ls = [];
    var EventListener1 = function() { ls.push(this); }
    var EventListener2 = function() { ls.push(this); }
    EventListener1.prototype.handleEvent = function(event) { _handleEvent(event); }
    EventListener2.prototype.handleEvent = function(event) { _handleEvent(event); }
    var _handleEvent = function(event) {
      es.push(event);
      ls.forEach(function(l){
        event.currentTarget.removeEventListener("foo", l.handleEvent, false);
      })
    }
    // test
    var listener1 = new EventListener1();
    var listener2 = new EventListener2();
    document.addEventListener("foo", listener1.handleEvent, false);
    document.addEventListener("foo", listener2.handleEvent, false);
    var event = document.createEvent("Events");
    event.initEvent("foo",true,false);
    document.dispatchEvent(event);
    console.log(es.length); // returns 1 in Chrome + FF

So, both listeners remove themselves + the other listener, which seems to cause the currently dispatched event to only get to 1 listener since the other one is removed.

In event listener invoke the spec says to copy the event listener list, which should cause it to not be impacted by changes from removeEventListener (I'd assume).

@ArkadiuszMichalski
Copy link
Contributor

Hi, some times ago I noticed the same thing, although its correct in IE:
https://bugzilla.mozilla.org/show_bug.cgi?id=906608

@Ms2ger
Copy link
Member

Ms2ger commented Sep 27, 2015

Somewhat more readable:

<!DOCTYPE html>
<script>
var es = [];
var ls = [
  function(event) { _handleEvent(event); },
  function(event) { _handleEvent(event); },
];
var _handleEvent = function(event) {
  es.push(event);
  ls.forEach(function(l) {
    document.removeEventListener("foo", l);
  })
};
ls.forEach(function(l) {
  document.addEventListener("foo", l);
});

var event = new Event("foo");
document.dispatchEvent(event);
w(es.length); // returns 1 in Chrome + FF
</script>

@Ms2ger
Copy link
Member

Ms2ger commented Sep 27, 2015

Servo returns 2.

@annevk
Copy link
Member

annevk commented Sep 27, 2015

I'm pretty sure this was tested. That's why the whole copy clause is there. I wonder what's up.

@bzbarsky
Copy link

@annevk I thought the copy was to avoid invoking newly added listeners? The behavior in Gecko is certainly that listeners added to the current target during dispatch to one of its listeners are NOT invoked, but listeners removed from it will also not be invoked if they haven't been already. We don't make a list copy internally; we use an iterator along a live list that stops at what used to be the list end when the iterator started.

@annevk
Copy link
Member

annevk commented Sep 28, 2015

Ah that makes sense. Not entirely sure how to describe iterator-like behavior in prose though. I guess I could "If any event listener is removed, also remove it from listeners" but that seems like a cop-out.

@annevk
Copy link
Member

annevk commented Nov 11, 2015

The best I can think of is that rather than a copy we create a list of pointers. And then when a listener is removed some kind of "removed flag" gets set that is checked by the invoke algorithm. The concept of iteration over a live list isn't really defined (ideally IDL would define some constructs for us).

Would that work @bzbarsky?

@bzbarsky
Copy link

That's starting to sound like the setup ES uses for Map and Set, which works as follows:

There is a list of things, each of which is either an entry or a removed-entry marker (e.g. in the case of Map the removed-entry marker is an entry record with [[key]] set to empty; in our case the removed-entry marker could be something like storing an EventListener? and treating null as the removed-entry marker). Operations on the list skip over removed-entry markers, and the removed-entry markers are never explicitly observed (this is important). As a spec device the list never shrinks. Removal of an entry simply replaces it in the list with a removed-entry marker. Implementations, of course, would never implement it this way, but are free to do anything that produces equivalent observable behavior (e.g. having some way to notify all live iterators when a removal happens and actually removing from the list).

It's not quite exactly what we want here, because we want the event dispatch iteration to not consider newly added stuff. But we could do that by grabbing the index to stop at up front in the dispatch and then stopping there, I think.

@annevk
Copy link
Member

annevk commented Nov 11, 2015

The way to not consider new stuff was to iterate once and only store pointers for the stuff currently in the list. So you wouldn't have any pointers to newly added listeners.

@bzbarsky
Copy link

Sure, that seems equivalent as long as removal marks the thing the pointer points to as "not a listener anymore" or something. Which specification device is clearer/simpler, I dunno.

@annevk annevk closed this as completed in 02710dd Nov 12, 2015
@annevk
Copy link
Member

annevk commented Nov 12, 2015

Thank you @Sebmaster for reporting this. I forgot to add your name in this commit, but that'll be sorted in a future one. Hope that's okay.

@Sebmaster
Copy link
Author

Sure, no big deal.

@annevk
Copy link
Member

annevk commented Nov 12, 2015

It's been sorted by the way, have a look: https://dom.spec.whatwg.org/#acks.

@Sebmaster
Copy link
Author

Sweet 👍 Now let's have a look at this HTML spec... 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants