diff --git a/Gruntfile.js b/Gruntfile.js
index 407dfee6c6b1..34f1e4c629ce 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -196,7 +196,7 @@ module.exports = function(grunt) {
unused: true,
global_defs: {
- 'TEST': false
+ '__DEV__': false
}
}
},
diff --git a/src/raven.js b/src/raven.js
index e46e519d4122..18fefed96848 100644
--- a/src/raven.js
+++ b/src/raven.js
@@ -1,27 +1,10 @@
-/*global XDomainRequest:false*/
+/*global XDomainRequest:false, __DEV__:false*/
'use strict';
var TraceKit = require('../vendor/TraceKit/tracekit');
var RavenConfigError = require('./configError');
-var utils = require('./utils');
var stringify = require('json-stringify-safe');
-var isFunction = utils.isFunction;
-var isUndefined = utils.isUndefined;
-var isError = utils.isError;
-var isEmptyObject = utils.isEmptyObject;
-var hasKey = utils.hasKey;
-var joinRegExp = utils.joinRegExp;
-var each = utils.each;
-var objectMerge = utils.objectMerge;
-var truncate = utils.truncate;
-var urlencode = utils.urlencode;
-var uuid4 = utils.uuid4;
-var htmlTreeAsString = utils.htmlTreeAsString;
-var parseUrl = utils.parseUrl;
-var isString = utils.isString;
-var fill = utils.fill;
-
var wrapConsoleMethod = require('./console').wrapMethod;
var dsnKeys = 'source protocol user pass host port path'.split(' '),
@@ -31,6 +14,8 @@ function now() {
return +new Date();
}
+var _window = typeof window !== 'undefined' ? window : undefined;
+var _document = _window && _window.document;
// First, check for JSON support
// If there is no JSON, we no-op the core features of Raven
@@ -38,7 +23,7 @@ function now() {
function Raven() {
this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);
// Raven can run in contexts where there's no document (react-native)
- this._hasDocument = typeof document !== 'undefined';
+ this._hasDocument = !isUndefined(_document);
this._lastCapturedException = null;
this._lastEventId = null;
this._globalServer = null;
@@ -62,7 +47,7 @@ function Raven() {
this._originalErrorStackTraceLimit = Error.stackTraceLimit;
// capture references to window.console *and* all its methods first
// before the console plugin has a chance to monkey patch
- this._originalConsole = window.console || {};
+ this._originalConsole = _window.console || {};
this._originalConsoleMethods = {};
this._plugins = [];
this._startTime = now();
@@ -70,7 +55,7 @@ function Raven() {
this._breadcrumbs = [];
this._lastCapturedEvent = null;
this._keypressTimeout;
- this._location = window.location;
+ this._location = _window.location;
this._lastHref = this._location && this._location.href;
for (var method in this._originalConsole) { // eslint-disable-line guard-for-in
@@ -105,11 +90,13 @@ Raven.prototype = {
config: function(dsn, options) {
var self = this;
- if (this._globalServer) {
+ if (self._globalServer) {
this._logDebug('error', 'Error: Raven has already been configured');
- return this;
+ return self;
}
- if (!dsn) return this;
+ if (!dsn) return self;
+
+ var globalOptions = self._globalOptions;
// merge in options
if (options) {
@@ -118,24 +105,24 @@ Raven.prototype = {
if (key === 'tags' || key === 'extra') {
self._globalContext[key] = value;
} else {
- self._globalOptions[key] = value;
+ globalOptions[key] = value;
}
});
}
- this.setDSN(dsn);
+ self.setDSN(dsn);
// "Script error." is hard coded into browsers for errors that it can't read.
// this is the result of a script being pulled in from an external domain and CORS.
- this._globalOptions.ignoreErrors.push(/^Script error\.?$/);
- this._globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
+ globalOptions.ignoreErrors.push(/^Script error\.?$/);
+ globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
// join regexp rules into one big rule
- this._globalOptions.ignoreErrors = joinRegExp(this._globalOptions.ignoreErrors);
- this._globalOptions.ignoreUrls = this._globalOptions.ignoreUrls.length ? joinRegExp(this._globalOptions.ignoreUrls) : false;
- this._globalOptions.whitelistUrls = this._globalOptions.whitelistUrls.length ? joinRegExp(this._globalOptions.whitelistUrls) : false;
- this._globalOptions.includePaths = joinRegExp(this._globalOptions.includePaths);
- this._globalOptions.maxBreadcrumbs = Math.max(0, Math.min(this._globalOptions.maxBreadcrumbs || 100, 100)); // default and hard limit is 100
+ globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);
+ globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false;
+ globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false;
+ globalOptions.includePaths = joinRegExp(globalOptions.includePaths);
+ globalOptions.maxBreadcrumbs = Math.max(0, Math.min(globalOptions.maxBreadcrumbs || 100, 100)); // default and hard limit is 100
var autoBreadcrumbDefaults = {
xhr: true,
@@ -144,18 +131,18 @@ Raven.prototype = {
location: true
};
- var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
+ var autoBreadcrumbs = globalOptions.autoBreadcrumbs;
if ({}.toString.call(autoBreadcrumbs) === '[object Object]') {
autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs);
} else if (autoBreadcrumbs !== false) {
autoBreadcrumbs = autoBreadcrumbDefaults;
}
- this._globalOptions.autoBreadcrumbs = autoBreadcrumbs;
+ globalOptions.autoBreadcrumbs = autoBreadcrumbs;
- TraceKit.collectWindowErrors = !!this._globalOptions.collectWindowErrors;
+ TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;
// return for chaining
- return this;
+ return self;
},
/*
@@ -168,21 +155,21 @@ Raven.prototype = {
*/
install: function() {
var self = this;
- if (this.isSetup() && !this._isRavenInstalled) {
+ if (self.isSetup() && !self._isRavenInstalled) {
TraceKit.report.subscribe(function () {
self._handleOnErrorStackInfo.apply(self, arguments);
});
- this._instrumentTryCatch();
+ self._instrumentTryCatch();
if (self._globalOptions.autoBreadcrumbs)
- this._instrumentBreadcrumbs();
+ self._instrumentBreadcrumbs();
// Install all of the plugins
- this._drainPlugins();
+ self._drainPlugins();
- this._isRavenInstalled = true;
+ self._isRavenInstalled = true;
}
- Error.stackTraceLimit = this._globalOptions.stackTraceLimit;
+ Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
return this;
},
@@ -192,19 +179,20 @@ Raven.prototype = {
* @param {string} dsn The public Sentry DSN
*/
setDSN: function(dsn) {
- var uri = this._parseDSN(dsn),
+ var self = this,
+ uri = self._parseDSN(dsn),
lastSlash = uri.path.lastIndexOf('/'),
path = uri.path.substr(1, lastSlash);
- this._dsn = dsn;
- this._globalKey = uri.user;
- this._globalSecret = uri.pass && uri.pass.substr(1);
- this._globalProject = uri.path.substr(lastSlash + 1);
+ self._dsn = dsn;
+ self._globalKey = uri.user;
+ self._globalSecret = uri.pass && uri.pass.substr(1);
+ self._globalProject = uri.path.substr(lastSlash + 1);
- this._globalServer = this._getGlobalServer(uri);
+ self._globalServer = self._getGlobalServer(uri);
- this._globalEndpoint = this._globalServer +
- '/' + path + 'api/' + this._globalProject + '/store/';
+ self._globalEndpoint = self._globalServer +
+ '/' + path + 'api/' + self._globalProject + '/store/';
},
/*
@@ -427,7 +415,7 @@ Raven.prototype = {
},
addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
- var pluginArgs = Array.prototype.slice.call(arguments, 1);
+ var pluginArgs = [].slice.call(arguments, 1);
this._plugins.push([plugin, pluginArgs]);
if (this._isRavenInstalled) {
@@ -606,14 +594,14 @@ Raven.prototype = {
// TODO: remove window dependence?
// Attempt to initialize Raven on load
- var RavenConfig = window.RavenConfig;
+ var RavenConfig = _window.RavenConfig;
if (RavenConfig) {
this.config(RavenConfig.dsn, RavenConfig.config).install();
}
},
showReportDialog: function (options) {
- if (!window.document) // doesn't work without a document (React native)
+ if (!_document) // doesn't work without a document (React native)
return;
options = options || {};
@@ -641,10 +629,10 @@ Raven.prototype = {
var globalServer = this._getGlobalServer(this._parseDSN(dsn));
- var script = document.createElement('script');
+ var script = _document.createElement('script');
script.async = true;
script.src = globalServer + '/api/embed/error-page/' + qs;
- (document.head || document.body).appendChild(script);
+ (_document.head || _document.body).appendChild(script);
},
/**** Private functions ****/
@@ -668,11 +656,11 @@ Raven.prototype = {
eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1);
- if (document.createEvent) {
- evt = document.createEvent('HTMLEvents');
+ if (_document.createEvent) {
+ evt = _document.createEvent('HTMLEvents');
evt.initEvent(eventType, true, true);
} else {
- evt = document.createEventObject();
+ evt = _document.createEventObject();
evt.eventType = eventType;
}
@@ -680,14 +668,14 @@ Raven.prototype = {
evt[key] = options[key];
}
- if (document.createEvent) {
+ if (_document.createEvent) {
// IE9 if standards
- document.dispatchEvent(evt);
+ _document.dispatchEvent(evt);
} else {
// IE8 regardless of Quirks or Standards
// IE9 if quirks
try {
- document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
+ _document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
} catch(e) {
// Do nothing
}
@@ -837,7 +825,7 @@ Raven.prototype = {
var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
function wrapEventTarget(global) {
- var proto = window[global] && window[global].prototype;
+ var proto = _window[global] && _window[global].prototype;
if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {
fill(proto, 'addEventListener', function(orig) {
return function (evtName, fn, capture, secure) { // preserve arity
@@ -875,10 +863,10 @@ Raven.prototype = {
}
}
- fill(window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
- fill(window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
- if (window.requestAnimationFrame) {
- fill(window, 'requestAnimationFrame', function (orig) {
+ fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
+ fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
+ if (_window.requestAnimationFrame) {
+ fill(_window, 'requestAnimationFrame', function (orig) {
return function (cb) {
return orig(self.wrap(cb));
};
@@ -892,7 +880,7 @@ Raven.prototype = {
wrapEventTarget(eventTargets[i]);
}
- var $ = window.jQuery || window.$;
+ var $ = _window.jQuery || _window.$;
if ($ && $.fn && $.fn.ready) {
fill($.fn, 'ready', function (orig) {
return function (fn) {
@@ -926,7 +914,7 @@ Raven.prototype = {
}
}
- if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in window) {
+ if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {
var xhrproto = XMLHttpRequest.prototype;
fill(xhrproto, 'open', function(origOpen) {
return function (method, url) { // preserve arity
@@ -986,14 +974,14 @@ Raven.prototype = {
// Capture breadcrumbs from any click that is unhandled / bubbled up all the way
// to the document. Do this before we instrument addEventListener.
if (autoBreadcrumbs.dom && this._hasDocument) {
- if (document.addEventListener) {
- document.addEventListener('click', self._breadcrumbEventHandler('click'), false);
- document.addEventListener('keypress', self._keypressEventHandler(), false);
+ if (_document.addEventListener) {
+ _document.addEventListener('click', self._breadcrumbEventHandler('click'), false);
+ _document.addEventListener('keypress', self._keypressEventHandler(), false);
}
else {
// IE8 Compatibility
- document.attachEvent('onclick', self._breadcrumbEventHandler('click'));
- document.attachEvent('onkeypress', self._keypressEventHandler());
+ _document.attachEvent('onclick', self._breadcrumbEventHandler('click'));
+ _document.attachEvent('onkeypress', self._keypressEventHandler());
}
}
@@ -1001,13 +989,13 @@ Raven.prototype = {
// NOTE: in Chrome App environment, touching history.pushState, *even inside
// a try/catch block*, will cause Chrome to output an error to console.error
// borrowed from: https://github.com/angular/angular.js/pull/13945/files
- var chrome = window.chrome;
+ var chrome = _window.chrome;
var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
- var hasPushState = !isChromePackagedApp && window.history && history.pushState;
+ var hasPushState = !isChromePackagedApp && _window.history && history.pushState;
if (autoBreadcrumbs.location && hasPushState) {
// TODO: remove onpopstate handler on uninstall()
- var oldOnPopState = window.onpopstate;
- window.onpopstate = function () {
+ var oldOnPopState = _window.onpopstate;
+ _window.onpopstate = function () {
var currentHref = self._location.href;
self._captureUrlChange(self._lastHref, currentHref);
@@ -1033,7 +1021,7 @@ Raven.prototype = {
}, wrappedBuiltIns);
}
- if (autoBreadcrumbs.console && 'console' in window && console.log) {
+ if (autoBreadcrumbs.console && 'console' in _window && console.log) {
// console
var consoleMethodCallback = function (msg, data) {
self.captureBreadcrumb({
@@ -1232,7 +1220,7 @@ Raven.prototype = {
},
_getHttpData: function() {
- if (!this._hasDocument || !document.location || !document.location.href) {
+ if (!this._hasDocument || !_document.location || !_document.location.href) {
return;
}
@@ -1242,10 +1230,10 @@ Raven.prototype = {
}
};
- httpData.url = document.location.href;
+ httpData.url = _document.location.href;
- if (document.referrer) {
- httpData.headers.Referer = document.referrer;
+ if (_document.referrer) {
+ httpData.headers.Referer = _document.referrer;
}
return httpData;
@@ -1319,6 +1307,10 @@ Raven.prototype = {
this._sendProcessedPayload(data);
},
+ _getUuid: function () {
+ return uuid4();
+ },
+
_sendProcessedPayload: function(data, callback) {
var self = this;
var globalOptions = this._globalOptions;
@@ -1326,7 +1318,7 @@ Raven.prototype = {
// Send along an event_id if not explicitly passed.
// This event_id can be used to reference the error within Sentry itself.
// Set lastEventId after we know the error should actually be sent
- this._lastEventId = data.event_id || (data.event_id = uuid4());
+ this._lastEventId = data.event_id || (data.event_id = this._getUuid());
// Try and clean up the packet before sending by truncating long values
data = this._trimPacket(data);
@@ -1442,6 +1434,285 @@ Raven.prototype = {
}
};
+/*------------------------------------------------
+ * utils
+ *
+ * conditionally exported for test via Raven.utils
+ =================================================
+ */
+var objectPrototype = Object.prototype;
+
+function isUndefined(what) {
+ return what === void 0;
+}
+
+function isFunction(what) {
+ return typeof what === 'function';
+}
+
+function isString(what) {
+ return objectPrototype.toString.call(what) === '[object String]';
+}
+
+function isObject(what) {
+ return typeof what === 'object' && what !== null;
+}
+
+function isEmptyObject(what) {
+ for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars
+ return true;
+}
+
+// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
+// with some tiny modifications
+function isError(what) {
+ var toString = objectPrototype.toString.call(what);
+ return isObject(what) &&
+ toString === '[object Error]' ||
+ toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions
+ what instanceof Error;
+}
+
+function each(obj, callback) {
+ var i, j;
+
+ if (isUndefined(obj.length)) {
+ for (i in obj) {
+ if (hasKey(obj, i)) {
+ callback.call(null, i, obj[i]);
+ }
+ }
+ } else {
+ j = obj.length;
+ if (j) {
+ for (i = 0; i < j; i++) {
+ callback.call(null, i, obj[i]);
+ }
+ }
+ }
+}
+
+function objectMerge(obj1, obj2) {
+ if (!obj2) {
+ return obj1;
+ }
+ each(obj2, function(key, value){
+ obj1[key] = value;
+ });
+ return obj1;
+}
+
+function truncate(str, max) {
+ return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
+}
+
+/**
+ * hasKey, a better form of hasOwnProperty
+ * Example: hasKey(MainHostObject, property) === true/false
+ *
+ * @param {Object} host object to check property
+ * @param {string} key to check
+ */
+function hasKey(object, key) {
+ return objectPrototype.hasOwnProperty.call(object, key);
+}
+
+function joinRegExp(patterns) {
+ // Combine an array of regular expressions and strings into one large regexp
+ // Be mad.
+ var sources = [],
+ i = 0, len = patterns.length,
+ pattern;
+
+ for (; i < len; i++) {
+ pattern = patterns[i];
+ if (isString(pattern)) {
+ // If it's a string, we need to escape it
+ // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
+ sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
+ } else if (pattern && pattern.source) {
+ // If it's a regexp already, we want to extract the source
+ sources.push(pattern.source);
+ }
+ // Intentionally skip other cases
+ }
+ return new RegExp(sources.join('|'), 'i');
+}
+
+function urlencode(o) {
+ var pairs = [];
+ each(o, function(key, value) {
+ pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+ });
+ return pairs.join('&');
+}
+
+// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
+// intentionally using regex and not href parsing trick because React Native and other
+// environments where DOM might not be available
+function parseUrl(url) {
+ var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
+ if (!match) return {};
+
+ // coerce to undefined values to empty string so we don't get 'undefined'
+ var query = match[6] || '';
+ var fragment = match[8] || '';
+ return {
+ protocol: match[2],
+ host: match[4],
+ path: match[5],
+ relative: match[5] + query + fragment // everything minus origin
+ };
+}
+function uuid4() {
+ var crypto = window.crypto || window.msCrypto;
+
+ if (!isUndefined(crypto) && crypto.getRandomValues) {
+ // Use window.crypto API if available
+ var arr = new Uint16Array(8);
+ crypto.getRandomValues(arr);
+
+ // set 4 in byte 7
+ arr[3] = arr[3] & 0xFFF | 0x4000;
+ // set 2 most significant bits of byte 9 to '10'
+ arr[4] = arr[4] & 0x3FFF | 0x8000;
+
+ var pad = function(num) {
+ var v = num.toString(16);
+ while (v.length < 4) {
+ v = '0' + v;
+ }
+ return v;
+ };
+
+ return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) +
+ pad(arr[5]) + pad(arr[6]) + pad(arr[7]);
+ } else {
+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
+ return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random()*16|0,
+ v = c === 'x' ? r : r&0x3|0x8;
+ return v.toString(16);
+ });
+ }
+}
+
+/**
+ * Given a child DOM element, returns a query-selector statement describing that
+ * and its ancestors
+ * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
+ * @param elem
+ * @returns {string}
+ */
+function htmlTreeAsString(elem) {
+ /* eslint no-extra-parens:0*/
+ var MAX_TRAVERSE_HEIGHT = 5,
+ MAX_OUTPUT_LEN = 80,
+ out = [],
+ height = 0,
+ len = 0,
+ separator = ' > ',
+ sepLength = separator.length,
+ nextStr;
+
+ while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
+
+ nextStr = htmlElementAsString(elem);
+ // bail out if
+ // - nextStr is the 'html' element
+ // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
+ // (ignore this limit if we are on the first iteration)
+ if (nextStr === 'html' || height > 1 && len + (out.length * sepLength) + nextStr.length >= MAX_OUTPUT_LEN) {
+ break;
+ }
+
+ out.push(nextStr);
+
+ len += nextStr.length;
+ elem = elem.parentNode;
+ }
+
+ return out.reverse().join(separator);
+}
+
+/**
+ * Returns a simple, query-selector representation of a DOM element
+ * e.g. [HTMLElement] => input#foo.btn[name=baz]
+ * @param HTMLElement
+ * @returns {string}
+ */
+function htmlElementAsString(elem) {
+ var out = [],
+ className,
+ classes,
+ key,
+ attr,
+ i;
+
+ if (!elem || !elem.tagName) {
+ return '';
+ }
+
+ out.push(elem.tagName.toLowerCase());
+ if (elem.id) {
+ out.push('#' + elem.id);
+ }
+
+ className = elem.className;
+ if (className && isString(className)) {
+ classes = className.split(' ');
+ for (i = 0; i < classes.length; i++) {
+ out.push('.' + classes[i]);
+ }
+ }
+ var attrWhitelist = ['type', 'name', 'title', 'alt'];
+ for (i = 0; i < attrWhitelist.length; i++) {
+ key = attrWhitelist[i];
+ attr = elem.getAttribute(key);
+ if (attr) {
+ out.push('[' + key + '="' + attr + '"]');
+ }
+ }
+ return out.join('');
+}
+
+/**
+ * Polyfill a method
+ * @param obj object e.g. `document`
+ * @param name method name present on object e.g. `addEventListener`
+ * @param replacement replacement function
+ * @param track {optional} record instrumentation to an array
+ */
+function fill(obj, name, replacement, track) {
+ var orig = obj[name];
+ obj[name] = replacement(orig);
+ if (track) {
+ track.push([obj, name, orig]);
+ }
+}
+
+if (typeof __DEV__ !== 'undefined' && __DEV__) {
+ Raven.utils = {
+ isUndefined: isUndefined,
+ isFunction: isFunction,
+ isString: isString,
+ isObject: isObject,
+ isEmptyObject: isEmptyObject,
+ isError: isError,
+ each: each,
+ objectMerge: objectMerge,
+ truncate: truncate,
+ hasKey: hasKey,
+ joinRegExp: joinRegExp,
+ urlencode: urlencode,
+ uuid4: uuid4,
+ htmlTreeAsString: htmlTreeAsString,
+ htmlElementAsString: htmlElementAsString,
+ parseUrl: parseUrl,
+ fill: fill
+ };
+};
+
// Deprecations
Raven.prototype.setUser = Raven.prototype.setUserContext;
Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
diff --git a/src/utils.js b/src/utils.js
deleted file mode 100644
index adbc62aafe7b..000000000000
--- a/src/utils.js
+++ /dev/null
@@ -1,272 +0,0 @@
-/*eslint no-extra-parens:0*/
-'use strict';
-
-var objectPrototype = Object.prototype;
-
-function isUndefined(what) {
- return what === void 0;
-}
-
-function isFunction(what) {
- return typeof what === 'function';
-}
-
-function isString(what) {
- return objectPrototype.toString.call(what) === '[object String]';
-}
-
-function isObject(what) {
- return typeof what === 'object' && what !== null;
-}
-
-function isEmptyObject(what) {
- for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars
- return true;
-}
-
-// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
-// with some tiny modifications
-function isError(what) {
- var toString = objectPrototype.toString.call(what);
- return isObject(what) &&
- toString === '[object Error]' ||
- toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions
- what instanceof Error;
-}
-
-function each(obj, callback) {
- var i, j;
-
- if (isUndefined(obj.length)) {
- for (i in obj) {
- if (hasKey(obj, i)) {
- callback.call(null, i, obj[i]);
- }
- }
- } else {
- j = obj.length;
- if (j) {
- for (i = 0; i < j; i++) {
- callback.call(null, i, obj[i]);
- }
- }
- }
-}
-
-function objectMerge(obj1, obj2) {
- if (!obj2) {
- return obj1;
- }
- each(obj2, function(key, value){
- obj1[key] = value;
- });
- return obj1;
-}
-
-function truncate(str, max) {
- return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
-}
-
-/**
- * hasKey, a better form of hasOwnProperty
- * Example: hasKey(MainHostObject, property) === true/false
- *
- * @param {Object} host object to check property
- * @param {string} key to check
- */
-function hasKey(object, key) {
- return objectPrototype.hasOwnProperty.call(object, key);
-}
-
-function joinRegExp(patterns) {
- // Combine an array of regular expressions and strings into one large regexp
- // Be mad.
- var sources = [],
- i = 0, len = patterns.length,
- pattern;
-
- for (; i < len; i++) {
- pattern = patterns[i];
- if (isString(pattern)) {
- // If it's a string, we need to escape it
- // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
- sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
- } else if (pattern && pattern.source) {
- // If it's a regexp already, we want to extract the source
- sources.push(pattern.source);
- }
- // Intentionally skip other cases
- }
- return new RegExp(sources.join('|'), 'i');
-}
-
-function urlencode(o) {
- var pairs = [];
- each(o, function(key, value) {
- pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
- });
- return pairs.join('&');
-}
-
-// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
-// intentionally using regex and not href parsing trick because React Native and other
-// environments where DOM might not be available
-function parseUrl(url) {
- var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
- if (!match) return {};
-
- // coerce to undefined values to empty string so we don't get 'undefined'
- var query = match[6] || '';
- var fragment = match[8] || '';
- return {
- protocol: match[2],
- host: match[4],
- path: match[5],
- relative: match[5] + query + fragment // everything minus origin
- };
-}
-function uuid4() {
- var crypto = window.crypto || window.msCrypto;
-
- if (!isUndefined(crypto) && crypto.getRandomValues) {
- // Use window.crypto API if available
- var arr = new Uint16Array(8);
- crypto.getRandomValues(arr);
-
- // set 4 in byte 7
- arr[3] = arr[3] & 0xFFF | 0x4000;
- // set 2 most significant bits of byte 9 to '10'
- arr[4] = arr[4] & 0x3FFF | 0x8000;
-
- var pad = function(num) {
- var v = num.toString(16);
- while (v.length < 4) {
- v = '0' + v;
- }
- return v;
- };
-
- return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) +
- pad(arr[5]) + pad(arr[6]) + pad(arr[7]);
- } else {
- // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
- return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0,
- v = c === 'x' ? r : r&0x3|0x8;
- return v.toString(16);
- });
- }
-}
-
-/**
- * Given a child DOM element, returns a query-selector statement describing that
- * and its ancestors
- * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
- * @param elem
- * @returns {string}
- */
-function htmlTreeAsString(elem) {
- var MAX_TRAVERSE_HEIGHT = 5,
- MAX_OUTPUT_LEN = 80,
- out = [],
- height = 0,
- len = 0,
- separator = ' > ',
- sepLength = separator.length,
- nextStr;
-
- while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
-
- nextStr = htmlElementAsString(elem);
- // bail out if
- // - nextStr is the 'html' element
- // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
- // (ignore this limit if we are on the first iteration)
- if (nextStr === 'html' || height > 1 && len + (out.length * sepLength) + nextStr.length >= MAX_OUTPUT_LEN) {
- break;
- }
-
- out.push(nextStr);
-
- len += nextStr.length;
- elem = elem.parentNode;
- }
-
- return out.reverse().join(separator);
-}
-
-/**
- * Returns a simple, query-selector representation of a DOM element
- * e.g. [HTMLElement] => input#foo.btn[name=baz]
- * @param HTMLElement
- * @returns {string}
- */
-function htmlElementAsString(elem) {
- var out = [],
- className,
- classes,
- key,
- attr,
- i;
-
- if (!elem || !elem.tagName) {
- return '';
- }
-
- out.push(elem.tagName.toLowerCase());
- if (elem.id) {
- out.push('#' + elem.id);
- }
-
- className = elem.className;
- if (className && isString(className)) {
- classes = className.split(' ');
- for (i = 0; i < classes.length; i++) {
- out.push('.' + classes[i]);
- }
- }
- var attrWhitelist = ['type', 'name', 'title', 'alt'];
- for (i = 0; i < attrWhitelist.length; i++) {
- key = attrWhitelist[i];
- attr = elem.getAttribute(key);
- if (attr) {
- out.push('[' + key + '="' + attr + '"]');
- }
- }
- return out.join('');
-}
-
-/**
- * Polyfill a method
- * @param obj object e.g. `document`
- * @param name method name present on object e.g. `addEventListener`
- * @param replacement replacement function
- * @param track {optional} record instrumentation to an array
- */
-function fill(obj, name, replacement, track) {
- var orig = obj[name];
- obj[name] = replacement(orig);
- if (track) {
- track.push([obj, name, orig]);
- }
-}
-
-module.exports = {
- isUndefined: isUndefined,
- isFunction: isFunction,
- isString: isString,
- isObject: isObject,
- isEmptyObject: isEmptyObject,
- isError: isError,
- each: each,
- objectMerge: objectMerge,
- truncate: truncate,
- hasKey: hasKey,
- joinRegExp: joinRegExp,
- urlencode: urlencode,
- uuid4: uuid4,
- htmlTreeAsString: htmlTreeAsString,
- htmlElementAsString: htmlElementAsString,
- parseUrl: parseUrl,
- fill: fill
-};
diff --git a/test/index.html b/test/index.html
index 63028e17b21d..c273d684532b 100644
--- a/test/index.html
+++ b/test/index.html
@@ -19,7 +19,7 @@