fortress
Version:
Fortress is designed for the defense of client side applications.
1,300 lines (1,119 loc) • 33.4 kB
JavaScript
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Fortress=e():"undefined"!=typeof global?global.Fortress=e():"undefined"!=typeof self&&(self.Fortress=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var EventEmitter = require('eventemitter3')
, BaseImage = require('./image')
, slice = Array.prototype.slice
, iframe = require('frames');
/**
* Representation of a single container.
*
* Options:
*
* - retries; When an error occurs, how many times should we attempt to restart
* the code before we automatically stop() the container.
* - stop; Stop the container when an error occurs.
* - timeout; How long can a ping packet timeout before we assume that the
* container has died and should be restarted.
*
* @constructor
* @param {Element} mount The element we should attach to.
* @param {String} id A unique id for this container.
* @param {String} code The actual that needs to run within the sandbox.
* @param {Object} options Container configuration.
* @api private
*/
function Container(mount, id, code, options) {
if ('object' === typeof code) {
options = code;
code = null;
}
options = options || {};
this.i = iframe(mount, id); // The generated iframe.
this.mount = mount; // Mount point of the container.
this.console = []; // Historic console.* output.
this.setTimeout = {}; // Stores our setTimeout references.
this.id = id; // Unique id.
this.readyState = Container.CLOSED; // The readyState of the container.
this.created = +new Date(); // Creation EPOCH.
this.started = null; // Start EPOCH.
this.retries = 'retries' in options // How many times should we reload
? +options.retries || 3
: 3;
this.timeout = 'timeout' in options // Ping timeout before we reboot.
? +options.timeout || 1050
: 1050;
//
// Initialise as an EventEmitter before we start loading in the code.
//
EventEmitter.call(this);
//
// Optional code to load in the container and start it directly.
//
if (code) this.load(code).start();
}
//
// The container inherits from the EventEmitter3.
//
Container.prototype = new EventEmitter();
Container.prototype.constructor = Container;
/**
* Internal readyStates for the container.
*
* @type {Number}
* @private
*/
Container.CLOSING = 1;
Container.OPENING = 2;
Container.CLOSED = 3;
Container.OPEN = 4;
/**
* Start a new ping timeout.
*
* @api private
*/
Container.prototype.ping = function ping() {
if (this.setTimeout.pong) clearTimeout(this.setTimeout.pong);
var self = this;
this.setTimeout.pong = setTimeout(function pong() {
self.onmessage({
type: 'error',
scope: 'iframe.timeout',
args: [
'the iframe is no longer responding with ping packets'
]
});
}, this.timeout);
return this;
};
/**
* Retry loading the code in the iframe. The container will be restored to a new
* state or completely reset the iframe.
*
* @api private
*/
Container.prototype.retry = function retry() {
switch (this.retries) {
//
// This is our last attempt, we've tried to have the iframe restart the code
// it self, so for our last attempt we're going to completely create a new
// iframe and re-compile the code for it.
//
case 1:
this.stop(); // Clear old iframe and nuke it's references
this.i = iframe(this.mount, this.id);
this.load(this.image.source).start();
break;
//
// No more attempts left.
//
case 0:
this.stop();
this.emit('end');
return;
//
// By starting and stopping (and there for removing and adding it back to
// the DOM) the iframe will reload it's HTML and the added code.
//
default:
this.stop().start();
break;
}
this.emit('retry', this.retries);
this.retries--;
return this;
};
/**
* Inspect the container to get some useful statistics about it and it's health.
*
* @returns {Object}
* @api public
*/
Container.prototype.inspect = function inspect() {
if (!this.i.attached()) return {};
var date = new Date()
, memory;
//
// Try to read out the `performance` information from the iframe.
//
if (this.i.window() && this.i.window().performance) {
memory = this.i.window().performance.memory;
}
memory = memory || {};
return {
readyState: this.readyState,
retries: this.retries,
uptime: this.started ? (+date) - this.started : 0,
date: date,
memory: {
limit: memory.jsHeapSizeLimit || 0,
total: memory.totalJSHeapSize || 0,
used: memory.usedJSHeapSize || 0
}
};
};
/**
* Parse and process incoming messages from the iframe. The incoming messages
* should be objects that have a `type` property. The main reason why we have
* this as a separate method is to give us flexibility. We are leveraging iframes
* at the moment, but in the future we might want to leverage WebWorkers for the
* sand boxing of JavaScript.
*
* @param {Object} packet The incoming message.
* @returns {Boolean} Message was handled y/n.
* @api private
*/
Container.prototype.onmessage = function onmessage(packet) {
if ('object' !== typeof packet) return false;
if (!('type' in packet)) return false;
packet.args = packet.args || [];
switch (packet.type) {
//
// The code in the iframe used the `console` method.
//
case 'console':
this.console.push({
scope: packet.scope,
epoch: +new Date(),
args: packet.args
});
if (packet.attach) {
this.emit.apply(this, ['attach::'+ packet.scope].concat(packet.args));
this.emit.apply(this, ['attach', packet.scope].concat(packet.args));
}
break;
//
// An error happened in the iframe, process it.
//
case 'error':
var failure = packet.args[0].stack ? packet.args[0] : new Error(packet.args[0]);
failure.scope = packet.scope || 'generic';
this.emit('error', failure);
this.retry();
break;
//
// The iframe and it's code has been loaded.
//
case 'load':
if (this.readyState !== Container.OPEN) {
this.readyState = Container.OPEN;
this.emit('start');
}
break;
//
// The iframe is unloading, attaching
//
case 'unload':
if (this.readyState !== Container.CLOSED) {
this.readyState = Container.CLOSED;
this.emit('stop');
}
break;
//
// We've received a ping response from the iframe, so we know it's still
// running as intended.
//
case 'ping':
this.ping();
this.emit('ping');
break;
//
// Handle unknown package types by just returning false after we've emitted
// it as an `regular` message.
//
default:
this.emit.apply(this, ['message'].concat(packet.args));
return false;
}
return true;
};
/**
* Small wrapper around sandbox evaluation.
*
* @param {String} cmd The command to executed in the iframe.
* @param {Function} fn Callback
* @api public
*/
Container.prototype.eval = function evil(cmd, fn) {
var data;
try {
data = this.i.add().window().eval(cmd);
} catch (e) {
return fn(e);
}
return fn(undefined, data);
};
/**
* Start the container.
*
* @returns {Container}
* @api public
*/
Container.prototype.start = function start() {
this.readyState = Container.OPENING;
var self = this;
/**
* Simple argument proxy.
*
* @api private
*/
function onmessage() {
self.onmessage.apply(self, arguments);
}
//
// Code loading is an sync process, but this COULD cause huge stack traces
// and really odd feedback loops in the stack trace. So we deliberately want
// to destroy the stack trace here.
//
this.setTimeout.start = setTimeout(function async() {
var doc = self.i.document();
//
// No doc.open, the iframe has already been destroyed!
//
if (!doc.open || !self.i) return;
//
// We need to open and close the iframe in order for it to trigger an onload
// event. Certain scripts might require in order to execute properly.
//
doc.open();
doc.write('<!doctype html>'); // Smallest, valid HTML5 document possible.
//
// Introduce our messaging variable, this needs to be done before we eval
// our code. If we set this value before the setTimeout, it doesn't work in
// Opera due to reasons.
//
self.i.window()[self.id] = onmessage;
self.eval(self.image.toString(), function evil(err) {
if (err) return self.onmessage({
type: 'error',
scope: 'iframe.eval',
args: [ err ]
});
});
//
// If executing the code results to an error we could actually be stopping
// and removing the iframe from the source before we're able to close it.
// This is because executing the code inside the iframe is actually an sync
// operation.
//
if (doc.close) doc.close();
}, 0);
//
// We can only write to the iframe if it's actually in the DOM. The `i.add()`
// method ensures that the iframe is added to the DOM.
//
this.i.add();
this.started = +new Date();
return this;
};
/**
* Stop running the code inside the container.
*
* @returns {Container}
* @api private
*/
Container.prototype.stop = function stop() {
if (this.readyState !== Container.CLOSED && this.readyState !== Container.CLOSING) {
this.readyState = Container.CLOSING;
}
this.i.remove();
//
// Opera doesn't support unload events. So adding an listener inside the
// iframe for `unload` doesn't work. This is the only way around it.
//
this.onmessage({ type: 'unload' });
//
// It's super important that this removed AFTER we've cleaned up all other
// references as we might need to communicate back to our container when we
// are unloading or when an `unload` event causes an error.
//
this.i.window()[this.id] = null;
//
// Clear the timeouts.
//
for (var timeout in this.setTimeout) {
clearTimeout(this.setTimeout[timeout]);
delete this.setTimeout[timeout];
}
return this;
};
/**
* Load the given code as image on to the container.
*
* @param {String} code The code that should run on the container.
* @returns {Container}
* @api public
*/
Container.prototype.load = function load(code) {
this.image = new BaseImage(this.id, code);
return this;
};
/**
* Completely destroy the given container and ensure that all references are
* nuked so we can clean up as much memory as possible.
*
* @returns {Container}
* @api private
*/
Container.prototype.destroy = function destroy() {
if (!this.i) return this;
this.stop();
//
// Remove all possible references to release as much memory as possible.
//
this.mount = this.image = this.id = this.i = this.created = null;
this.console.length = 0;
this.removeAllListeners();
return this;
};
//
// Expose the module.
//
module.exports = Container;
},{"./image":2,"eventemitter3":5,"frames":6}],2:[function(require,module,exports){
'use strict';
/**
* The BaseImage that is loaded on to the container.
*
* @constructor
* @param {String} id The id of the container.
* @param {String} source The actual code.
* @api private
*/
function BaseImage(id, source) {
if (!(this instanceof BaseImage)) return new BaseImage(id, source);
this.compiled = null;
this.source = source;
this.id = id;
}
/**
* Assume that the source of the BaseImage is loaded using toString() so it will be
* automatically transformed when the BaseImage instance is concatenated or added to
* the DOM.
*
* @returns {String}
* @api public
*/
BaseImage.prototype.toString = function toString() {
if (this.compiled) return this.compiled;
return this.compiled = this.transform();
};
/**
* Apply source code transformations to the code so it can work inside an
* iframe.
*
* @TODO allow custom code transformations.
* @returns {String}
* @api private
*/
BaseImage.prototype.transform = function transform() {
var code = ('('+ (function fort(global) {
//
// When you toString a function which is created while in strict mode,
// firefox will add "use strict"; to the body of the function. Chrome leaves
// the source intact. Knowing this, we cannot blindly assume that we can
// inject code after the first opening bracked `{`.
//
this.fort();
/**
* Simple helper function to do nothing.
*
* @type {Function}
* @api private
*/
function noop() { /* I do nothing useful */ }
/**
* AddListener polyfill
*
* @param {Mixed} thing What ever we want to listen on.
* @param {String} evt The event we're listening for.
* @param {Function} fn The function that gets executed.
* @api private
*/
function on(thing, evt, fn) {
if (thing.attachEvent) {
thing.attachEvent('on'+ evt, fn);
} else if (thing.addEventListener) {
thing.addEventListener(evt, fn, false);
}
return { on: on };
}
//
// Force the same domain as our 'root' script.
//
try { document.domain = '_fortress_domain_'; }
catch (e) { /* FireFox 26 throws an Security error for this as we use eval */ }
//
// Prevent common iframe detection scripts that do frame busting.
//
try { global.top = global.self = global.parent = global; }
catch (e) { /* Damn, read-only */ }
//
// Add a error listener. Adding it on the iframe it self doesn't make it
// bubble up to the container. So in order to capture errors and notifying
// the container we need to add a `window.onerror` listener inside the
// iframe it self.
// @TODO add proper stack trace tool here?
//
global.onerror = function onerror() {
var a = Array.prototype.slice.call(arguments, 0);
this._fortress_id_({ type: 'error', scope: 'window.onerror', args: a });
return true;
};
//
// Eliminate the browsers blocking dialogs, we're in a iframe not a browser.
//
var blocking = ['alert', 'prompt', 'confirm', 'print', 'open'];
for (var i = 0; i < blocking.length; i++) {
try { global[blocking[i]] = noop; }
catch (e) {}
}
//
// Override the build-in console.log so we can transport the logging messages to
// the actual page.
//
// @see https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
// for the minimum supported console.* methods.
//
var methods = [
'debug', 'error', 'info', 'log', 'warn', 'dir', 'dirxml', 'table', 'trace'
, 'assert', 'count', 'markTimeline', 'profile', 'profileEnd', 'time'
, 'timeEnd', 'timeStamp', 'timeline', 'timelineEnd', 'group'
, 'groupCollapsed', 'groupEnd', 'clear', 'select', 'exception'
, 'isIndependentlyComposed'
], fconsole = typeof console !== 'undefined' ? console : {};
global.console = {};
/**
* Helper method to polyfill our global console method so we can proxy it's
* usage to the
*
* @param {String} method The console method we want to polyfill.
* @api private
*/
function polyconsole(method) {
var attach = { debug: 1, error: 1, log: 1, warn: 1 };
//
// Ensure that this host environment always has working console.
//
global.console[method] = function polyfilled() {
var args = Array.prototype.slice.call(arguments, 0);
//
// If the host supports this given method natively, execute it.
//
if (method in fconsole) fconsole[method].apply(fconsole, args);
//
// Proxy messages to the container.
//
this._fortress_id_({
attach: method in attach,
type: 'console',
scope: method,
args: args
});
};
}
for (i = 0; i < methods.length; i++) {
polyconsole(methods[i]);
}
//
// The setInterval allows us to detect if the iframe is still running of if
// it has crashed or maybe it's just freezing up. We will be missing pings
// or get extremely slow responses. Browsers will kill long running scripts
// after 5 seconds of freezing:
//
// http://www.nczonline.net/blog/2009/01/05/what-determines-that-a-script-is-long-running/
//
setInterval(function ping() {
this._fortress_id_({ type: 'ping' });
}, 1000);
//
// Add load listeners so we know when the iframe is alive and working.
//
on(global, 'load', function () {
this._fortress_id_({ type: 'load' });
});
//
// Ideally we load this code after our `load` event so we know that our own
// bootstrapping has been loaded completely. But the problem is that we
// actually cause full browser crashes in chrome when we execute this.
//
var self = this;
setTimeout(function timeout() {
try { self.fort(); }
catch (e) {
this._fortress_id_({ type: 'error', scope: 'iframe.start', args: [e] });
}
}, 0);
})+').call({}, this)');
//
// Replace our "template tags" with the actual content.
//
return code
.replace(/_fortress_domain_/g, document.domain)
.replace(/this\._fortress_id_/g, this.id)
.replace(/this\.fort\(\);/g, 'this.fort=function fort() {'+ this.source +'};');
};
module.exports = BaseImage;
},{}],3:[function(require,module,exports){
'use strict';
var EventEmitter = require('eventemitter3')
, Container = require('./container')
, BaseImage = require('./image')
, iframe = require('frames');
/**
* Fortress: Container and Image management for front-end code.
*
* @constructor
* @param {Object} options Fortress configuration
* @api private
*/
function Fortress(options) {
if (!(this instanceof Fortress)) return new Fortress(options);
options = options || {};
//
// Create a small dedicated container that houses all our iframes. This might
// add an extra DOM node to the page in addition to each iframe but it will
// ultimately result in a cleaner DOM as everything is nicely tucked away.
//
var scripts = document.getElementsByTagName('script')
, append = scripts[scripts.length - 1] || document.body
, div = document.createElement('div');
append.parentNode.insertBefore(div, append);
this.global = (function () { return this; })() || window;
this.containers = {};
this.mount = div;
scripts = null;
EventEmitter.call(this);
}
//
// Fortress inherits from EventEmitter3.
//
Fortress.prototype = new EventEmitter();
/**
* Detect HTMLfile support in Internet Explorer. This might be used for more
* advanced sand boxing in IE.
*
* @type {Boolean}
* @api private
*/
Fortress.prototype.htmlfile = false;
try { Fortress.prototype.htmlfile = !!new ActiveXObject('htmlfile'); }
catch (e) {}
/**
* Detect the current globals that are loaded in to this page. This way we can
* see if we are leaking data.
*
* @param {Array} old Optional array with previous or known leaks.
* @returns {Array} Names of the leaked globals.
* @api private
*/
Fortress.prototype.globals = function globals(old) {
var i = iframe(this.mount, +new Date())
, windoh = i.add().window()
, global = this.global
, result = [];
i.remove();
//
// Detect the globals and return them.
//
for (var key in global) {
var introduced = !(key in windoh);
//
// We've been given an array, so we should use that as the source of previous
// and acknowledged leaks and only return an array that contains newly
// introduced leaks.
//
if (introduced) {
if (old && old.length && !!~old.indexOf(key)) continue;
result.push(key);
}
}
return result;
};
/**
* List all active containers.
*
* @returns {Array} Active containers.
* @api public
*/
Fortress.prototype.all = function all() {
var everything = [];
for (var id in this.containers) {
everything.push(this.containers[id]);
}
return everything;
};
/**
* Generate an unique, unknown id that we can use for our container storage.
*
* @returns {String}
* @api private
*/
Fortress.prototype.id = function id() {
for (var i = 0, generated = []; i < 4; i++) {
generated.push(Math.random().toString(36).substring(2));
}
generated = 'fortress_'+ generated.join('_');
//
// Ensure that we didn't generate a pre-existing id, if we did, generate
// another id.
//
if (generated in this.containers) return this.id();
return generated;
};
/**
* Create a new container.
*
* @param {String} code
* @param {Object} options Options for the container
* @returns {Container}
* @api public
*/
Fortress.prototype.create = function create(code, options) {
var container = new Container(this.mount, this.id(), code, options);
this.containers[container.id] = container;
return container;
};
/**
* Get a container based on it's unique id.
*
* @param {String} id The container id.
* @returns {Container}
* @api public
*/
Fortress.prototype.get = function get(id) {
return this.containers[id];
};
/**
* Inspect a running Container in order to get more detailed information about
* the process and the state of the container.
*
* @param {String} id The container id.
* @api public
*/
Fortress.prototype.inspect = Fortress.prototype.top = function inspect(id) {
var container = this.get(id);
if (!container) return {};
return container.inspect();
};
/**
* Start the container with the given id.
*
* @param {String} id The container id.
* @api public
*/
Fortress.prototype.start = function start(id) {
var container = this.get(id);
if (!container) return this;
container.start();
return this;
};
/**
* Stop a running container, this does not fully destroy the container. It
* merely stops it from running. Stopping an container will cause the container
* to start from the beginning again once it's started. This is not a pause
* function.
*
* @param {String} id The container id.
* @api public
*/
Fortress.prototype.stop = function stop(id) {
var container = this.get(id);
if (!container) return this;
container.stop();
return this;
};
/**
* Restart a container. Basically, just a start and stop.
*
* @param {String} id The container id.
* @api public
*/
Fortress.prototype.restart = function restart(id) {
var container = this.get(id);
if (!container) return this;
container.stop().start();
return this;
};
/**
* Completely remove and shutdown the given container id.
*
* @param {String} id The container id.
* @api public
*/
Fortress.prototype.kill = function kill(id) {
var container = this.get(id);
if (!container) return this;
container.destroy();
delete this.containers[id];
return this;
};
/**
* Start streaming logging information and cached logs.
*
* @param {String} id The container id.
* @param {String} method The log method name.
* @param {Function} fn The function that needs to be called for each stream.
* @api public
*/
Fortress.prototype.attach = function attach(id, method, fn) {
var container = this.get(id);
if (!container) return this;
if ('function' === typeof method) {
fn = method;
method = 'attach';
} else {
method += 'attach::'+ method;
}
container.on(method, fn);
return this;
};
/**
* Stop streaming logging information and cached logs.
*
* @param {String} id The container id.
* @param {String} method The log method name.
* @param {Function} fn The function that needs to be called for each stream.
* @api public
*/
Fortress.prototype.detach = function detach(id, method, fn) {
var container = this.get(id);
if (!container) return this;
if ('function' === typeof method) {
fn = method;
method = 'attach';
} else {
method += 'attach::'+ method;
}
if (!fn) container.removeAllListeners(method);
else container.on(method, fn);
return this;
};
/**
* Destroy all active containers and clean up all references. We expect no more
* further calls to this Fortress instance.
*
* @api public
*/
Fortress.prototype.destroy = function destroy() {
for (var id in this.containers) {
this.kill(id);
}
this.mount.parentNode.removeChild(this.mount);
this.global = this.mount = this.containers = null;
};
/**
* Prepare a file or function to be loaded in to a Fortress based Container.
* When the transfer boolean is set we assume that you want to load pass the
* result of to a function or assign it a variable from the server to the client
* side:
*
* ```
* <script>
* var code = <%- Fortress.stringify(code, true) %>
* </script>
* ```
*
* @param {String|Function} code The code that needs to be transformed.
* @param {Boolean} transfer Prepare the code for transfer.
* @returns {String}
* @api public
*/
Fortress.stringify = function stringify(code, transfer) {
if ('function' === typeof code) {
//
// We've been given a pure function, so we need to wrap it a little bit
// after we've done a `toString` for the source retrieval so the function
// will automatically execute when it's activated.
//
code = '('+ code.toString() +'())';
} else {
//
// We've been given a string, so we're going to assume that it's path to file
// that should be included instead.
//
code = require('fs').readFileSync(code, 'utf-8');
}
return transfer ? JSON.stringify(code) : code;
};
//
// Expose our Container and Image so it can be extended by third party.
//
Fortress.Container = Container;
Fortress.Image = BaseImage;
//
// Expose the module.
//
module.exports = Fortress;
},{"./container":1,"./image":2,"eventemitter3":5,"frames":6,"fs":4}],4:[function(require,module,exports){
},{}],5:[function(require,module,exports){
'use strict';
/**
* Minimal EventEmitter interface that is molded against the Node.js
* EventEmitter interface.
*
* @constructor
* @api public
*/
function EventEmitter() {
this._events = {};
}
/**
* Return a list of assigned event listeners.
*
* @param {String} event The events that should be listed.
* @returns {Array}
* @api public
*/
EventEmitter.prototype.listeners = function listeners(event) {
return Array.apply(this, this._events[event] || []);
};
/**
* Emit an event to all registered event listeners.
*
* @param {String} event The name of the event.
* @returns {Boolean} Indication if we've emitted an event.
* @api public
*/
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
if (!this._events || !this._events[event]) return false;
var listeners = this._events[event]
, length = listeners.length
, handler = listeners[0]
, len = arguments.length
, args
, i;
if (1 === length) {
switch (len) {
case 1:
handler.call(this);
break;
case 2:
handler.call(this, a1);
break;
case 3:
handler.call(this, a1, a2);
break;
case 4:
handler.call(this, a1, a2, a3);
break;
case 5:
handler.call(this, a1, a2, a3, a4);
break;
case 6:
handler.call(this, a1, a2, a3, a4, a5);
break;
default:
for (i = 1, args = new Array(len -1); i < len; i++) {
args[i - 1] = arguments[i];
}
handler.apply(this, args);
}
if (handler.once) this.removeListener(event, handler);
} else {
for (i = 1, args = new Array(len -1); i < len; i++) {
args[i - 1] = arguments[i];
}
for (i = 0; i < length; i++) {
listeners[i].apply(this, args);
if (listeners[i].once) this.removeListener(event, handler[i]);
}
}
return true;
};
/**
* Register a new EventListener for the given event.
*
* @param {String} event Name of the event.
* @param {Functon} fn Callback function.
* @api public
*/
EventEmitter.prototype.on = function on(event, fn) {
if (!this._events) this._events = {};
if (!this._events[event]) this._events[event] = [];
this._events[event].push(fn);
return this;
};
/**
* Add an EventListener that's only called once.
*
* @param {String} event Name of the event.
* @param {Function} fn Callback function.
* @api public
*/
EventEmitter.prototype.once = function once(event, fn) {
fn.once = true;
return this.on(event, fn);
};
/**
* Remove event listeners.
*
* @param {String} event The event we want to remove.
* @param {Function} fn The listener that we need to find.
* @api public
*/
EventEmitter.prototype.removeListener = function removeListener(event, fn) {
if (!this._events || !this._events[event]) return this;
var listeners = this._events[event]
, events = [];
for (var i = 0, length = listeners.length; i < length; i++) {
if (fn && listeners[i] !== fn && listeners[i].fn !== fn) {
events.push(listeners[i]);
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) this._events[event] = events;
else this._events[event] = null;
return this;
};
/**
* Remove all listeners or only the listeners for the specified event.
*
* @param {String} event The event want to remove all listeners for.
* @api public
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
if (!this._events) return this;
if (event) this._events[event] = null;
else this._events = {};
return this;
};
//
// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
//
// This function doesn't apply anymore.
//
EventEmitter.prototype.setMaxListeners = function setMaxListeners() {
return this;
};
//
// Expose the module.
//
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.EventEmitter2 = EventEmitter;
EventEmitter.EventEmitter3 = EventEmitter;
try { module.exports = EventEmitter; }
catch (e) {}
},{}],6:[function(require,module,exports){
'use strict';
/**
* Create a new pre-configured iframe.
*
* Options:
*
* visible: (boolean) Don't hide the iframe by default.
* sandbox: (array) Sandbox properties.
*
* @param {Element} el DOM element where the iframe should be added on.
* @param {String} id A unique name/id for the iframe.
* @param {String} options Options.
* @return {Object}
* @api private
*/
module.exports = function iframe(el, id, options) {
var i;
options = options || {};
options.sandbox = options.sandbox || [
'allow-pointer-lock',
'allow-same-origin',
'allow-scripts',
'allow-popups',
'allow-forms'
];
try {
//
// Internet Explorer 6/7 require a unique name attribute in order to work.
// In addition to that, dynamic name attributes cannot be added using
// `i.name` as it will just ignore it. Creating it using this oddly <iframe>
// element fixes these issues.
//
i = document.createElement('<iframe name="'+ id +'">');
} catch (e) {
i = document.createElement('iframe');
i.name = id;
}
//
// The iframe needs to be added in to the DOM before we can modify it, make
// sure it's remains unseen.
//
if (!options.visible) {
i.style.top = i.style.left = -10000;
i.style.position = 'absolute';
i.style.display = 'none';
}
i.setAttribute('frameBorder', 0);
if (options.sandbox.length) {
i.setAttribute('sandbox', (options.sandbox).join(' '));
}
i.id = id;
return {
/**
* Return the document which we can use to inject or modify the HTML.
*
* @returns {Document}
* @api public
*/
document: function doc() {
return this.window().document;
},
/**
* Return the global or the window from the iframe.
*
* @returns {Window}
* @api public
*/
window: function win() {
return i.contentWindow || (i.contentDocument
? i.contentDocument.parentWindow || {}
: {}
);
},
/**
* Add the iframe to the DOM, use insertBefore first child to avoid
* `Operation Aborted` error in IE6.
*
* @api public
*/
add: function add() {
if (!this.attached()) {
el.insertBefore(i, el.firstChild);
}
return this;
},
/**
* Remove the iframe from the DOM.
*
* @api public
*/
remove: function remove() {
if (this.attached()) {
el.removeChild(i);
}
return this;
},
/**
* Checks if the iframe is currently attached to the DOM.
*
* @returns {Boolean} The container is attached to the mount point.
* @api private
*/
attached: function attached() {
return !!document.getElementById(id);
},
/**
* Reference to the iframe element.
*
* @type {HTMLIFRAMEElement}
* @public
*/
frame: i
};
};
},{}]},{},[3])
(3)
});
;