mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
333 lines
10 KiB
JavaScript
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++;
|
|
}
|
|
}
|
|
};
|
|
|
|
});
|