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 (2008) // Johan Euphrosine (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. * Feature 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.\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++; } } }; });