Commit 2776db24f43723f3e4ac64732fc29938bce93559
Exists in
master
and in
1 other branch
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', | ... | ... |