437 lines
14 KiB
JavaScript
Executable File
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;
|
|
};
|