Commit 2776db24f43723f3e4ac64732fc29938bce93559

Authored by Cyril Mougel
2 parents 962a926a 1a5ac736
Exists in master and in 1 other branch production

Merge branch 'master' of github.com:errbit/errbit

Showing 1 changed file with 288 additions and 138 deletions   Show diff stats
public/javascripts/notifier.js
... ... @@ -47,76 +47,112 @@
47 47 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 48  
49 49 /**
50   - * Main function giving a function stack trace with a forced or passed in Error
  50 + * Main function giving a function stack trace with a forced or passed in Error
51 51 *
52 52 * @cfg {Error} e The error to create a stacktrace from (optional)
53 53 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
54   - * @return {Array} of Strings with functions, lines, files, and arguments where possible
  54 + * @return {Array} of Strings with functions, lines, files, and arguments where possible
55 55 */
56 56 function printStackTrace(options) {
57   - var ex = (options && options.e) ? options.e : null;
58   - var guess = options ? !!options.guess : true;
59   -
60   - var p = new printStackTrace.implementation();
61   - var result = p.run(ex);
62   - return (guess) ? p.guessFunctions(result) : result;
  57 + options = options || {guess: true};
  58 + var ex = options.e || null, guess = !!options.guess;
  59 + var p = new printStackTrace.implementation(), result = p.run(ex);
  60 + return (guess) ? p.guessAnonymousFunctions(result) : result;
  61 +}
  62 +
  63 +if (typeof module !== "undefined" && module.exports) {
  64 + module.exports = printStackTrace;
63 65 }
64 66  
65   -printStackTrace.implementation = function() {};
  67 +printStackTrace.implementation = function() {
  68 +};
