211 lines
6.0 KiB
JavaScript
211 lines
6.0 KiB
JavaScript
// 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 acutal 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
|
|
};
|