blob: 82b34803e847d6535a280fdd6d61608c4bacc832 [file] [log] [blame]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
'use strict';
/**
* Describes an event listener registered on an {@linkplain EventEmitter}.
*/
class Listener {
/**
* @param {!Function} fn The actual listener function.
* @param {(Object|undefined)} scope The object in whose scope to invoke the
* listener.
* @param {boolean} oneshot Whether this listener should only be used once.
*/
constructor(fn, scope, oneshot) {
this.fn = fn;
this.scope = scope;
this.oneshot = oneshot;
}
}
/** @type {!WeakMap<!EventEmitter, !Map<string, !Set<!Listener>>>} */
const EVENTS = new WeakMap;
/**
* Object that can emit events for others to listen for.
*/
class EventEmitter {
/**
* Fires an event and calls all listeners.
* @param {string} type The type of event to emit.
* @param {...*} var_args Any arguments to pass to each listener.
*/
emit(type, var_args) {
let events = EVENTS.get(this);
if (!events) {
return;
}
let args = Array.prototype.slice.call(arguments, 1);
let listeners = events.get(type);
if (listeners) {
for (let listener of listeners) {
listener.fn.apply(listener.scope, args);
if (listener.oneshot) {
listeners.delete(listener);
}
}
}
}
/**
* Returns a mutable list of listeners for a specific type of event.
* @param {string} type The type of event to retrieve the listeners for.
* @return {!Set<!Listener>} The registered listeners for the given event
* type.
*/
listeners(type) {
let events = EVENTS.get(this);
if (!events) {
events = new Map;
EVENTS.set(this, events);
}
let listeners = events.get(type);
if (!listeners) {
listeners = new Set;
events.set(type, listeners);
}
return listeners;
}
/**
* Registers a listener.
* @param {string} type The type of event to listen for.
* @param {!Function} fn The function to invoke when the event is fired.
* @param {Object=} opt_self The object in whose scope to invoke the listener.
* @param {boolean=} opt_oneshot Whether the listener should b (e removed after
* the first event is fired.
* @return {!EventEmitter} A self reference.
* @private
*/
addListener_(type, fn, opt_self, opt_oneshot) {
let listeners = this.listeners(type);
for (let listener of listeners) {
if (listener.fn === fn) {
return this;
}
}
listeners.add(new Listener(fn, opt_self || undefined, !!opt_oneshot));
return this;
}
/**
* Registers a listener.
* @param {string} type The type of event to listen for.
* @param {!Function} fn The function to invoke when the event is fired.
* @param {Object=} opt_self The object in whose scope to invoke the listener.
* @return {!EventEmitter} A self reference.
*/
addListener(type, fn, opt_self) {
return this.addListener_(type, fn, opt_self, false);
}
/**
* Registers a one-time listener which will be called only the first time an
* event is emitted, after which it will be removed.
* @param {string} type The type of event to listen for.
* @param {!Function} fn The function to invoke when the event is fired.
* @param {Object=} opt_self The object in whose scope to invoke the listener.
* @return {!EventEmitter} A self reference.
*/
once(type, fn, opt_self) {
return this.addListener_(type, fn, opt_self, true);
}
/**
* An alias for {@link #addListener() addListener()}.
* @param {string} type The type of event to listen for.
* @param {!Function} fn The function to invoke when the event is fired.
* @param {Object=} opt_self The object in whose scope to invoke the listener.
* @return {!EventEmitter} A self reference.
*/
on(type, fn, opt_self) {
return this.addListener(type, fn, opt_self);
}
/**
* Removes a previously registered event listener.
* @param {string} type The type of event to unregister.
* @param {!Function} listenerFn The handler function to remove.
* @return {!EventEmitter} A self reference.
*/
removeListener(type, listenerFn) {
if (typeof type !== 'string' || typeof listenerFn !== 'function') {
throw TypeError('invalid args: expected (string, function), got ('
+ (typeof type) + ', ' + (typeof listenerFn) + ')');
}
let events = EVENTS.get(this);
if (!events) {
return this;
}
let listeners = events.get(type);
if (!listeners) {
return this;
}
let match;
for (let listener of listeners) {
if (listener.fn === listenerFn) {
match = listener;
break;
}
}
if (match) {
listeners.delete(match);
if (!listeners.size) {
events.delete(type);
}
}
return this;
}
/**
* Removes all listeners for a specific type of event. If no event is
* specified, all listeners across all types will be removed.
* @param {string=} opt_type The type of event to remove listeners from.
* @return {!EventEmitter} A self reference.
*/
removeAllListeners(opt_type) {
let events = EVENTS.get(this);
if (events) {
if (typeof opt_type === 'string') {
events.delete(opt_type);
} else {
EVENTS.delete(this);
}
}
return this;
}
}
// PUBLIC API
module.exports = {
EventEmitter: EventEmitter,
Listener: Listener
};