66 69  
67 70 printStackTrace.implementation.prototype = {
68   - run: function(ex) {
69   - ex = ex ||
70   - (function() {
71   - try {
72   - var _err = __undef__ << 1;
73   - } catch (e) {
74   - return e;
75   - }
76   - })();
77   - // Use either the stored mode, or resolve it
78   - var mode = this._mode || this.mode(ex);
  71 + /**
  72 + * @param {Error} ex The error to create a stacktrace from (optional)
  73 + * @param {String} mode Forced mode (optional, mostly for unit tests)
  74 + */
  75 + run: function(ex, mode) {
  76 + ex = ex || this.createException();
  77 + // examine exception properties w/o debugger
  78 + //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
  79 + mode = mode || this.mode(ex);
79 80 if (mode === 'other') {
80 81 return this.other(arguments.callee);
81 82 } else {
82 83 return this[mode](ex);
83 84 }
84 85 },
85   -
  86 +
  87 + createException: function() {
  88 + try {
  89 + this.undef();
  90 + } catch (e) {
  91 + return e;
  92 + }
  93 + },
  94 +
86 95 /**
87   - * @return {String} mode of operation for the environment in question.
  96 + * Mode could differ for different exception, e.g.
  97 + * exceptions in Chrome may or may not have arguments or stack.
  98 + *
  99 + * @return {String} mode of operation for the exception
88 100 */
89 101 mode: function(e) {
90   - if (e['arguments']) {
91   - return (this._mode = 'chrome');
92   - } else if (window.opera && e.stacktrace) {
93   - return (this._mode = 'opera10');
  102 + if (e['arguments'] && e.stack) {
  103 + return 'chrome';
  104 + } else if (e.stack && e.sourceURL) {
  105 + return 'safari';
  106 + } else if (e.stack && e.number) {
  107 + return 'ie';
  108 + } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
  109 + // e.message.indexOf("Backtrace:") > -1 -> opera
  110 + // !e.stacktrace -> opera
  111 + if (!e.stacktrace) {
  112 + return 'opera9'; // use e.message
  113 + }
  114 + // 'opera#sourceloc' in e -> opera9, opera10a
  115 + if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
  116 + return 'opera9'; // use e.message
  117 + }
  118 + // e.stacktrace && !e.stack -> opera10a
  119 + if (!e.stack) {
  120 + return 'opera10a'; // use e.stacktrace
  121 + }
  122 + // e.stacktrace && e.stack -> opera10b
  123 + if (e.stacktrace.indexOf("called from line") < 0) {
  124 + return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
  125 + }
  126 + // e.stacktrace && e.stack -> opera11
  127 + return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
  128 + } else if (e.stack && !e.fileName) {
  129 + // Chrome 27 does not have e.arguments as earlier versions,
  130 + // but still does not have e.fileName as Firefox
  131 + return 'chrome';
94 132 } else if (e.stack) {
95   - return (this._mode = 'firefox');
96   - } else if (window.opera && !('stacktrace' in e)) { //Opera 9-
97   - return (this._mode = 'opera');
  133 + return 'firefox';
98 134 }
99   - return (this._mode = 'other');
  135 + return 'other';
100 136 },
101 137  
102 138 /**
103 139 * Given a context, function name, and callback function, overwrite it so that it calls
104 140 * printStackTrace() first with a callback and then runs the rest of the body.
105   - *
  141 + *
106 142 * @param {Object} context of execution (e.g. window)
107 143 * @param {String} functionName to instrument
108   - * @param {Function} function to call with a stack trace on invocation
  144 + * @param {Function} callback function to call with a stack trace on invocation
109 145 */
110 146 instrumentFunction: function(context, functionName, callback) {
111 147 context = context || window;
112   - context['_old' + functionName] = context[functionName];
113   - context[functionName] = function() {
114   - callback.call(this, printStackTrace());
115   - return context['_old' + functionName].apply(this, arguments);
  148 + var original = context[functionName];
  149 + context[functionName] = function instrumented() {
  150 + callback.call(this, printStackTrace().slice(4));
  151 + return context[functionName]._instrumented.apply(this, arguments);
116 152 };
117   - context[functionName]._instrumented = true;
  153 + context[functionName]._instrumented = original;
118 154 },
119   -
  155 +
120 156 /**
121 157 * Given a context and function name of a function that has been
122 158 * instrumented, revert the function to it's original (non-instrumented)
... ... @@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = {
128 164 deinstrumentFunction: function(context, functionName) {
129 165 if (context[functionName].constructor === Function &&
130 166 context[functionName]._instrumented &&
131   - context['_old' + functionName].constructor === Function) {
132   - context[functionName] = context['_old' + functionName];
  167 + context[functionName]._instrumented.constructor === Function) {
  168 + context[functionName] = context[functionName]._instrumented;
133 169 }
134 170 },
135   -
  171 +
136 172 /**
137 173 * Given an Error object, return a formatted Array based on Chrome's stack string.
138   - *
  174 + *
139 175 * @param e - Error object to inspect
140 176 * @return Array<String> of function calls, files and line numbers
141 177 */
142 178 chrome: function(e) {
143   - return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n');
  179 + var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
  180 + replace(/^\s+(at eval )?at\s+/gm, '').
  181 + replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
  182 + replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
  183 + stack.pop();
  184 + return stack;
  185 + },
  186 +
  187 + /**
  188 + * Given an Error object, return a formatted Array based on Safari's stack string.
  189 + *
  190 + * @param e - Error object to inspect
  191 + * @return Array<String> of function calls, files and line numbers
  192 + */
  193 + safari: function(e) {
  194 + return e.stack.replace(/\[native code\]\n/m, '')
  195 + .replace(/^(?=\w+Error\:).*$\n/m, '')
  196 + .replace(/^@/gm, '{anonymous}()@')
  197 + .split('\n');
  198 + },
  199 +
  200 + /**
  201 + * Given an Error object, return a formatted Array based on IE's stack string.
  202 + *
  203 + * @param e - Error object to inspect
  204 + * @return Array<String> of function calls, files and line numbers
  205 + */
  206 + ie: function(e) {
  207 + var lineRE = /^.*at (\w+) \(([^\)]+)\)$/gm;
  208 + return e.stack.replace(/at Anonymous function /gm, '{anonymous}()@')
  209 + .replace(/^(?=\w+Error\:).*$\n/m, '')
  210 + .replace(lineRE, '$1@$2')
  211 + .split('\n');
144 212 },
145 213  
146 214 /**
147 215 * Given an Error object, return a formatted Array based on Firefox's stack string.
148   - *
  216 + *
149 217 * @param e - Error object to inspect
150 218 * @return Array<String> of function calls, files and line numbers
151 219 */
152 220 firefox: function(e) {
153   - return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
  221 + return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^[\(@]/gm, '{anonymous}()@').split('\n');
  222 + },
  223 +
  224 + opera11: function(e) {
  225 + var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
  226 + var lines = e.stacktrace.split('\n'), result = [];
  227 +
  228 + for (var i = 0, len = lines.length; i < len; i += 2) {
  229 + var match = lineRE.exec(lines[i]);
  230 + if (match) {
  231 + var location = match[4] + ':' + match[1] + ':' + match[2];
  232 + var fnName = match[3] || "global code";
  233 + fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
  234 + result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
  235 + }
  236 + }
  237 +
  238 + return result;
  239 + },
  240 +
  241 + opera10b: function(e) {
  242 + // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
  243 + // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
  244 + // "@file://localhost/G:/js/test/functional/testcase1.html:15"
  245 + var lineRE = /^(.*)@(.+):(\d+)$/;
  246 + var lines = e.stacktrace.split('\n'), result = [];
  247 +
  248 + for (var i = 0, len = lines.length; i < len; i++) {
  249 + var match = lineRE.exec(lines[i]);
  250 + if (match) {
  251 + var fnName = match[1]? (match[1] + '()') : "global code";
  252 + result.push(fnName + '@' + match[2] + ':' + match[3]);
  253 + }
  254 + }
  255 +
  256 + return result;
154 257 },
155 258  
156 259 /**
157 260 * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
158   - *
  261 + *
159 262 * @param e - Error object to inspect
160 263 * @return Array<String> of function calls, files and line numbers
161 264 */
162   - opera10: function(e) {
163   - var stack = e.stacktrace;
164   - var lines = stack.split('\n'), ANON = '{anonymous}',
165   - lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len;
166   - for (i = 2, j = 0, len = lines.length; i < len - 2; i++) {
167   - if (lineRE.test(lines[i])) {
168   - var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2;
169   - var fnName = RegExp.$3;
170   - fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON);
171   - lines[j++] = fnName + '@' + location;
  265 + opera10a: function(e) {
  266 + // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
  267 + // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
  268 + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
  269 + var lines = e.stacktrace.split('\n'), result = [];
  270 +
  271 + for (var i = 0, len = lines.length; i < len; i += 2) {
  272 + var match = lineRE.exec(lines[i]);
  273 + if (match) {
  274 + var fnName = match[3] || ANON;
  275 + result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
172 276 }
173 277 }
174   -
175   - lines.splice(j, lines.length - j);
176   - return lines;
  278 +
  279 + return result;
177 280 },
178   -
179   - // Opera 7.x-9.x only!
180   - opera: function(e) {
181   - var lines = e.message.split('\n'), ANON = '{anonymous}',
182   - lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i,
183   - i, j, len;
184   -
185   - for (i = 4, j = 0, len = lines.length; i < len; i += 2) {
186   - //TODO: RegExp.exec() would probably be cleaner here
187   - if (lineRE.test(lines[i])) {
188   - lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, '');
  281 +
  282 + // Opera 7.x-9.2x only!
  283 + opera9: function(e) {
  284 + // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
  285 + // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
  286 + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
  287 + var lines = e.message.split('\n'), result = [];
  288 +
  289 + for (var i = 2, len = lines.length; i < len; i += 2) {
  290 + var match = lineRE.exec(lines[i]);
  291 + if (match) {
  292 + result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
189 293 }
190 294 }
191   -
192   - lines.splice(j, lines.length - j);
193   - return lines;
  295 +
  296 + return result;
194 297 },
195   -
196   - // Safari, IE, and others
  298 +
  299 + // Safari 5-, IE 9-, and others
197 300 other: function(curr) {
198   - var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i,
199   - stack = [], fn, args, maxStackSize = 10;
200   -
201   - while (curr && stack.length < maxStackSize) {
  301 + var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
  302 + while (curr && curr['arguments'] && stack.length < maxStackSize) {
202 303 fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
203   - args = Array.prototype.slice.call(curr['arguments']);
  304 + args = Array.prototype.slice.call(curr['arguments'] || []);
204 305 stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
205 306 curr = curr.caller;
206 307 }
207 308 return stack;
208 309 },
209   -
  310 +
210 311 /**
211   - * Given arguments array as a String, subsituting type names for non-string types.
  312 + * Given arguments array as a String, substituting type names for non-string types.
212 313 *
213   - * @param {Arguments} object
214   - * @return {Array} of Strings with stringified arguments
  314 + * @param {Arguments,Array} args
  315 + * @return {String} stringified arguments
215 316 */
216 317 stringifyArguments: function(args) {
  318 + var result = [];
  319 + var slice = Array.prototype.slice;
217 320 for (var i = 0; i < args.length; ++i) {
218 321 var arg = args[i];
219 322 if (arg === undefined) {
220   - args[i] = 'undefined';
  323 + result[i] = 'undefined';
221 324 } else if (arg === null) {
222   - args[i] = 'null';
  325 + result[i] = 'null';
223 326 } else if (arg.constructor) {
224 327 if (arg.constructor === Array) {
225 328 if (arg.length < 3) {
226   - args[i] = '[' + this.stringifyArguments(arg) + ']';
  329 + result[i] = '[' + this.stringifyArguments(arg) + ']';
227 330 } else {
228   - args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']';
  331 + result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
229 332 }
230 333 } else if (arg.constructor === Object) {
231   - args[i] = '#object';
  334 + result[i] = '#object';
232 335 } else if (arg.constructor === Function) {
233   - args[i] = '#function';
  336 + result[i] = '#function';
234 337 } else if (arg.constructor === String) {
235   - args[i] = '"' + arg + '"';
  338 + result[i] = '"' + arg + '"';
  339 + } else if (arg.constructor === Number) {
  340 + result[i] = arg;
236 341 }
237 342 }
238 343 }
239   - return args.join(',');
  344 + return result.join(',');
240 345 },
241   -
  346 +
242 347 sourceCache: {},
243   -
  348 +
244 349 /**
245   - * @return the text from a given URL.
  350 + * @return the text from a given URL
246 351 */
247 352 ajax: function(url) {
248 353 var req = this.createXMLHTTPObject();
249   - if (!req) {
250   - return;
  354 + if (req) {
  355 + try {
  356 + req.open('GET', url, false);
  357 + //req.overrideMimeType('text/plain');
  358 + //req.overrideMimeType('text/javascript');
  359 + req.send(null);
  360 + //return req.status == 200 ? req.responseText : '';
  361 + return req.responseText;
  362 + } catch (e) {
  363 + }
251 364 }
252   - req.open('GET', url, false);
253   - // REMOVED FOR JS TEST.
254   - //req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
255   - req.send('');
256   - return req.responseText;
  365 + return '';
257 366 },
258   -
  367 +
259 368 /**
260 369 * Try XHR methods in order and store XHR factory.
261 370 *
... ... @@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = {
279 388 // Use memoization to cache the factory
280 389 this.createXMLHTTPObject = XMLHttpFactories[i];
281 390 return xmlhttp;
282   - } catch (e) {}
  391 + } catch (e) {
  392 + }
283 393 }
284 394 },
285 395  
... ... @@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = {
288 398 * via Ajax).
289 399 *
290 400 * @param url <String> source url
291   - * @return False if we need a cross-domain request
  401 + * @return <Boolean> False if we need a cross-domain request
292 402 */
293 403 isSameDomain: function(url) {
294   - return url.indexOf(location.hostname) !== -1;
  404 + return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
295 405 },
296   -
  406 +
297 407 /**
298 408 * Get source code from given URL if in the same domain.
299 409 *
... ... @@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = {
301 411 * @return <Array> Array of source code lines
302 412 */
303 413 getSource: function(url) {
  414 + // TODO reuse source from script tags?
304 415 if (!(url in this.sourceCache)) {
305 416 this.sourceCache[url] = this.ajax(url).split('\n');
306 417 }
307 418 return this.sourceCache[url];
308 419 },
309   -
310   - guessFunctions: function(stack) {
  420 +
  421 + guessAnonymousFunctions: function(stack) {
311 422 for (var i = 0; i < stack.length; ++i) {
312   - var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/;
313   - var frame = stack[i], m = reStack.exec(frame);
314   - if (m) {
315   - var file = m[1], lineno = m[4]; //m[7] is character position in Chrome
316   - if (file && this.isSameDomain(file) && lineno) {
317   - var functionName = this.guessFunctionName(file, lineno);
318   - stack[i] = frame.replace('{anonymous}', functionName);
  423 + var reStack = /\{anonymous\}\(.*\)@(.*)/,
  424 + reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
  425 + frame = stack[i], ref = reStack.exec(frame);
  426 +
  427 + if (ref) {
  428 + var m = reRef.exec(ref[1]);
  429 + if (m) { // If falsey, we did not get any file/line information
  430 + var file = m[1], lineno = m[2], charno = m[3] || 0;
  431 + if (file && this.isSameDomain(file) && lineno) {
  432 + var functionName = this.guessAnonymousFunction(file, lineno, charno);
  433 + stack[i] = frame.replace('{anonymous}', functionName);
  434 + }
319 435 }
320 436 }
321 437 }
322 438 return stack;
323 439 },
324   -
325   - guessFunctionName: function(url, lineNo) {
  440 +
  441 + guessAnonymousFunction: function(url, lineNo, charNo) {
  442 + var ret;
326 443 try {
327   - return this.guessFunctionNameFromLines(lineNo, this.getSource(url));
  444 + ret = this.findFunctionName(this.getSource(url), lineNo);
328 445 } catch (e) {
329   - return 'getSource failed with url: ' + url + ', exception: ' + e.toString();
  446 + ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
330 447 }
  448 + return ret;
331 449 },
332   -
333   - guessFunctionNameFromLines: function(lineNo, source) {
334   - var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
335   - var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
336   - // Walk backwards from the first line in the function until we find the line which
337   - // matches the pattern above, which is the function definition
338   - var line = "", maxLines = 10;
  450 +
  451 + findFunctionName: function(source, lineNo) {
  452 + // FIXME findFunctionName fails for compressed source
  453 + // (more than one function on the same line)
  454 + // function {name}({args}) m[1]=name m[2]=args
  455 + var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
  456 + // {name} = function ({args}) TODO args capture
  457 + // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
  458 + var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
  459 + // {name} = eval()
  460 + var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
  461 + // Walk backwards in the source lines until we find
  462 + // the line which matches one of the patterns above
  463 + var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
339 464 for (var i = 0; i < maxLines; ++i) {
340   - line = source[lineNo - i] + line;
341   - if (line !== undefined) {
342   - var m = reGuessFunction.exec(line);
  465 + // lineNo is 1-based, source[] is 0-based
  466 + line = source[lineNo - i - 1];
  467 + commentPos = line.indexOf('//');
  468 + if (commentPos >= 0) {
  469 + line = line.substr(0, commentPos);
  470 + }
  471 + // TODO check other types of comments? Commented code may lead to false positive
  472 + if (line) {
  473 + code = line + code;
  474 + m = reFunctionExpression.exec(code);
  475 + if (m && m[1]) {
  476 + return m[1];
  477 + }
  478 + m = reFunctionDeclaration.exec(code);
  479 + if (m && m[1]) {
  480 + //return m[1] + "(" + (m[2] || "") + ")";
  481 + return m[1];
  482 + }
  483 + m = reFunctionEvaluation.exec(code);
343 484 if (m && m[1]) {
344 485 return m[1];
345   - } else {
346   - m = reFunctionArgNames.exec(line);
347   - if (m && m[1]) {
348   - return m[1];
349   - }
350 486 }
351 487 }
352 488 }
... ... @@ -379,6 +515,11 @@ printStackTrace.implementation.prototype = {
379 515 '<project-root>{project_root}</project-root>' +
380 516 '<environment-name>{environment}</environment-name>' +
381 517 '</server-environment>' +
  518 + '<current-user>' +
  519 + '<id>{user_id}</id>' +
  520 + '<name>{user_name}</name>' +
  521 + '<email>{user_email}</email>' +
  522 + '</current-user>' +
382 523 '</notice>',
383 524 REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>',
384 525 REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>',
... ... @@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = {
411 552 "rootDirectory": "{project_root}",
412 553 "action": "{request_action}",
413 554  
414   - "userId": "{}",
415   - "userName": "{}",
416   - "userEmail": "{}",
  555 + "userId": "{user_id}",
  556 + "userName": "{user_name}",
  557 + "userEmail": "{user_email}",
417 558 },
418 559 "environment": {},
419 560 //"session": "",
... ... @@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = {
683 824 variable: 'outputFormat',
684 825 namespace: 'options'
685 826 }, {
  827 + methodName: 'setCurrentUser',
  828 + method: (function (value) {
  829 + for (var key in value) {
  830 + if (value.hasOwnProperty(key)) {
  831 + Config.xmlData['user_' + key] = value[key];
  832 + }
  833 + }
  834 + })
  835 + }, {
686 836 methodName: 'setTrackJQ',
687 837 variable: 'trackJQ',
688 838 namespace: 'options',
... ...