// 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>>} */ 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} 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 };