Files
atom/HTML/pilot/stacktrace.js
2011-08-27 00:14:44 -07:00

333 lines
10 KiB
JavaScript

define(function(require, exports, module) {
var ua = require("pilot/useragent");
var console = require('pilot/console');
// Changed to suit the specific needs of running within Skywriter
// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
// Luke Smith http://lucassmith.name/ (2008)
// Loic Dachary <loic@dachary.org> (2008)
// Johan Euphrosine <proppy@aminche.com> (2008)
// Øyvind Sean Kinsey http://kinsey.no/blog
//
// Information and discussions
// http://jspoker.pokersource.info/skin/test-printstacktrace.html
// http://eriwen.com/javascript/js-stack-trace/
// http://eriwen.com/javascript/stacktrace-update/
// http://pastie.org/253058
// http://browsershots.org/http://jspoker.pokersource.info/skin/test-printstacktrace.html
//
//
// guessFunctionNameFromLines comes from firebug
//
// Software License Agreement (BSD License)
//
// Copyright (c) 2007, Parakey Inc.
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the
// following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// * Neither the name of Parakey Inc. nor the names of its
// contributors may be used to endorse or promote products
// derived from this software without specific prior
// written permission of Parakey Inc.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Different browsers create stack traces in different ways.
* <strike>Feature</strike> Browser detection baby ;).
*/
var mode = (function() {
// We use SC's browser detection here to avoid the "break on error"
// functionality provided by Firebug. Firebug tries to do the right
// thing here and break, but it happens every time you load the page.
// bug 554105
if (ua.isGecko) {
return 'firefox';
} else if (ua.isOpera) {
return 'opera';
} else {
return 'other';
}
// SC doesn't do any detection of Chrome at this time.
// this is the original feature detection code that is used as a
// fallback.
try {
(0)();
} catch (e) {
if (e.arguments) {
return 'chrome';
}
if (e.stack) {
return 'firefox';
}
if (window.opera && !('stacktrace' in e)) { //Opera 9-
return 'opera';
}
}
return 'other';
})();
/**
*
*/
function stringifyArguments(args) {
for (var i = 0; i < args.length; ++i) {
var argument = args[i];
if (typeof argument == 'object') {
args[i] = '#object';
} else if (typeof argument == 'function') {
args[i] = '#function';
} else if (typeof argument == 'string') {
args[i] = '"' + argument + '"';
}
}
return args.join(',');
}
/**
* Extract a stack trace from the format emitted by each browser.
*/
var decoders = {
chrome: function(e) {
var stack = e.stack;
if (!stack) {
console.log(e);
return [];
}
return stack.replace(/^.*?\n/, '').
replace(/^.*?\n/, '').
replace(/^.*?\n/, '').
replace(/^[^\(]+?[\n$]/gm, '').
replace(/^\s+at\s+/gm, '').
replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').
split('\n');
},
firefox: function(e) {
var stack = e.stack;
if (!stack) {
console.log(e);
return [];
}
// stack = stack.replace(/^.*?\n/, '');
stack = stack.replace(/(?:\n@:0)?\s+$/m, '');
stack = stack.replace(/^\(/gm, '{anonymous}(');
return stack.split('\n');
},
// Opera 7.x and 8.x only!
opera: function(e) {
var lines = e.message.split('\n'), ANON = '{anonymous}',
lineRE = /Line\s+(\d+).*?script\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i, i, j, len;
for (i = 4, j = 0, len = lines.length; i < len; i += 2) {
if (lineRE.test(lines[i])) {
lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) +
' -- ' +
lines[i + 1].replace(/^\s+/, '');
}
}
lines.splice(j, lines.length - j);
return lines;
},
// Safari, Opera 9+, IE, and others
other: function(curr) {
var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], j = 0, fn, args;
var maxStackSize = 10;
while (curr && stack.length < maxStackSize) {
fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
args = Array.prototype.slice.call(curr['arguments']);
stack[j++] = fn + '(' + stringifyArguments(args) + ')';
//Opera bug: if curr.caller does not exist, Opera returns curr (WTF)
if (curr === curr.caller && window.opera) {
//TODO: check for same arguments if possible
break;
}
curr = curr.caller;
}
return stack;
}
};
/**
*
*/
function NameGuesser() {
}
NameGuesser.prototype = {
sourceCache: {},
ajax: function(url) {
var req = this.createXMLHTTPObject();
if (!req) {
return;
}
req.open('GET', url, false);
req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
req.send('');
return req.responseText;
},
createXMLHTTPObject: function() {
// Try XHR methods in order and store XHR factory
var xmlhttp, XMLHttpFactories = [
function() {
return new XMLHttpRequest();
}, function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}, function() {
return new ActiveXObject('Msxml3.XMLHTTP');
}, function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
];
for (var i = 0; i < XMLHttpFactories.length; i++) {
try {
xmlhttp = XMLHttpFactories[i]();
// Use memoization to cache the factory
this.createXMLHTTPObject = XMLHttpFactories[i];
return xmlhttp;
} catch (e) {}
}
},
getSource: function(url) {
if (!(url in this.sourceCache)) {
this.sourceCache[url] = this.ajax(url).split('\n');
}
return this.sourceCache[url];
},
guessFunctions: function(stack) {
for (var i = 0; i < stack.length; ++i) {
var reStack = /{anonymous}\(.*\)@(\w+:\/\/([-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/;
var frame = stack[i], m = reStack.exec(frame);
if (m) {
var file = m[1], lineno = m[4]; //m[7] is character position in Chrome
if (file && lineno) {
var functionName = this.guessFunctionName(file, lineno);
stack[i] = frame.replace('{anonymous}', functionName);
}
}
}
return stack;
},
guessFunctionName: function(url, lineNo) {
try {
return this.guessFunctionNameFromLines(lineNo, this.getSource(url));
} catch (e) {
return 'getSource failed with url: ' + url + ', exception: ' + e.toString();
}
},
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 = '', maxLines = 10;
for (var i = 0; i < maxLines; ++i) {
line = source[lineNo - i] + line;
if (line !== undefined) {
var m = reGuessFunction.exec(line);
if (m) {
return m[1];
}
else {
m = reFunctionArgNames.exec(line);
}
if (m && m[1]) {
return m[1];
}
}
}
return '(?)';
}
};
var guesser = new NameGuesser();
var frameIgnorePatterns = [
/http:\/\/localhost:4020\/sproutcore.js:/
];
exports.ignoreFramesMatching = function(regex) {
frameIgnorePatterns.push(regex);
};
/**
* Create a stack trace from an exception
* @param ex {Error} The error to create a stacktrace from (optional)
* @param guess {Boolean} If we should try to resolve the names of anonymous functions
*/
exports.Trace = function Trace(ex, guess) {
this._ex = ex;
this._stack = decoders[mode](ex);
if (guess) {
this._stack = guesser.guessFunctions(this._stack);
}
};
/**
* Log to the console a number of lines (default all of them)
* @param lines {number} Maximum number of lines to wrote to console
*/
exports.Trace.prototype.log = function(lines) {
if (lines <= 0) {
// You aren't going to have more lines in your stack trace than this
// and it still fits in a 32bit integer
lines = 999999999;
}
var printed = 0;
for (var i = 0; i < this._stack.length && printed < lines; i++) {
var frame = this._stack[i];
var display = true;
frameIgnorePatterns.forEach(function(regex) {
if (regex.test(frame)) {
display = false;
}
});
if (display) {
console.debug(frame);
printed++;
}
}
};
});