Make sure we call event listeners in the order they are registered (fix #4743) (#4769)

This commit is contained in:
Yohan Boniface
2016-08-02 16:53:06 +02:00
committed by Vladimir Agafonkin
parent 450ef3ec77
commit 8047b0b7a9
2 changed files with 88 additions and 59 deletions

View File

@ -35,6 +35,62 @@ describe('Events', function () {
// expect(spy6.callCount).to.be(1); // expect(spy6.callCount).to.be(1);
}); });
it('fires all listeners in the order they are added', function () {
var obj = new L.Evented(),
ctx1 = new L.Class(),
ctx2 = new L.Class(),
count = {one: 0, two: 0, three: 0, four: 0};
function listener1(e) {
count.one++;
expect(count.two).to.eql(0);
}
function listener2(e) {
count.two++;
expect(count.one).to.eql(1);
expect(count.three).to.eql(0);
if (count.two === 1) {
expect(this).to.eql(ctx2);
} else if (count.two === 2) {
expect(this).to.eql(ctx1);
} else {
expect(this).to.eql(obj);
}
}
function listener3(e) {
count.three++;
expect(count.two).to.eql(3);
expect(count.four).to.eql(0);
if (count.three === 1) {
expect(this).to.eql(ctx1);
} else if (count.three === 2) {
expect(this).to.eql(ctx2);
}
}
function listener4(e) {
count.four++;
expect(count.three).to.eql(2);
}
obj.on('test', listener1, ctx1);
obj.on('test', listener2, ctx2);
obj.on('test', listener2, ctx1); // Same listener but with different context.
obj.on('test', listener2); // Same listener but without context.
obj.on('test', listener3, ctx1);
obj.on('test', listener3, ctx2);
obj.on('test', listener4, ctx2);
obj.fireEvent('test');
expect(count.one).to.be(1);
expect(count.two).to.be(3);
expect(count.three).to.be(2);
expect(count.four).to.be(1);
});
it('provides event object to listeners and executes them in the right context', function () { it('provides event object to listeners and executes them in the right context', function () {
var obj = new L.Evented(), var obj = new L.Evented(),
obj2 = new L.Evented(), obj2 = new L.Evented(),

View File

@ -96,30 +96,22 @@ L.Evented = L.Class.extend({
var typeListeners = this._events[type]; var typeListeners = this._events[type];
if (!typeListeners) { if (!typeListeners) {
typeListeners = { typeListeners = {
listeners: {}, listeners: [],
count: 0 count: 0
}; };
this._events[type] = typeListeners; this._events[type] = typeListeners;
} }
var contextId = context && context !== this && L.stamp(context), if (context === this) {
newListener = {fn: fn, ctx: context}; // Less memory footprint.
context = undefined;
if (!contextId) {
contextId = 'no_context';
newListener.ctx = undefined;
}
// fn array for context
var listeners = typeListeners.listeners[contextId];
if (!listeners) {
listeners = [];
typeListeners.listeners[contextId] = listeners;
} }
var newListener = {fn: fn, ctx: context},
listeners = typeListeners.listeners;
// check if fn already there // check if fn already there
for (var i = 0, len = listeners.length; i < len; i++) { for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn) { if (listeners[i].fn === fn && listeners[i].ctx === context) {
return; return;
} }
} }
@ -130,68 +122,55 @@ L.Evented = L.Class.extend({
_off: function (type, fn, context) { _off: function (type, fn, context) {
var typeListeners, var typeListeners,
contextId,
listeners, listeners,
i, i,
len; len;
if (!this._events) { return; } if (!this._events) { return; }
if (!fn) {
// Set all removed listeners to noop so they are not called if remove happens in fire
typeListeners = this._events[type]; typeListeners = this._events[type];
if (typeListeners) {
for (contextId in typeListeners.listeners) {
listeners = typeListeners.listeners[contextId];
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = L.Util.falseFn;
}
}
// clear all listeners for a type if function isn't specified
delete this._events[type];
}
return;
}
typeListeners = this._events[type];
if (!typeListeners) { if (!typeListeners) {
return; return;
} }
contextId = context && context !== this && L.stamp(context); listeners = typeListeners.listeners;
if (!contextId) {
contextId = 'no_context'; if (!fn) {
// Set all removed listeners to noop so they are not called if remove happens in fire
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = L.Util.falseFn;
}
// clear all listeners for a type if function isn't specified
delete this._events[type];
return;
}
if (context === this) {
context = undefined;
} }
listeners = typeListeners.listeners[contextId];
if (listeners) { if (listeners) {
// find fn and remove it // find fn and remove it
for (i = 0, len = listeners.length; i < len; i++) { for (i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i]; var l = listeners[i];
if (l.ctx !== context) { continue; }
if (l.fn === fn) { if (l.fn === fn) {
// set the removed listener to noop so that's not called if remove happens in fire // set the removed listener to noop so that's not called if remove happens in fire
l.fn = L.Util.falseFn; l.fn = L.Util.falseFn;
typeListeners.count--; typeListeners.count--;
if (len > 1) { if (this._isFiring) {
if (!this._isFiring) {
listeners.splice(i, 1);
} else {
/* copy array in case events are being fired */ /* copy array in case events are being fired */
typeListeners.listeners[contextId] = listeners.slice(); listeners = listeners.slice();
typeListeners.listeners[contextId].splice(i, 1);
}
} else {
delete typeListeners.listeners[contextId];
} }
listeners.splice(i, 1);
return; return;
} }
if (listeners.length === 0) {
delete typeListeners.listeners[contextId];
}
} }
} }
}, },
@ -210,17 +189,11 @@ L.Evented = L.Class.extend({
if (typeListeners) { if (typeListeners) {
this._isFiring = true; this._isFiring = true;
var listeners = typeListeners.listeners;
// each context
for (var contextId in typeListeners.listeners) {
var listeners = typeListeners.listeners[contextId];
// each fn in context
for (var i = 0, len = listeners.length; i < len; i++) { for (var i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i]; var l = listeners[i];
l.fn.call(l.ctx || this, event); l.fn.call(l.ctx || this, event);
} }
}
this._isFiring = false; this._isFiring = false;
} }