Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js: Extended patching jQuery with try/catch. Added Chrome stacktrace parsing. #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
348 changes: 242 additions & 106 deletions js/crashkit-javascript.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
// Include this file to use CrashKit for reporting errors in your application.
// Visit http://crashkitapp.appspot.com/ for details.
//
// Copyright (c) 2009 Andrey Tarantsov, YourSway LLC (http://crashkitapp.appspot.com/)
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Huge thanks goes to Eric Wendelin, Luke Smith and Loic Dachary
// for inspiring us and giving us a head-start in JS stack trace collection.
// See: http://eriwen.com/javascript/js-stack-trace/
//
/*!
* Include this file to use CrashKit for reporting errors in your application.
* Visit http://crashkitapp.appspot.com/ for details.
*
* Copyright (c) 2009 Andrey Tarantsov, YourSway LLC (http://crashkitapp.appspot.com/)
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Huge thanks goes to Eric Wendelin, Luke Smith and Loic Dachary
* for inspiring us and giving us a head-start in JS stack trace collection.
* See: http://eriwen.com/javascript/js-stack-trace/
*/

// This file consists of three parts:
// 1) definition of CrashKit.report
// 2) definition of CrashKit.computeStackTrace
// 3) integration code that sends CrashKit.report notifications to CrashKit servers
//
// 2012-01-27 11:35:56 +0400 John Babak <babak.john@gmail.com> Extended to patch jQuery.fn.ready and jQuery.fn.each with try/catch.
// 2012-02-22 15:39:41 +0400 John Babak <babak.john@gmail.com> Extended to support Chrome stacktrace.


var CrashKit = {};

@@ -35,6 +41,7 @@ var CrashKit = {};
// try { ...code... } catch(ex) { CrashKit.report(ex); }
//
// Supports:
// - Chrome: full stack trace with line numbers and column numbers
// - Firefox: full stack trace with line numbers
// - Opera: full stack trace with line numbers
// - Safari: line number for the topmost element only, some frames may be missing
@@ -75,10 +82,14 @@ CrashKit.report = (function() {
lastExceptionStack = null;
lastException = null;
} else {
var location = {'url': url, 'line': lineNo};
var location = { 'url': url, 'line': lineNo };
location.func = CrashKit.computeStackTrace.guessFunctionName(location.url, location.line);
location.context = CrashKit.computeStackTrace.gatherContext(location.url, location.line);
stack = {'mode': 'onerror', 'message': message, 'stack': [location]};
stack = { 'mode': 'onerror', 'name': 'Error', 'message': message, 'stack': [location] };
if (/Uncaught\s*([^:]+)\s*:\s*(.*)/.test(message)) {
stack.name = RegExp.$1;
stack.message = RegExp.$2;
}
}
notifyHandlers(stack);
return false;
@@ -106,33 +117,56 @@ CrashKit.report = (function() {
notifyHandlers(stack);
}
}, (stack.incomplete ? 500 : 1));

throw ex; // re-throw to propagate to the top level (and cause window.onerror)
};

window.onerror = onerrorHandler;

// jQuery integration (not really tested)
if(typeof jQuery != 'undefined') {
// override jQuery.fn.bind to wrap every provided function in try/catch
var jQueryBind = jQuery.fn.bind;
jQuery.fn.bind = function(type, data, fn) {
if (!fn && data && typeof data == 'function') {
fn = data;
data = null;
}
if (fn) {
var origFn = fn;
var wrappedFn = function() {
try {
return origFn.apply(this, arguments);
} catch (ex) {
CrashKit.report(ex);
}
};
fn = wrappedFn;
}
return jQueryBind.call(this, type, data, fn);
};
function CrashKit_wrap(fn) {
return function CrashKit_wrapped() {
try {
return fn.apply(this, arguments);
} catch (ex) {
CrashKit.report(ex);
}
};
}
function CrashKit_patch(original, patchGuid) {
return function CrashKit_patched() {
var args = $.makeArray(arguments), orig = [],
i, ic = args.length,
ret;

// Patch functions passed as arguments:
for (i = 0; i < ic; ++i) {
if (typeof args[i] === 'function') {
orig[i] = args[i];
args[i] = CrashKit_wrap(orig[i]);
}
}

ret = original.apply(this, args);

// Patch function guids of jQuery event system after the original function is applied:
if (patchGuid) {
for (i = 0; i < ic; ++i) {
if (orig[i]) {
orig[i].guid = args[i].guid; //< handler.guid is used in jQuery.fn.unbind; for unbind to work, mark the original function with the guid given to the wrapper.
}
}
}

return ret;
};
}

// override jQuery.fn.bind and jQuery.fn.ready to wrap every provided function in try/catch
jQuery.fn.bind = CrashKit_patch(jQuery.fn.bind, true);
jQuery.fn.ready = CrashKit_patch(jQuery.fn.ready);
jQuery.fn.each = CrashKit_patch(jQuery.fn.each);
}

