tikiwiki/packages/tiki-pkg-casperjs/jerome-breton/casperjs/modules/events.js
2023-11-20 20:52:04 +00:00

437 lines
14 KiB
JavaScript
Executable File

/*!
* Casper is a navigation utility for PhantomJS.
*
* Documentation: http://casperjs.org/
* Repository: http://github.com/casperjs/casperjs
*
* Copyright (c) 2011-2012 Nicolas Perriault
*
* Part of source code is 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.
*
*/
/*global CasperError*/
var isArray = Array.isArray;
var EventEmitter = function EventEmitter() {
"use strict";
this._filters = {};
};
exports.EventEmitter = EventEmitter;
var defaultMaxListeners = 10;
/**
* By default EventEmitters will print a warning if more than 10 listeners
* are added for a particular event. This is a useful default that helps finding memory leaks.
* Obviously, not all events should be limited to just 10 listeners.
* The emitter.setMaxListeners() method allows the limit to be modified for
* this specific EventEmitter instance. The value can be set to Infinity (or 0)
* to indicate an unlimited number of listeners.
*
* @param Integer nb Max Number of Listeners
*/
EventEmitter.prototype.setMaxListeners = function(nb) {
"use strict";
if (!this._events) {
this._events = {};
}
this._maxListeners = nb;
};
/**
* Synchronously calls each of the listeners registered for event,
* in the order they were registered, passing the supplied arguments to each.
* Returns true if event had listeners, false otherwise
*
* @param Array args... The rest of arguments passed to fn
* @return Boolean
* @throws {Error} If invokation failed.
*/
EventEmitter.prototype.emit = function emit() {
"use strict";
var type = arguments[0];
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events || !this._events.error ||
isArray(this._events.error) && !this._events.error.length) {
if (arguments[1] instanceof Error) {
throw arguments[1]; // Unhandled 'error' event
} else {
throw new CasperError("Uncaught, unspecified 'error' event.");
}
}
}
if (!this._events) {
return false;
}
var handler = this._events[type];
if (!handler) {
return false;
}
if (typeof handler === 'function') {
try {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) {
args[i - 1] = arguments[i];
}
handler.apply(this, args);
}
} catch (err) {
this.emit('event.error', err);
}
return true;
} else if (isArray(handler)) {
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) {
args[i - 1] = arguments[i];
}
var listeners = handler.slice();
for (var i = 0, l = listeners.length; i < l; i++) {
listeners[i].apply(this, args);
}
return true;
} else {
return false;
}
};
/**
* Adds the listener function to the end of the listeners array for the specified event.
* No checks are made to see if the listener has already been added.
* Multiple calls passing the same combination of event and listener
* will result in the listener being added, and called, multiple times.
* EventEmitter is defined in src/node_events.cc
* EventEmitter.prototype.emit() is also defined there.
*
* @param String type An event name.
* @param function filterFn An options callback to apply on event.
* @return EventEmitter A reference to the EventEmitter so calls can be chained
* @throws {CasperError} If invokation failed.
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
"use strict";
if (typeof listener !== 'function') {
throw new CasperError('addListener only takes instances of Function');
}
if (!this._events) {
this._events = {};
}
// To avoid recursion in the case that type == "newListeners"! Before
// adding it to the listeners, first emit "newListeners".
this.emit('newListener', type, listener);
if (!this._events[type]) {
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
} else if (isArray(this._events[type])) {
// If we've already got an array, just append.
this._events[type][ type === 'fail' ? 'unshift' : 'push'](listener);
// Check for listener leak
if (!this._events[type].warned) {
var m;
if (typeof this._maxListeners !== 'undefined') {
m = this._maxListeners;
} else {
m = defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('Warning: Possible EventEmitter memory leak detected. ' +
this._events[type].length +
' listeners of type ' + type + ' added. Use casper.setMaxListeners() to increase' +
' the limit if you are expecting the current limit of ' + m + ' to be exceeded.' +
' Please refer to online documentation for more information.');
console.trace();
}
}
} else {
// Adding the second element, need to change to array.
this._events[type] = type === 'fail' ? [listener, this._events[type]] : [this._events[type], listener];
}
return this;
};
/**
* Alias for emitter.addListener(type, listener).
*
* @param String type An event name.
* @param function filterFn An options callback to apply on event.
* @return EventEmitter A reference to the EventEmitter so calls can be chained
* @throws {CasperError} If invokation failed.
*/
EventEmitter.prototype.prependListener = function prependListener(type, listener) {
"use strict";
if (typeof listener !== 'function') {
throw new CasperError('addListener only takes instances of Function');
}
if (!this._events) {
this._events = {};
}
// To avoid recursion in the case that type == "newListeners"! Before
// adding it to the listeners, first emit "newListeners".
this.emit('newListener', type, listener);
if (!this._events[type]) {
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
} else if (isArray(this._events[type])) {
// If we've already got an array, just append.
this._events[type].unshift(listener);
// Check for listener leak
if (!this._events[type].warned) {
var m;
if (typeof this._maxListeners !== 'undefined') {
m = this._maxListeners;
} else {
m = defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('Warning: Possible EventEmitter memory leak detected. ' +
this._events[type].length +
' listeners of type ' + type + ' added. Use casper.setMaxListeners() to increase' +
' the limit if you are expecting the current limit of ' + m + ' to be exceeded.' +
' Please refer to online documentation for more information.');
console.trace();
}
}
} else {
// Adding the second element, need to change to array.
this._events[type] = [listener, this._events[type]];
}
return this;
};
/**
* Alias for emitter.addListener(type, listener).
*
* @param String type An event name.
* @param function filterFn An options callback to apply on event.
* @return EventEmitter A reference to the EventEmitter so calls can be chained
*/
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/**
* Adds a one time listener function for the event.
* This listener is invoked only the next time event is triggered, after which it is removed.
*
* @param String type An event name.
* @param function filterFn An options callback to apply on event.
* @return EventEmitter A reference to the EventEmitter so calls can be chained
* @throws {CasperError} If invokation failed.
*/
EventEmitter.prototype.once = function once(type, listener) {
"use strict";
if (typeof listener !== 'function') {
throw new CasperError('.once only takes instances of Function');
}
var self = this;
var g = function g() {
self.removeListener(type, g);
listener.apply(this, arguments);
};
g.listener = listener;
self.on(type, g);
return this;
};
/**
* Removes the specified listener from the listener array for the specified event.
*
* @param String type An event name
* @param function filterFn An options callback to apply on event
* @return EventEmitter A reference to the EventEmitter so calls can be chained
* @throws {CasperError} If invokation failed.
*/
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
"use strict";
if (typeof listener !== 'function') {
throw new CasperError('removeListener only takes instances of Function');
}
// does not use listeners(), so no side effect of creating _events[type]
if (!this._events || !this._events[type]) {
return this;
}
var list = this._events[type];
if (isArray(list)) {
var position = -1;
for (var i = 0, length = list.length; i < length; i++) {
if (list[i] === listener ||
list[i].listener && list[i].listener === listener) {
position = i;
break;
}
}
if (position < 0) {
return this;
}
list.splice(position, 1);
if (list.length === 0) {
delete this._events[type];
}
} else if (list === listener ||
list.listener && list.listener === listener) {
delete this._events[type];
}
return this;
};
/**
* Removes all listeners, or those of the specified event.
*
* @param String type An event name
* @return EventEmitter A reference to the EventEmitter so calls can be chained
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
"use strict";
if (arguments.length === 0) {
this._events = {};
return this;
}
// does not use listeners(), so no side effect of creating _events[type]
if (type && this._events && this._events[type]) {
this._events[type] = null;
}
return this;
};
/**
* get callbacks listener.
*
* @param String type An event name
* @return mixed
*/
EventEmitter.prototype.listeners = function listeners(type) {
"use strict";
if (!this._events) {
this._events = {};
}
if (!this._events[type]) {
this._events[type] = [];
}
if (!isArray(this._events[type])) {
this._events[type] = [this._events[type]];
}
return this._events[type];
};
/**
* Trigger filter attached to an event and return value(s).
*
* @param String type An event name
* @param Array args... The rest of arguments passed to fn
* @return mixed
*/
EventEmitter.prototype.filter = function filter() {
"use strict";
var type = arguments[0], res;
if (!this._filters) {
this._filters = {};
}
var _filter = this._filters[type];
if (typeof _filter === 'function') {
res = _filter.apply(this, Array.prototype.slice.call(arguments, 1));
}
this.emit(Array.prototype.slice.call(arguments, 0).concat([res]));
return res;
};
/**
* Remove all filters attached to events or only filters attached to a specific event.
*
* @param String type An event name
* @return EventEmitter A reference to the EventEmitter so calls can be chained
*/
EventEmitter.prototype.removeAllFilters = function removeAllFilters(type) {
"use strict";
if (arguments.length === 0) {
this._filters = {};
return this;
}
if (type && this._filters && this._filters[type]) {
this._filters[type] = null;
}
return this;
};
/**
* attaches a filter to an event.
*
* @param String type An event name
* @param function filterFn An options callback to apply on event
* @return Boolean
* @throws {CasperError} If invokation failed
*/
EventEmitter.prototype.setFilter = function setFilter(type, filterFn) {
"use strict";
if (!this._filters) {
this._filters = {};
}
if (typeof filterFn !== 'function') {
throw new CasperError('setFilter only takes instances of Function');
}
if (!this._filters[type]) {
this._filters[type] = filterFn;
return true;
}
// TODO: process multiple filters? in which order? disallow?
return false;
};