977 lines
28 KiB
JavaScript
977 lines
28 KiB
JavaScript
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
// Modified by the vm2 team to make this a standalone module to be loaded into the sandbox.
|
|
|
|
'use strict';
|
|
|
|
const host = fromhost;
|
|
|
|
const {
|
|
Boolean,
|
|
Error,
|
|
String,
|
|
Symbol
|
|
} = globalThis;
|
|
|
|
const ReflectApply = Reflect.apply;
|
|
const ReflectOwnKeys = Reflect.ownKeys;
|
|
|
|
const ErrorCaptureStackTrace = Error.captureStackTrace;
|
|
|
|
const NumberIsNaN = Number.isNaN;
|
|
|
|
const ObjectCreate = Object.create;
|
|
const ObjectDefineProperty = Object.defineProperty;
|
|
const ObjectDefineProperties = Object.defineProperties;
|
|
const ObjectGetPrototypeOf = Object.getPrototypeOf;
|
|
|
|
const SymbolFor = Symbol.for;
|
|
|
|
function uncurryThis(func) {
|
|
return (thiz, ...args) => ReflectApply(func, thiz, args);
|
|
}
|
|
|
|
const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf);
|
|
const ArrayPrototypeJoin = uncurryThis(Array.prototype.join);
|
|
const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice);
|
|
const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice);
|
|
const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift);
|
|
|
|
const kRejection = SymbolFor('nodejs.rejection');
|
|
|
|
function inspect(obj) {
|
|
return typeof obj === 'symbol' ? obj.toString() : `${obj}`;
|
|
}
|
|
|
|
function spliceOne(list, index) {
|
|
for (; index + 1 < list.length; index++)
|
|
list[index] = list[index + 1];
|
|
list.pop();
|
|
}
|
|
|
|
function assert(what, message) {
|
|
if (!what) throw new Error(message);
|
|
}
|
|
|
|
function E(key, msg, Base) {
|
|
return function NodeError(...args) {
|
|
const error = new Base();
|
|
const message = ReflectApply(msg, error, args);
|
|
ObjectDefineProperties(error, {
|
|
message: {
|
|
value: message,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true,
|
|
},
|
|
toString: {
|
|
value() {
|
|
return `${this.name} [${key}]: ${this.message}`;
|
|
},
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true,
|
|
},
|
|
});
|
|
error.code = key;
|
|
return error;
|
|
};
|
|
}
|
|
|
|
|
|
const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE',
|
|
(name, expected, actual) => {
|
|
assert(typeof name === 'string', "'name' must be a string");
|
|
if (!ArrayIsArray(expected)) {
|
|
expected = [expected];
|
|
}
|
|
|
|
let msg = 'The ';
|
|
if (StringPrototypeEndsWith(name, ' argument')) {
|
|
// For cases like 'first argument'
|
|
msg += `${name} `;
|
|
} else {
|
|
const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument';
|
|
msg += `"${name}" ${type} `;
|
|
}
|
|
msg += 'must be ';
|
|
|
|
const types = [];
|
|
const instances = [];
|
|
const other = [];
|
|
|
|
for (const value of expected) {
|
|
assert(typeof value === 'string',
|
|
'All expected entries have to be of type string');
|
|
if (ArrayPrototypeIncludes(kTypes, value)) {
|
|
ArrayPrototypePush(types, StringPrototypeToLowerCase(value));
|
|
} else if (RegExpPrototypeTest(classRegExp, value)) {
|
|
ArrayPrototypePush(instances, value);
|
|
} else {
|
|
assert(value !== 'object',
|
|
'The value "object" should be written as "Object"');
|
|
ArrayPrototypePush(other, value);
|
|
}
|
|
}
|
|
|
|
// Special handle `object` in case other instances are allowed to outline
|
|
// the differences between each other.
|
|
if (instances.length > 0) {
|
|
const pos = ArrayPrototypeIndexOf(types, 'object');
|
|
if (pos !== -1) {
|
|
ArrayPrototypeSplice(types, pos, 1);
|
|
ArrayPrototypePush(instances, 'Object');
|
|
}
|
|
}
|
|
|
|
if (types.length > 0) {
|
|
if (types.length > 2) {
|
|
const last = ArrayPrototypePop(types);
|
|
msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`;
|
|
} else if (types.length === 2) {
|
|
msg += `one of type ${types[0]} or ${types[1]}`;
|
|
} else {
|
|
msg += `of type ${types[0]}`;
|
|
}
|
|
if (instances.length > 0 || other.length > 0)
|
|
msg += ' or ';
|
|
}
|
|
|
|
if (instances.length > 0) {
|
|
if (instances.length > 2) {
|
|
const last = ArrayPrototypePop(instances);
|
|
msg +=
|
|
`an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`;
|
|
} else {
|
|
msg += `an instance of ${instances[0]}`;
|
|
if (instances.length === 2) {
|
|
msg += ` or ${instances[1]}`;
|
|
}
|
|
}
|
|
if (other.length > 0)
|
|
msg += ' or ';
|
|
}
|
|
|
|
if (other.length > 0) {
|
|
if (other.length > 2) {
|
|
const last = ArrayPrototypePop(other);
|
|
msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`;
|
|
} else if (other.length === 2) {
|
|
msg += `one of ${other[0]} or ${other[1]}`;
|
|
} else {
|
|
if (StringPrototypeToLowerCase(other[0]) !== other[0])
|
|
msg += 'an ';
|
|
msg += `${other[0]}`;
|
|
}
|
|
}
|
|
|
|
if (actual == null) {
|
|
msg += `. Received ${actual}`;
|
|
} else if (typeof actual === 'function' && actual.name) {
|
|
msg += `. Received function ${actual.name}`;
|
|
} else if (typeof actual === 'object') {
|
|
if (actual.constructor && actual.constructor.name) {
|
|
msg += `. Received an instance of ${actual.constructor.name}`;
|
|
} else {
|
|
const inspected = inspect(actual, { depth: -1 });
|
|
msg += `. Received ${inspected}`;
|
|
}
|
|
} else {
|
|
let inspected = inspect(actual, { colors: false });
|
|
if (inspected.length > 25)
|
|
inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`;
|
|
msg += `. Received type ${typeof actual} (${inspected})`;
|
|
}
|
|
return msg;
|
|
}, TypeError);
|
|
|
|
const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError);
|
|
|
|
const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE',
|
|
(str, range, input, replaceDefaultBoolean = false) => {
|
|
assert(range, 'Missing "range" argument');
|
|
let msg = replaceDefaultBoolean ? str :
|
|
`The value of "${str}" is out of range.`;
|
|
const received = inspect(input);
|
|
msg += ` It must be ${range}. Received ${received}`;
|
|
return msg;
|
|
}, RangeError);
|
|
|
|
const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR',
|
|
err => {
|
|
const msg = 'Unhandled error.';
|
|
if (err === undefined) return msg;
|
|
return `${msg} (${err})`;
|
|
}, Error);
|
|
|
|
function validateBoolean(value, name) {
|
|
if (typeof value !== 'boolean')
|
|
throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value);
|
|
}
|
|
|
|
function validateFunction(value, name) {
|
|
if (typeof value !== 'function')
|
|
throw new ERR_INVALID_ARG_TYPE(name, 'Function', value);
|
|
}
|
|
|
|
function validateString(value, name) {
|
|
if (typeof value !== 'string')
|
|
throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
|
|
}
|
|
|
|
function nc(cond, e) {
|
|
return cond === undefined || cond === null ? e : cond;
|
|
}
|
|
|
|
function oc(base, key) {
|
|
return base === undefined || base === null ? undefined : base[key];
|
|
}
|
|
|
|
const kCapture = Symbol('kCapture');
|
|
const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor');
|
|
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
|
|
const kMaxEventTargetListenersWarned =
|
|
Symbol('events.maxEventTargetListenersWarned');
|
|
|
|
const kIsEventTarget = SymbolFor('nodejs.event_target');
|
|
|
|
function isEventTarget(obj) {
|
|
return oc(oc(obj, 'constructor'), kIsEventTarget);
|
|
}
|
|
|
|
/**
|
|
* Creates a new `EventEmitter` instance.
|
|
* @param {{ captureRejections?: boolean; }} [opts]
|
|
* @constructs {EventEmitter}
|
|
*/
|
|
function EventEmitter(opts) {
|
|
EventEmitter.init.call(this, opts);
|
|
}
|
|
module.exports = EventEmitter;
|
|
if (host.once) module.exports.once = host.once;
|
|
if (host.on) module.exports.on = host.on;
|
|
if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners;
|
|
// Backwards-compat with node 0.10.x
|
|
EventEmitter.EventEmitter = EventEmitter;
|
|
|
|
EventEmitter.usingDomains = false;
|
|
|
|
EventEmitter.captureRejectionSymbol = kRejection;
|
|
ObjectDefineProperty(EventEmitter, 'captureRejections', {
|
|
get() {
|
|
return EventEmitter.prototype[kCapture];
|
|
},
|
|
set(value) {
|
|
validateBoolean(value, 'EventEmitter.captureRejections');
|
|
|
|
EventEmitter.prototype[kCapture] = value;
|
|
},
|
|
enumerable: true
|
|
});
|
|
|
|
if (host.EventEmitterReferencingAsyncResource) {
|
|
const kAsyncResource = Symbol('kAsyncResource');
|
|
const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource;
|
|
|
|
class EventEmitterAsyncResource extends EventEmitter {
|
|
/**
|
|
* @param {{
|
|
* name?: string,
|
|
* triggerAsyncId?: number,
|
|
* requireManualDestroy?: boolean,
|
|
* }} [options]
|
|
*/
|
|
constructor(options = undefined) {
|
|
let name;
|
|
if (typeof options === 'string') {
|
|
name = options;
|
|
options = undefined;
|
|
} else {
|
|
if (new.target === EventEmitterAsyncResource) {
|
|
validateString(oc(options, 'name'), 'options.name');
|
|
}
|
|
name = oc(options, 'name') || new.target.name;
|
|
}
|
|
super(options);
|
|
|
|
this[kAsyncResource] =
|
|
new EventEmitterReferencingAsyncResource(this, name, options);
|
|
}
|
|
|
|
/**
|
|
* @param {symbol,string} event
|
|
* @param {...any} args
|
|
* @returns {boolean}
|
|
*/
|
|
emit(event, ...args) {
|
|
if (this[kAsyncResource] === undefined)
|
|
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
|
|
const { asyncResource } = this;
|
|
ArrayPrototypeUnshift(args, super.emit, this, event);
|
|
return ReflectApply(asyncResource.runInAsyncScope, asyncResource,
|
|
args);
|
|
}
|
|
|
|
/**
|
|
* @returns {void}
|
|
*/
|
|
emitDestroy() {
|
|
if (this[kAsyncResource] === undefined)
|
|
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
|
|
this.asyncResource.emitDestroy();
|
|
}
|
|
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
get asyncId() {
|
|
if (this[kAsyncResource] === undefined)
|
|
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
|
|
return this.asyncResource.asyncId();
|
|
}
|
|
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
get triggerAsyncId() {
|
|
if (this[kAsyncResource] === undefined)
|
|
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
|
|
return this.asyncResource.triggerAsyncId();
|
|
}
|
|
|
|
/**
|
|
* @type {EventEmitterReferencingAsyncResource}
|
|
*/
|
|
get asyncResource() {
|
|
if (this[kAsyncResource] === undefined)
|
|
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
|
|
return this[kAsyncResource];
|
|
}
|
|
}
|
|
EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource;
|
|
}
|
|
|
|
EventEmitter.errorMonitor = kErrorMonitor;
|
|
|
|
// The default for captureRejections is false
|
|
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
|
|
value: false,
|
|
writable: true,
|
|
enumerable: false
|
|
});
|
|
|
|
EventEmitter.prototype._events = undefined;
|
|
EventEmitter.prototype._eventsCount = 0;
|
|
EventEmitter.prototype._maxListeners = undefined;
|
|
|
|
// By default EventEmitters will print a warning if more than 10 listeners are
|
|
// added to it. This is a useful default which helps finding memory leaks.
|
|
let defaultMaxListeners = 10;
|
|
|
|
function checkListener(listener) {
|
|
validateFunction(listener, 'listener');
|
|
}
|
|
|
|
ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', {
|
|
enumerable: true,
|
|
get: function() {
|
|
return defaultMaxListeners;
|
|
},
|
|
set: function(arg) {
|
|
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
|
|
throw new ERR_OUT_OF_RANGE('defaultMaxListeners',
|
|
'a non-negative number',
|
|
arg);
|
|
}
|
|
defaultMaxListeners = arg;
|
|
}
|
|
});
|
|
|
|
ObjectDefineProperties(EventEmitter, {
|
|
kMaxEventTargetListeners: {
|
|
value: kMaxEventTargetListeners,
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false,
|
|
},
|
|
kMaxEventTargetListenersWarned: {
|
|
value: kMaxEventTargetListenersWarned,
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false,
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Sets the max listeners.
|
|
* @param {number} n
|
|
* @param {EventTarget[] | EventEmitter[]} [eventTargets]
|
|
* @returns {void}
|
|
*/
|
|
EventEmitter.setMaxListeners =
|
|
function(n = defaultMaxListeners, ...eventTargets) {
|
|
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n))
|
|
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
|
|
if (eventTargets.length === 0) {
|
|
defaultMaxListeners = n;
|
|
} else {
|
|
for (let i = 0; i < eventTargets.length; i++) {
|
|
const target = eventTargets[i];
|
|
if (isEventTarget(target)) {
|
|
target[kMaxEventTargetListeners] = n;
|
|
target[kMaxEventTargetListenersWarned] = false;
|
|
} else if (typeof target.setMaxListeners === 'function') {
|
|
target.setMaxListeners(n);
|
|
} else {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'eventTargets',
|
|
['EventEmitter', 'EventTarget'],
|
|
target);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// If you're updating this function definition, please also update any
|
|
// re-definitions, such as the one in the Domain module (lib/domain.js).
|
|
EventEmitter.init = function(opts) {
|
|
|
|
if (this._events === undefined ||
|
|
this._events === ObjectGetPrototypeOf(this)._events) {
|
|
this._events = ObjectCreate(null);
|
|
this._eventsCount = 0;
|
|
}
|
|
|
|
this._maxListeners = this._maxListeners || undefined;
|
|
|
|
|
|
if (oc(opts, 'captureRejections')) {
|
|
validateBoolean(opts.captureRejections, 'options.captureRejections');
|
|
this[kCapture] = Boolean(opts.captureRejections);
|
|
} else {
|
|
// Assigning the kCapture property directly saves an expensive
|
|
// prototype lookup in a very sensitive hot path.
|
|
this[kCapture] = EventEmitter.prototype[kCapture];
|
|
}
|
|
};
|
|
|
|
function addCatch(that, promise, type, args) {
|
|
if (!that[kCapture]) {
|
|
return;
|
|
}
|
|
|
|
// Handle Promises/A+ spec, then could be a getter
|
|
// that throws on second use.
|
|
try {
|
|
const then = promise.then;
|
|
|
|
if (typeof then === 'function') {
|
|
then.call(promise, undefined, function(err) {
|
|
// The callback is called with nextTick to avoid a follow-up
|
|
// rejection from this promise.
|
|
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
|
|
});
|
|
}
|
|
} catch (err) {
|
|
that.emit('error', err);
|
|
}
|
|
}
|
|
|
|
function emitUnhandledRejectionOrErr(ee, err, type, args) {
|
|
if (typeof ee[kRejection] === 'function') {
|
|
ee[kRejection](err, type, ...args);
|
|
} else {
|
|
// We have to disable the capture rejections mechanism, otherwise
|
|
// we might end up in an infinite loop.
|
|
const prev = ee[kCapture];
|
|
|
|
// If the error handler throws, it is not catchable and it
|
|
// will end up in 'uncaughtException'. We restore the previous
|
|
// value of kCapture in case the uncaughtException is present
|
|
// and the exception is handled.
|
|
try {
|
|
ee[kCapture] = false;
|
|
ee.emit('error', err);
|
|
} finally {
|
|
ee[kCapture] = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increases the max listeners of the event emitter.
|
|
* @param {number} n
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
|
|
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
|
|
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
|
|
}
|
|
this._maxListeners = n;
|
|
return this;
|
|
};
|
|
|
|
function _getMaxListeners(that) {
|
|
if (that._maxListeners === undefined)
|
|
return EventEmitter.defaultMaxListeners;
|
|
return that._maxListeners;
|
|
}
|
|
|
|
/**
|
|
* Returns the current max listener value for the event emitter.
|
|
* @returns {number}
|
|
*/
|
|
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
|
|
return _getMaxListeners(this);
|
|
};
|
|
|
|
/**
|
|
* Synchronously calls each of the listeners registered
|
|
* for the event.
|
|
* @param {string | symbol} type
|
|
* @param {...any} [args]
|
|
* @returns {boolean}
|
|
*/
|
|
EventEmitter.prototype.emit = function emit(type, ...args) {
|
|
let doError = (type === 'error');
|
|
|
|
const events = this._events;
|
|
if (events !== undefined) {
|
|
if (doError && events[kErrorMonitor] !== undefined)
|
|
this.emit(kErrorMonitor, ...args);
|
|
doError = (doError && events.error === undefined);
|
|
} else if (!doError)
|
|
return false;
|
|
|
|
// If there is no 'error' event listener then throw.
|
|
if (doError) {
|
|
let er;
|
|
if (args.length > 0)
|
|
er = args[0];
|
|
if (er instanceof Error) {
|
|
try {
|
|
const capture = {};
|
|
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
|
|
} catch (e) {}
|
|
|
|
// Note: The comments on the `throw` lines are intentional, they show
|
|
// up in Node's output if this results in an unhandled exception.
|
|
throw er; // Unhandled 'error' event
|
|
}
|
|
|
|
let stringifiedEr;
|
|
try {
|
|
stringifiedEr = inspect(er);
|
|
} catch (e) {
|
|
stringifiedEr = er;
|
|
}
|
|
|
|
// At least give some kind of context to the user
|
|
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
|
|
err.context = er;
|
|
throw err; // Unhandled 'error' event
|
|
}
|
|
|
|
const handler = events[type];
|
|
|
|
if (handler === undefined)
|
|
return false;
|
|
|
|
if (typeof handler === 'function') {
|
|
const result = handler.apply(this, args);
|
|
|
|
// We check if result is undefined first because that
|
|
// is the most common case so we do not pay any perf
|
|
// penalty
|
|
if (result !== undefined && result !== null) {
|
|
addCatch(this, result, type, args);
|
|
}
|
|
} else {
|
|
const len = handler.length;
|
|
const listeners = arrayClone(handler);
|
|
for (let i = 0; i < len; ++i) {
|
|
const result = listeners[i].apply(this, args);
|
|
|
|
// We check if result is undefined first because that
|
|
// is the most common case so we do not pay any perf
|
|
// penalty.
|
|
// This code is duplicated because extracting it away
|
|
// would make it non-inlineable.
|
|
if (result !== undefined && result !== null) {
|
|
addCatch(this, result, type, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
function _addListener(target, type, listener, prepend) {
|
|
let m;
|
|
let events;
|
|
let existing;
|
|
|
|
checkListener(listener);
|
|
|
|
events = target._events;
|
|
if (events === undefined) {
|
|
events = target._events = ObjectCreate(null);
|
|
target._eventsCount = 0;
|
|
} else {
|
|
// To avoid recursion in the case that type === "newListener"! Before
|
|
// adding it to the listeners, first emit "newListener".
|
|
if (events.newListener !== undefined) {
|
|
target.emit('newListener', type,
|
|
nc(listener.listener, listener));
|
|
|
|
// Re-assign `events` because a newListener handler could have caused the
|
|
// this._events to be assigned to a new object
|
|
events = target._events;
|
|
}
|
|
existing = events[type];
|
|
}
|
|
|
|
if (existing === undefined) {
|
|
// Optimize the case of one listener. Don't need the extra array object.
|
|
events[type] = listener;
|
|
++target._eventsCount;
|
|
} else {
|
|
if (typeof existing === 'function') {
|
|
// Adding the second element, need to change to array.
|
|
existing = events[type] =
|
|
prepend ? [listener, existing] : [existing, listener];
|
|
// If we've already got an array, just append.
|
|
} else if (prepend) {
|
|
existing.unshift(listener);
|
|
} else {
|
|
existing.push(listener);
|
|
}
|
|
|
|
// Check for listener leak
|
|
m = _getMaxListeners(target);
|
|
if (m > 0 && existing.length > m && !existing.warned) {
|
|
existing.warned = true;
|
|
// No error code for this since it is a Warning
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
const w = new Error('Possible EventEmitter memory leak detected. ' +
|
|
`${existing.length} ${String(type)} listeners ` +
|
|
`added to ${inspect(target, { depth: -1 })}. Use ` +
|
|
'emitter.setMaxListeners() to increase limit');
|
|
w.name = 'MaxListenersExceededWarning';
|
|
w.emitter = target;
|
|
w.type = type;
|
|
w.count = existing.length;
|
|
process.emitWarning(w);
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* Adds a listener to the event emitter.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.addListener = function addListener(type, listener) {
|
|
return _addListener(this, type, listener, false);
|
|
};
|
|
|
|
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
|
|
|
/**
|
|
* Adds the `listener` function to the beginning of
|
|
* the listeners array.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.prependListener =
|
|
function prependListener(type, listener) {
|
|
return _addListener(this, type, listener, true);
|
|
};
|
|
|
|
function onceWrapper() {
|
|
if (!this.fired) {
|
|
this.target.removeListener(this.type, this.wrapFn);
|
|
this.fired = true;
|
|
if (arguments.length === 0)
|
|
return this.listener.call(this.target);
|
|
return this.listener.apply(this.target, arguments);
|
|
}
|
|
}
|
|
|
|
function _onceWrap(target, type, listener) {
|
|
const state = { fired: false, wrapFn: undefined, target, type, listener };
|
|
const wrapped = onceWrapper.bind(state);
|
|
wrapped.listener = listener;
|
|
state.wrapFn = wrapped;
|
|
return wrapped;
|
|
}
|
|
|
|
/**
|
|
* Adds a one-time `listener` function to the event emitter.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.once = function once(type, listener) {
|
|
checkListener(listener);
|
|
|
|
this.on(type, _onceWrap(this, type, listener));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adds a one-time `listener` function to the beginning of
|
|
* the listeners array.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.prependOnceListener =
|
|
function prependOnceListener(type, listener) {
|
|
checkListener(listener);
|
|
|
|
this.prependListener(type, _onceWrap(this, type, listener));
|
|
return this;
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes the specified `listener` from the listeners array.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.removeListener =
|
|
function removeListener(type, listener) {
|
|
checkListener(listener);
|
|
|
|
const events = this._events;
|
|
if (events === undefined)
|
|
return this;
|
|
|
|
const list = events[type];
|
|
if (list === undefined)
|
|
return this;
|
|
|
|
if (list === listener || list.listener === listener) {
|
|
if (--this._eventsCount === 0)
|
|
this._events = ObjectCreate(null);
|
|
else {
|
|
delete events[type];
|
|
if (events.removeListener)
|
|
this.emit('removeListener', type, list.listener || listener);
|
|
}
|
|
} else if (typeof list !== 'function') {
|
|
let position = -1;
|
|
|
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
if (list[i] === listener || list[i].listener === listener) {
|
|
position = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (position < 0)
|
|
return this;
|
|
|
|
if (position === 0)
|
|
list.shift();
|
|
else {
|
|
spliceOne(list, position);
|
|
}
|
|
|
|
if (list.length === 1)
|
|
events[type] = list[0];
|
|
|
|
if (events.removeListener !== undefined)
|
|
this.emit('removeListener', type, listener);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
|
|
|
/**
|
|
* Removes all listeners from the event emitter. (Only
|
|
* removes listeners for a specific event name if specified
|
|
* as `type`).
|
|
* @param {string | symbol} [type]
|
|
* @returns {EventEmitter}
|
|
*/
|
|
EventEmitter.prototype.removeAllListeners =
|
|
function removeAllListeners(type) {
|
|
const events = this._events;
|
|
if (events === undefined)
|
|
return this;
|
|
|
|
// Not listening for removeListener, no need to emit
|
|
if (events.removeListener === undefined) {
|
|
if (arguments.length === 0) {
|
|
this._events = ObjectCreate(null);
|
|
this._eventsCount = 0;
|
|
} else if (events[type] !== undefined) {
|
|
if (--this._eventsCount === 0)
|
|
this._events = ObjectCreate(null);
|
|
else
|
|
delete events[type];
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Emit removeListener for all listeners on all events
|
|
if (arguments.length === 0) {
|
|
for (const key of ReflectOwnKeys(events)) {
|
|
if (key === 'removeListener') continue;
|
|
this.removeAllListeners(key);
|
|
}
|
|
this.removeAllListeners('removeListener');
|
|
this._events = ObjectCreate(null);
|
|
this._eventsCount = 0;
|
|
return this;
|
|
}
|
|
|
|
const listeners = events[type];
|
|
|
|
if (typeof listeners === 'function') {
|
|
this.removeListener(type, listeners);
|
|
} else if (listeners !== undefined) {
|
|
// LIFO order
|
|
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
this.removeListener(type, listeners[i]);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
function _listeners(target, type, unwrap) {
|
|
const events = target._events;
|
|
|
|
if (events === undefined)
|
|
return [];
|
|
|
|
const evlistener = events[type];
|
|
if (evlistener === undefined)
|
|
return [];
|
|
|
|
if (typeof evlistener === 'function')
|
|
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
|
|
|
|
return unwrap ?
|
|
unwrapListeners(evlistener) : arrayClone(evlistener);
|
|
}
|
|
|
|
/**
|
|
* Returns a copy of the array of listeners for the event name
|
|
* specified as `type`.
|
|
* @param {string | symbol} type
|
|
* @returns {Function[]}
|
|
*/
|
|
EventEmitter.prototype.listeners = function listeners(type) {
|
|
return _listeners(this, type, true);
|
|
};
|
|
|
|
/**
|
|
* Returns a copy of the array of listeners and wrappers for
|
|
* the event name specified as `type`.
|
|
* @param {string | symbol} type
|
|
* @returns {Function[]}
|
|
*/
|
|
EventEmitter.prototype.rawListeners = function rawListeners(type) {
|
|
return _listeners(this, type, false);
|
|
};
|
|
|
|
/**
|
|
* Returns the number of listeners listening to the event name
|
|
* specified as `type`.
|
|
* @deprecated since v3.2.0
|
|
* @param {EventEmitter} emitter
|
|
* @param {string | symbol} type
|
|
* @returns {number}
|
|
*/
|
|
EventEmitter.listenerCount = function(emitter, type) {
|
|
if (typeof emitter.listenerCount === 'function') {
|
|
return emitter.listenerCount(type);
|
|
}
|
|
return emitter.listenerCount(type);
|
|
};
|
|
|
|
EventEmitter.prototype.listenerCount = listenerCount;
|
|
|
|
/**
|
|
* Returns the number of listeners listening to event name
|
|
* specified as `type`.
|
|
* @param {string | symbol} type
|
|
* @returns {number}
|
|
*/
|
|
function listenerCount(type) {
|
|
const events = this._events;
|
|
|
|
if (events !== undefined) {
|
|
const evlistener = events[type];
|
|
|
|
if (typeof evlistener === 'function') {
|
|
return 1;
|
|
} else if (evlistener !== undefined) {
|
|
return evlistener.length;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns an array listing the events for which
|
|
* the emitter has registered listeners.
|
|
* @returns {any[]}
|
|
*/
|
|
EventEmitter.prototype.eventNames = function eventNames() {
|
|
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
|
|
};
|
|
|
|
function arrayClone(arr) {
|
|
// At least since V8 8.3, this implementation is faster than the previous
|
|
// which always used a simple for-loop
|
|
switch (arr.length) {
|
|
case 2: return [arr[0], arr[1]];
|
|
case 3: return [arr[0], arr[1], arr[2]];
|
|
case 4: return [arr[0], arr[1], arr[2], arr[3]];
|
|
case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]];
|
|
case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
|
|
}
|
|
return ArrayPrototypeSlice(arr);
|
|
}
|
|
|
|
function unwrapListeners(arr) {
|
|
const ret = arrayClone(arr);
|
|
for (let i = 0; i < ret.length; ++i) {
|
|
const orig = ret[i].listener;
|
|
if (typeof orig === 'function')
|
|
ret[i] = orig;
|
|
}
|
|
return ret;
|
|
}
|