report.subscribe = subscribe;
@@ -224,13 +258,13 @@ CrashKit.computeStackTrace = (function() {
};

var guessFunctionNameFromLines = function(lineNo, source) {
var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
var reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
// Walk backwards from the first line in the function until we find the line which
// matches the pattern above, which is the function definition
var line = "";
var maxLines = 10;
for (var i = 0; i < maxLines; ++i) {
var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
var reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
// Walk backwards from the first line in the function until we find the line which
// matches the pattern above, which is the function definition
var line = "";
var maxLines = 10;
for (var i = 0; i < maxLines; ++i) {
line = source[lineNo-i] + line;
if (line !== undefined)
{
@@ -267,7 +301,7 @@ CrashKit.computeStackTrace = (function() {

var escapeRegExp = function(text) {
if (!arguments.callee.sRE) {
var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ];
arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
}
return text.replace(arguments.callee.sRE, '\\$1');
@@ -285,7 +319,7 @@ CrashKit.computeStackTrace = (function() {
source = source.join("\n");
var m = re.exec(source);
if (m) {
var result = {'url': urls[i], 'line': null};
var result = { 'url': urls[i], 'line': null };
result.startLine = source.substring(0, m.index).split("\n").length;
if (singleLineExpected)
result.line = result.startLine;
@@ -298,8 +332,8 @@ CrashKit.computeStackTrace = (function() {
};

var findSourceByFunctionBody = function(func) {
var htmlUrls = [window.location.href];
var urls = [window.location.href];
var htmlUrls = [ window.location.href ];
var urls = [ window.location.href ];
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
@@ -348,6 +382,13 @@ CrashKit.computeStackTrace = (function() {

// Contents of Exception in various browsers.
//
// CHROME:
// ex.message = Can't find variable: qq
// ex.fileName = http://localhost:5005/static/javascript/crashkit-ie-test.html#
// ex.lineNumber = 59
// ex.stack = ...stack trace... (see the example below)
// ex.name = ReferenceError
//
// WEBKIT:
// ex.message = Can't find variable: qq
// ex.line = 59
@@ -376,7 +417,96 @@ CrashKit.computeStackTrace = (function() {
// ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'


var computeStackTraceFromFirefoxStackProp = function(ex) {
var computeStackTraceFromChromeStackProp = function(ex, depth) {
if (!ex.stack)
return null;

// In Chrome, ex.stack contains a stack trace as a string. Cite from http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi follows:
//
// By default, almost all errors thrown by V8 have a stack property that holds the topmost 10 stack frames, formatted as a string. Here's an example of a fully formatted stack trace:
//
// ReferenceError: FAIL is not defined
// at Constraint.execute (deltablue.js:525:2)
// at Constraint.recalculate (deltablue.js:424:21)
// at Planner.addPropagate (deltablue.js:701:6)
// at Constraint.satisfy (deltablue.js:184:15)
// at Planner.incrementalAdd (deltablue.js:591:21)
// at Constraint.addConstraint (deltablue.js:162:10)
// at Constraint.BinaryConstraint (deltablue.js:346:7)
// at Constraint.EqualityConstraint (deltablue.js:515:38)
// at chainTest (deltablue.js:807:6)
// at deltaBlue (deltablue.js:879:2)
//
// If all the above information is available a formatted stack frame will look like this:
// at Type.functionName [as methodName] (location)
// or, in the case of a construct call:
// at new functionName (location)
// If only one of functionName and methodName is available, or if they are both available but the same, the format will be:
// at Type.name (location)
// If neither is available <anonymous> will be used as the name.
// One special case is the global object where the Type is not shown. In that case the stack frame will be formatted as:
// at functionName [as methodName] (location)
// The location itself has several possible formats. Most common is the file name, line and column number within the script that defined the current function
// fileName:lineNumber:columnNumber
// If the current function was created using eval the format will be
// eval at position
// where position is the full position where the call to eval occurred. Note that this means that positions can be nested if there are nested calls to eval, for instance:
// eval at Foo.a (eval at Bar.z (myscript.js:10:3))
// If a stack frame is within V8's libraries the location will be
// native
// and if is unavailable it will be
// unknown location

// Examples:
// at http://my.ivideon.local/:132:67
// at Error.<anonymous> (eval at declareErrorClass (http://my.ivideon.local/static_src/ivideon/components-base/js/ivideon.utils.js:181:23))

var firstlineRE = /^.*$/i;
var lineRE = /^\s*(eval )?at (?:((?:new )?[\w<>.]+(?: \[as [\w<>.]+\])?) )?[(]?([^)]+)[)]?\s*$/i;
var locationRE = /^(native)|(unknown location)|(?:(.*):(\d+):(\d+))$/;
var lines = ex.stack.split("\n");
var stack = [];
for(var i = 0; i < lines.length; ++i) {
var line = lines[i];
if (i === 0) {
if (!firstlineRE.test(line)) {
return null; // first line does not match the format
}
}
else if (lineRE.test(line)) {
var func = RegExp.$2;
while (RegExp.$1 === 'eval ') { //< eval in the beginning
if (lineRE.test(RegExp.$3)) {
func += ' at '+RegExp.$2;
}
else { break; }
}
if (RegExp.$3.indexOf('eval ') === 0) { //< eval in the parens
while (RegExp.$3.indexOf('eval ') === 0) {
if (lineRE.test(RegExp.$3)) {
func += ' at '+RegExp.$2;
}
else { break; }
}
}
if (locationRE.test(RegExp.$3)) {
if (RegExp.$1) { func = RegExp.$1; }
else if (RegExp.$2) { func = RegExp.$2; }
var element = { 'url': RegExp.$3, 'func': func, 'line': RegExp.$4 ? parseInt(RegExp.$4, 10) : null, 'column': RegExp.$5 ? parseInt(RegExp.$5, 10) : null };
if (!element.func && element.line)
element.func = guessFunctionName(element.url, element.line);
if (element.line)
element.context = gatherContext(element.url, element.line);
stack.push(element);
}
}
}
if (!stack.length)
return null; // ex.stack is defined, but cannot be parsed
return { 'mode': 'chrome', 'name': ex.name + (ex.type ? ' ' + ex.type : ''), 'message': ex.message, 'stack': stack, 'error': ex, 'inner': ex.inner };
};

var computeStackTraceFromFirefoxStackProp = function(ex, depth) {
if (!ex.stack)
return null;

@@ -395,7 +525,7 @@ CrashKit.computeStackTrace = (function() {
for(var i in lines) {
var line = lines[i];
if (lineRE.test(line)) {
var element = {'url': RegExp.$2, 'func': RegExp.$1, 'line': RegExp.$3};
var element = { 'url': RegExp.$2, 'func': RegExp.$1, 'line': RegExp.$3 ? parseInt(RegExp.$3, 10) : null };
if (!element.func && element.line)
element.func = guessFunctionName(element.url, element.line);
if (element.line)
@@ -405,10 +535,10 @@ CrashKit.computeStackTrace = (function() {
}
if (!stack.length)
return null; // ex.stack is defined, but cannot be parsed
return {'mode': 'firefox', 'name': ex.name, 'message': ex.message, 'stack': stack};
return { 'mode': 'firefox', 'name': ex.name, 'message': ex.message, 'stack': stack, 'error': ex, 'inner': ex.inner };
};

var computeStackTraceFromOperaMultiLineMessage = function(ex) {
var computeStackTraceFromOperaMultiLineMessage = function(ex, depth) {
// Opera includes a stack trace into the exception message. An example is:
//
// Statement on line 3: Undefined variable: undefinedFunc
@@ -442,9 +572,9 @@ CrashKit.computeStackTrace = (function() {
for (var i=2, len=lines.length; i < len; i += 2) {
var item = null;
if (lineRE1.test(lines[i]))
item = {'url': RegExp.$2, 'func': RegExp.$3, 'line': RegExp.$1};
item = { 'url': RegExp.$2, 'func': RegExp.$3, 'line': RegExp.$1 ? parseInt(RegExp.$1, 10) : null };
else if (lineRE2.test(lines[i])) {
item = {'url': RegExp.$3, 'func': RegExp.$4};
item = { 'url': RegExp.$3, 'func': RegExp.$4 };
var relativeLine = (RegExp.$1 - 0); // relative to the start of the <SCRIPT> block
var script = inlineScriptBlocks[RegExp.$2 - 1];
if (script) {
@@ -462,7 +592,7 @@ CrashKit.computeStackTrace = (function() {
var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i+1]));
var source = findSourceInUrls(re, [url], true);
if (source)
item = {'url': url, 'line': source.line, 'func': ''};
item = { 'url': url, 'line': source.line, 'func': '' };
}
if (item) {
if (!item.func)
@@ -473,19 +603,18 @@ CrashKit.computeStackTrace = (function() {
item.context = context;
else {
// if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
item.context = [lines[i+1]];
item.context = [ lines[i+1] ];
}
stack.push(item);
}
}
if (!stack.length)
return null; // could not parse multiline exception message as Opera stack trace

return {'mode': 'opera', 'name': ex.name, 'message': lines[0], 'stack': stack};
return { 'mode': 'opera', 'name': ex.name, 'message': lines[0], 'stack': stack, 'error': ex, 'inner': ex.inner };
};

var augmentStackTraceWithInitialElement = function(stackInfo, url, lineNo) {
var initial = {'url': url, 'line': lineNo};
var initial = { 'url': url, 'line': lineNo };
if (initial.url && initial.line) {
stackInfo.incomplete = false;

@@ -513,7 +642,6 @@ CrashKit.computeStackTrace = (function() {
var computeStackTraceByWalkingCallerChain = function(ex, depth) {
var fnRE = /function\s*([\w\-$]+)?\s*\(/i;
var stack = [];

var funcs = {}, recursion = false;
for(var curr = arguments.callee.caller; curr && !recursion; curr = curr.caller) {
var fn = curr.name || '';
@@ -532,13 +660,13 @@ CrashKit.computeStackTrace = (function() {
recursion = !!funcs[curr];
funcs[curr] = true;

var item = {'url': url, 'func': fn, 'line': line};
var item = { 'url': url, 'func': fn, 'line': line };
if (recursion) item.recursion = true;
stack.push(item);
}
stack.splice(0, depth);

var result = {'mode': 'callers', 'name': ex.name, 'message': ex.message, 'stack': stack};
var result = { 'mode': 'callers', 'name': ex.name, 'message': ex.message, 'stack': stack, 'error': ex, 'inner': ex.inner };
augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber);
return result;
};
@@ -547,6 +675,15 @@ CrashKit.computeStackTrace = (function() {
var stack = null;
depth = (typeof depth == 'undefined' ? 0 : depth-0);

if (Object.prototype.toString.call(ex.stack) === '[object Array]') {
return { 'mode': 'crashkit', 'name': ex.name, 'message': ex.message, 'stack': ex.stack, 'error': ex, 'inner': ex.inner };
}

try {
stack = computeStackTraceFromChromeStackProp(ex);
if (stack) return stack;
} catch(e) { if(debug) throw e; }

try {
stack = computeStackTraceFromFirefoxStackProp(ex);
if (stack) return stack;
@@ -562,7 +699,7 @@ CrashKit.computeStackTrace = (function() {
if (stack) return stack;
} catch(e) { if(debug) throw e; }

return { 'mode': 'failed' };
return { 'mode': 'failed', 'name': ex.name, 'message': ex.message, 'stack': [], 'error': ex, 'inner': ex.inner };
};

var computeStackTraceOfCaller = function(depth) {
@@ -582,7 +719,6 @@ CrashKit.computeStackTrace = (function() {
return computeStackTrace;
})();


// this part is specific to CrashKit web application
(function() {
var crashKitHost = null;
@@ -596,7 +732,7 @@ CrashKit.computeStackTrace = (function() {
if (src && src.indexOf("crashkit-javascript.js?") >= 0) {
crashKitHost = src.substring(src.indexOf("://") + 3);
crashKitHost = crashKitHost.substring(0, crashKitHost.indexOf("/"));

var x = src.substring(src.indexOf("?") + 1).split("/");
var proto = (("https:" == document.location.protocol) ? "https://" : "http://");
webServiceUrl = proto + crashKitHost + "/" + x[0] + "/products/" + x[1] + "/post-report/0/0";
@@ -605,36 +741,36 @@ CrashKit.computeStackTrace = (function() {
};

var encodeJSON = function() {
var c = {"\b":"b","\t":"t","\n":"n","\f":"f","\r":"r",'"':'"',"\\":"\\","/":"/"},
d = function(n){return n<10?"0".concat(n):n},
e = function(c,f,e){e=eval;delete eval;if(typeof eval==="undefined")eval=e;f=eval(""+c);eval=e;return f},
i = function(e,p,l){return 1*e.substr(p,l)},
p = ["","000","00","0",""],
rc = null,
rd = /^[0-9]{4}\-[0-9]{2}\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/,
rt = /^([0-9]+|[0-9]+[,\.][0-9]{1,3})$/,
rs = /(\x5c|\x2F|\x22|[\x0c-\x0d]|[\x08-\x0a])/g,
s = function(i,d){return "\\".concat(c[d])},
ru = /([\x00-\x07]|\x0b|[\x0e-\x1f])/g,
u = function(i,d){
var n=d.charCodeAt(0).toString(16);
return "\\u".concat(p[n.length],n)
},
v = function(k,v){return $[typeof result](result)!==Function&&(v.hasOwnProperty?v.hasOwnProperty(k):v.constructor.prototype[k]!==v[k])},
$ = {
"boolean":function(){return Boolean},
"function":function(){return Function},
"number":function(){return Number},
"object":function(o){return o instanceof o.constructor?o.constructor:null},
"string":function(){return String},
"undefined":function(){return null}
},
$$ = function(m){
function $(c,t){t=c[m];delete c[m];try{e(c)}catch(z){c[m]=t;return 1}};
return $(Array)&&$(Object)
};
try { rc = new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$') }
catch(z) { rc=/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/ };
var c = {"\b":"b","\t":"t","\n":"n","\f":"f","\r":"r",'"':'"',"\\":"\\","/":"/"},
d = function(n){return n<10?"0".concat(n):n},
e = function(c,f,e){e=eval;delete eval;if(typeof eval==="undefined")eval=e;f=eval(""+c);eval=e;return f},
i = function(e,p,l){return 1*e.substr(p,l)},
p = ["","000","00","0",""],
rc = null,
rd = /^[0-9]{4}\-[0-9]{2}\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/,
rt = /^([0-9]+|[0-9]+[,\.][0-9]{1,3})$/,
rs = /(\x5c|\x2F|\x22|[\x0c-\x0d]|[\x08-\x0a])/g,
s = function(i,d){return "\\".concat(c[d])},
ru = /([\x00-\x07]|\x0b|[\x0e-\x1f])/g,
u = function(i,d){
var n=d.charCodeAt(0).toString(16);
return "\\u".concat(p[n.length],n)
},
v = function(k,v){return $[typeof result](result)!==Function&&(v.hasOwnProperty?v.hasOwnProperty(k):v.constructor.prototype[k]!==v[k])},
$ = {
"boolean":function(){return Boolean},
"function":function(){return Function},
"number":function(){return Number},
"object":function(o){return o instanceof o.constructor?o.constructor:null},
"string":function(){return String},
"undefined":function(){return null}
},
$$ = function(m){
function $(c,t){t=c[m];delete c[m];try{e(c)}catch(z){c[m]=t;return 1}};
return $(Array)&&$(Object)
};
try { rc = new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$') }
catch(z) { rc=/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/ };

var encodeJSON = function(self) {
if (self === null) return "null";
@@ -645,7 +781,7 @@ CrashKit.computeStackTrace = (function() {
switch (tmp) {
case Array:
result = [];
for(var i = 0, j = 0, k = self.length; j < k; j++) {
for(var i = 0, j = 0, k = self.length; j < k; j++) {
if(self[j] !== undefined && (tmp = encodeJSON(self[j])))
result[i++] = tmp;
};
@@ -661,7 +797,7 @@ CrashKit.computeStackTrace = (function() {
case String:
return '"'.concat(self.replace(rs, s).replace(ru, u), '"');
default:
var i = 0, key;
var i = 0, key;
result = [];
for (key in self) {
if (self[key] !== undefined && (tmp = encodeJSON(self[key])))
@@ -672,7 +808,7 @@ CrashKit.computeStackTrace = (function() {
};
return encodeJSON;
}();

var encodeError = function (stack) {
return encodeJSON([{
"userActionOrScreenNameOrBackgroundProcess": '',
@@ -701,7 +837,7 @@ CrashKit.computeStackTrace = (function() {
// oops
}
};

var sendToCrashKit = function(stack) {
if (webServiceUrl == null)
computeWebServiceUrl();
@@ -712,6 +848,6 @@ CrashKit.computeStackTrace = (function() {
window.console.info("Error report sent to " + crashKitHost + ":\n" + json);
sendPayload(json);
};

CrashKit.report.subscribe(sendToCrashKit);
})();