UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

398 lines (357 loc) 13.3 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2007-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * Methods to get information about the JavaScript call stack. * * @require(qx.lang.normalize.String) * @ignore(qx.bom.client.EcmaScript.*) * @ignore(qx.bom.client) * @ignore(qx.bom) * @ignore(qx.Class.*) */ qx.Bootstrap.define("qx.dev.StackTrace", { statics: { /** * Optional user-defined function to convert source file names into readable * class names. Will be called with the source file name extracted from the * browser's stack trace information as the only argument. The returned * string is used in the output of {@link #getStackTraceFromError} */ FILENAME_TO_CLASSNAME : null, /** * Optional user-defined formatting function for stack trace information. * Will be called by with an array of strings representing the calls in the * stack trace. {@link #getStackTraceFromError} will return the output of * this function. Must return an array of strings. */ FORMAT_STACKTRACE : null, /** * Get a stack trace of the current position in the code. * * Browser compatibility: * <ul> * <li>In new versions of Gecko, WebKit and Opera, the output of * {@link #getStackTraceFromError} and {@link #getStackTraceFromCaller} is * combined to generate the richest trace, including line numbers.</li> * <li>For Internet Explorer (and other engines that do not provide stack * traces), {@link #getStackTraceFromCaller} is used</li> * </ul> * * @return {String[]} Stack trace of the current position in the code. Each line in the array * represents one call in the stack trace. */ getStackTrace : function() { var trace = []; try { throw new Error(); } catch(ex) { if (qx.dev.StackTrace.hasEnvironmentCheck && qx.core.Environment.get("ecmascript.error.stacktrace")) { var errorTrace = qx.dev.StackTrace.getStackTraceFromError(ex); var callerTrace = qx.dev.StackTrace.getStackTraceFromCaller(arguments); qx.lang.Array.removeAt(errorTrace, 0); trace = callerTrace.length > errorTrace.length ? callerTrace : errorTrace; for (var i=0; i<Math.min(callerTrace.length, errorTrace.length); i++) { var callerCall = callerTrace[i]; if (callerCall.indexOf("anonymous") >= 0) { continue; } var methodName = null; var callerArr = callerCall.split("."); var mO = /(.*?)\(/.exec(callerArr[callerArr.length - 1]); if (mO && mO.length == 2) { methodName = mO[1]; callerArr.pop(); } if (callerArr[callerArr.length - 1] == "prototype") { callerArr.pop(); } var callerClassName = callerArr.join("."); var errorCall = errorTrace[i]; var errorArr = errorCall.split(":"); var errorClassName = errorArr[0]; var lineNumber = errorArr[1]; var columnNumber; if (errorArr[2]) { columnNumber = errorArr[2]; } var className = null; if (qx.Class && qx.Class.getByName(errorClassName)) { className = errorClassName; } else { className = callerClassName; } var line = className; if (methodName) { line += "." + methodName; } line += ":" + lineNumber; if (columnNumber) { line += ":" + columnNumber; } trace[i] = line; } } else { trace = this.getStackTraceFromCaller(arguments); } } return trace; }, /** * Get a stack trace from the arguments special variable using the * <code>caller</code> property. * * This methods returns class/mixin and function names of each step * in the call stack. * * Recursion is not supported. * * @param args {arguments} arguments variable. * @return {String[]} Stack trace of caller of the function the arguments variable belongs to. * Each line in the array represents one call in the stack trace. * @signature function(args) */ getStackTraceFromCaller : function(args) { var isStrictMode = function () { return (typeof this == 'undefined'); }; var trace = []; var fcn = null; if (!isStrictMode()) { try { fcn = qx.lang.Function.getCaller(args); }catch(ex) { // Nothing } } var knownFunction = {}; while (fcn) { var fcnName = qx.lang.Function.getName(fcn); trace.push(fcnName); try { fcn = fcn.caller; } catch(ex) { break; } if (!fcn) { break; } // avoid infinite recursion var hash = qx.core.ObjectRegistry.toHashCode(fcn); if (knownFunction[hash]) { trace.push("..."); break; } knownFunction[hash] = fcn; } return trace; }, /** * Try to get a stack trace from an Error object. Mozilla sets the field * <code>stack</code> for Error objects thrown using <code>throw new Error()</code>. * From this field it is possible to get a stack trace from the position * the exception was thrown at. * * This will get the JavaScript file names and the line numbers of each call. * The file names are converted into qooxdoo class names if possible (customizable * via {@link #FILENAME_TO_CLASSNAME}). * * The stack trace can be custom formatted using {@link #FORMAT_STACKTRACE}. * * This works reliably in Gecko-based browsers. Later Opera versions and * Chrome also provide a useful stack trace. For Safari, only the class or * file name and line number where the error occurred are returned. * IE 6/7/8/9 does not attach any stack information to error objects so an * empty array is returned. * * @param error {Error} Error exception instance. * @return {String[]} Stack trace of the exception. Each line in the array * represents one call in the stack trace. */ getStackTraceFromError : function(error) { var trace = []; var lineRe, hit, className, lineNumber, columnNumber, fileName, url; var traceProp = qx.dev.StackTrace.hasEnvironmentCheck ? qx.core.Environment.get("ecmascript.error.stacktrace") : null; if (traceProp === "stack") { if (!error.stack) { return trace; } // Gecko style, e.g. "()@http://localhost:8080/webcomponent-test-SNAPSHOT/webcomponent/js/com/ptvag/webcomponent/common/log/Logger:253" lineRe = /@(.+):(\d+)$/gm; while ((hit = lineRe.exec(error.stack)) != null) { url = hit[1]; lineNumber = hit[2]; className = this.__fileNameToClassName(url); trace.push(className + ":" + lineNumber); } if (trace.length > 0) { return this.__formatStackTrace(trace); } /* * Chrome trace info comes in two flavors: * at [jsObject].function (fileUrl:line:char) * at fileUrl:line:char */ lineRe = /at (.*)/gm; var fileReParens = /\((.*?)(:[\d:]+)\)/; var fileRe = /(.*?)(:[\d:]+$)/; while ((hit = lineRe.exec(error.stack)) != null) { var fileMatch = fileReParens.exec(hit[1]); if (!fileMatch) { fileMatch = fileRe.exec(hit[1]); } if (fileMatch) { className = this.__fileNameToClassName(fileMatch[1]); trace.push(className + fileMatch[2]); } else { trace.push(hit[1]); } } } else if (traceProp === "stacktrace") { // Opera var stacktrace = error.stacktrace; if (!stacktrace) { return trace; } if (stacktrace.indexOf("Error created at") >= 0) { stacktrace = stacktrace.split("Error created at")[0]; } // new Opera style (10.6+) lineRe = /line\ (\d+?),\ column\ (\d+?)\ in\ (?:.*?)\ in\ (.*?):[^\/]/gm; while ((hit = lineRe.exec(stacktrace)) != null) { lineNumber = hit[1]; columnNumber = hit[2]; url = hit[3]; className = this.__fileNameToClassName(url); trace.push(className + ":" + lineNumber + ":" + columnNumber); } if (trace.length > 0) { return this.__formatStackTrace(trace); } // older Opera style lineRe = /Line\ (\d+?)\ of\ linked\ script\ (.*?)$/gm; while ((hit = lineRe.exec(stacktrace)) != null) { lineNumber = hit[1]; url = hit[2]; className = this.__fileNameToClassName(url); trace.push(className + ":" + lineNumber); } } else if (error.message && error.message.indexOf("Backtrace:") >= 0) { // Some old Opera versions append the trace to the message property var traceString = error.message.split("Backtrace:")[1].trim(); var lines = traceString.split("\n"); for (var i=0; i<lines.length; i++) { var reResult = lines[i].match(/\s*Line ([0-9]+) of.* (\S.*)/); if (reResult && reResult.length >= 2) { lineNumber = reResult[1]; fileName = this.__fileNameToClassName(reResult[2]); trace.push(fileName + ":" + lineNumber); } } } else if (error.sourceURL && error.line) { // Safari trace.push(this.__fileNameToClassName(error.sourceURL) + ":" + error.line); } return this.__formatStackTrace(trace); }, /** * Converts the URL of a JavaScript file to a class name using either a * user-defined ({@link #FILENAME_TO_CLASSNAME}) or default * ({@link #__fileNameToClassNameDefault}) converter * * @param fileName {String} URL of the JavaScript file * @return {String} Result of the conversion */ __fileNameToClassName : function(fileName) { if (typeof qx.dev.StackTrace.FILENAME_TO_CLASSNAME == "function") { var convertedName = qx.dev.StackTrace.FILENAME_TO_CLASSNAME(fileName); if (qx.core.Environment.get("qx.debug") && !qx.lang.Type.isString(convertedName)) { throw new Error("FILENAME_TO_CLASSNAME must return a string!"); } return convertedName; } return qx.dev.StackTrace.__fileNameToClassNameDefault(fileName); }, /** * Converts the URL of a JavaScript file to a class name if the file is * named using the qooxdoo naming conventions. * * @param fileName {String} URL of the JavaScript file * @return {String} class name of the file if conversion was possible. * Otherwise the fileName is returned unmodified. */ __fileNameToClassNameDefault : function(fileName) { var scriptDir = "/source/class/"; var jsPos = fileName.indexOf(scriptDir); var paramPos = fileName.indexOf("?"); if (paramPos >= 0) { fileName = fileName.substring(0, paramPos); } var className = (jsPos == -1) ? fileName : fileName.substring(jsPos + scriptDir.length).replace(/\//g, ".").replace(/\.js$/, ""); return className; }, /** * Runs the given stack trace array through the formatter function * ({@link #FORMAT_STACKTRACE}) if available and returns it. Otherwise, the * original array is returned * * @param trace {String[]} Stack trace information * @return {String[]} Formatted stack trace info */ __formatStackTrace : function(trace) { if (typeof qx.dev.StackTrace.FORMAT_STACKTRACE == "function") { trace = qx.dev.StackTrace.FORMAT_STACKTRACE(trace); // Can't use qx.core.Assert here since it throws an AssertionError which // calls getStackTrace in its constructor, leading to infinite recursion if (qx.core.Environment.get("qx.debug") && !qx.lang.Type.isArray(trace)) { throw new Error("FORMAT_STACKTRACE must return an array of strings!"); } } return trace; } }, defer : function(statics) { // This is necessary to avoid an infinite loop when logging the absence // of the "ecmascript.error.stacktrace" environment key. statics.hasEnvironmentCheck = qx.bom && qx.bom.client && qx.bom.client.EcmaScript && qx.bom.client.EcmaScript.getStackTrace; } });