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

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

Merged
merged 1 commit into from
Aug 2, 2016
Merged
Show file tree
Hide file tree
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
Make sure we call event listeners in the order they are registered (fix
  • Loading branch information
yohanboniface committed Aug 1, 2016
commit 4685207b250e480a16f6a85bef54adc27cb0fd0e
56 changes: 56 additions & 0 deletions spec/suites/core/EventsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,62 @@ describe('Events', function () {
// 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 () {
var obj = new L.Evented(),
obj2 = new L.Evented(),
Expand Down
87 changes: 30 additions & 57 deletions src/core/Events.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,30 +96,22 @@ L.Evented = L.Class.extend({
var typeListeners = this._events[type];
if (!typeListeners) {
typeListeners = {
listeners: {},
listeners: [],
count: 0
Copy link
Contributor

@fab1an fab1an Aug 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this count variable can be removed and replaced by listeners.length.

Actually I think the whole typeListeners object can be replaced by the listeners array itself now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fab1an you're correct! We did away with typeListeners yesterday! 017d29c

};
this._events[type] = typeListeners;
}

var contextId = context && context !== this && L.stamp(context),
newListener = {fn: fn, ctx: context};

if (!contextId) {
contextId = 'no_context';
newListener.ctx = undefined;
}

// fn array for context
var listeners = typeListeners.listeners[contextId];
if (!listeners) {
listeners = [];
typeListeners.listeners[contextId] = listeners;
if (context === this) {
// Less memory footprint.
context = undefined;
}
var newListener = {fn: fn, ctx: context},
listeners = typeListeners.listeners;

// check if fn already there
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;
}
}
Expand All @@ -130,68 +122,55 @@ L.Evented = L.Class.extend({

_off: function (type, fn, context) {
var typeListeners,
contextId,
listeners,
i,
len;

if (!this._events) { return; }

typeListeners = this._events[type];

if (!typeListeners) {
return;
}

listeners = typeListeners.listeners;

if (!fn) {
// Set all removed listeners to noop so they are not called if remove happens in fire
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];
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) {
return;
}

contextId = context && context !== this && L.stamp(context);
if (!contextId) {
contextId = 'no_context';
if (context === this) {
context = undefined;
}

listeners = typeListeners.listeners[contextId];
if (listeners) {

// find fn and remove it
for (i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
if (l.ctx !== context) { continue; }
if (l.fn === fn) {

// set the removed listener to noop so that's not called if remove happens in fire
l.fn = L.Util.falseFn;
typeListeners.count--;

if (len > 1) {
if (!this._isFiring) {
listeners.splice(i, 1);
} else {
/* copy array in case events are being fired */
typeListeners.listeners[contextId] = listeners.slice();
typeListeners.listeners[contextId].splice(i, 1);
}
} else {
delete typeListeners.listeners[contextId];
if (this._isFiring) {
/* copy array in case events are being fired */
listeners = listeners.slice();
}
listeners.splice(i, 1);

return;
}
if (listeners.length === 0) {
delete typeListeners.listeners[contextId];
}
}
}
},
Expand All @@ -210,16 +189,10 @@ L.Evented = L.Class.extend({

if (typeListeners) {
this._isFiring = true;

// 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++) {
var l = listeners[i];
l.fn.call(l.ctx || this, event);
}
var listeners = typeListeners.listeners;
for (var i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
l.fn.call(l.ctx || this, event);
}

this._isFiring = false;
Expand Down