Prohibited duplicate listeners of the same event
parent
875c43cee4
commit
66e3d5b1ef
|
@ -193,6 +193,12 @@ module.exports = Trait( 'Evented',
|
|||
else if ( typeof listener !== 'function' ) {
|
||||
throw TypeError( "Event listener must be a function" );
|
||||
}
|
||||
else if ( this._hooksEvent( ev, listener ) )
|
||||
{
|
||||
throw Error(
|
||||
`Listener has already been bound to event \`${ev}'`
|
||||
);
|
||||
}
|
||||
|
||||
this.hookEvent( ev, listener );
|
||||
return this;
|
||||
|
@ -222,12 +228,17 @@ module.exports = Trait( 'Evented',
|
|||
let evls = this._events[ ev ],
|
||||
avail = this._gaps[ ev ].pop();
|
||||
|
||||
if ( listener[ _evid ] === undefined )
|
||||
{
|
||||
listener[ _evid ] = {};
|
||||
}
|
||||
|
||||
if ( avail === undefined ) {
|
||||
listener[ _evid ] = evls.length;
|
||||
listener[ _evid ][ ev ] = evls.length;
|
||||
evls.push( listener );
|
||||
}
|
||||
else {
|
||||
listener[ _evid ] = avail;
|
||||
listener[ _evid ][ ev ] = avail;
|
||||
evls[ avail ] = listener;
|
||||
}
|
||||
|
||||
|
@ -235,6 +246,27 @@ module.exports = Trait( 'Evented',
|
|||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether LISTENER is actively monitoring event EV
|
||||
*
|
||||
* Note that this method does not verify by reference that the given
|
||||
* listener is legitimate---it assumes that the event metadata attached
|
||||
* to LISTENER (if any) has not been tampered with or forged.
|
||||
*
|
||||
* @O {1} constant time
|
||||
*
|
||||
* @param {string} ev defined event id
|
||||
* @param {Function} listener listener to query
|
||||
*
|
||||
* @return {boolean} whether LISTENER hooks event EV
|
||||
*/
|
||||
_hooksEvent( ev, listener )
|
||||
{
|
||||
let levdata = listener[ _evid ];
|
||||
return ( levdata && ( levdata[ ev ] !== undefined ) );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Alias for #on
|
||||
*
|
||||
|
@ -274,15 +306,18 @@ module.exports = Trait( 'Evented',
|
|||
throw Error( `Cannot unhook undefined event \`${ev}'` );
|
||||
}
|
||||
|
||||
let evls = this._events[ ev ],
|
||||
index = listener[ _evid ];
|
||||
let evls = this._events[ ev ],
|
||||
levdata = listener[ _evid ] || {},
|
||||
index = levdata[ ev ];
|
||||
|
||||
// this is important, since we (a) cannot necessarily trust that the
|
||||
// cached index hasn't been forged and (b) if a listener is removed
|
||||
// multiple times, the index may contain a different listener
|
||||
if ( evls[ index ] === listener )
|
||||
if ( ( index !== undefined ) && ( evls[ index ] === listener ) )
|
||||
{
|
||||
evls[ index ] = undefined;
|
||||
levdata[ ev ] = undefined;
|
||||
|
||||
this._gaps[ ev ].push( index );
|
||||
}
|
||||
|
||||
|
|
|
@ -189,6 +189,31 @@ describe( 'event.Evented', () =>
|
|||
expect( () => stub[ on ]( ev, "kittens" ) )
|
||||
.to.throw( TypeError );
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* It is highly suspect if the exact same listener is registered
|
||||
* for the same event---it is either a bug, or a questionable
|
||||
* design decision; we assume that the former is far more likely
|
||||
* to be true, and that the latter should be addressed and
|
||||
* resolved.
|
||||
*
|
||||
* Note that this does not prevent a caller from wrapping a
|
||||
* listener in a function that is instead registered as the
|
||||
* listener---this is perfectly valid and has many use cases.
|
||||
*/
|
||||
it( 'does not permit same event listener multiple times', () =>
|
||||
{
|
||||
var ev = 'foo',
|
||||
f = () => {};
|
||||
|
||||
expect( () =>
|
||||
{
|
||||
stub.evDefineEvents( [ ev ] )
|
||||
[ on ]( ev, f )
|
||||
[ on ]( ev, f );
|
||||
} ).to.throw( Error );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
|
Loading…
Reference in New Issue