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,76 +47,112 @@ | ||
47 | // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 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 | * @cfg {Error} e The error to create a stacktrace from (optional) | 52 | * @cfg {Error} e The error to create a stacktrace from (optional) |
53 | * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions | 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 | function printStackTrace(options) { | 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 | printStackTrace.implementation.prototype = { | 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 | if (mode === 'other') { | 80 | if (mode === 'other') { |
80 | return this.other(arguments.callee); | 81 | return this.other(arguments.callee); |
81 | } else { | 82 | } else { |
82 | return this[mode](ex); | 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 | mode: function(e) { | 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 | } else if (e.stack) { | 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 | * Given a context, function name, and callback function, overwrite it so that it calls | 139 | * Given a context, function name, and callback function, overwrite it so that it calls |
104 | * printStackTrace() first with a callback and then runs the rest of the body. | 140 | * printStackTrace() first with a callback and then runs the rest of the body. |
105 | - * | 141 | + * |
106 | * @param {Object} context of execution (e.g. window) | 142 | * @param {Object} context of execution (e.g. window) |
107 | * @param {String} functionName to instrument | 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 | instrumentFunction: function(context, functionName, callback) { | 146 | instrumentFunction: function(context, functionName, callback) { |
111 | context = context || window; | 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 | * Given a context and function name of a function that has been | 157 | * Given a context and function name of a function that has been |
122 | * instrumented, revert the function to it's original (non-instrumented) | 158 | * instrumented, revert the function to it's original (non-instrumented) |
@@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = { | @@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = { | ||
128 | deinstrumentFunction: function(context, functionName) { | 164 | deinstrumentFunction: function(context, functionName) { |
129 | if (context[functionName].constructor === Function && | 165 | if (context[functionName].constructor === Function && |
130 | context[functionName]._instrumented && | 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 | * Given an Error object, return a formatted Array based on Chrome's stack string. | 173 | * Given an Error object, return a formatted Array based on Chrome's stack string. |
138 | - * | 174 | + * |
139 | * @param e - Error object to inspect | 175 | * @param e - Error object to inspect |
140 | * @return Array<String> of function calls, files and line numbers | 176 | * @return Array<String> of function calls, files and line numbers |
141 | */ | 177 | */ |
142 | chrome: function(e) { | 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 | * Given an Error object, return a formatted Array based on Firefox's stack string. | 215 | * Given an Error object, return a formatted Array based on Firefox's stack string. |
148 | - * | 216 | + * |
149 | * @param e - Error object to inspect | 217 | * @param e - Error object to inspect |
150 | * @return Array<String> of function calls, files and line numbers | 218 | * @return Array<String> of function calls, files and line numbers |
151 | */ | 219 | */ |
152 | firefox: function(e) { | 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 | * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. | 260 | * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. |
158 | - * | 261 | + * |
159 | * @param e - Error object to inspect | 262 | * @param e - Error object to inspect |
160 | * @return Array<String> of function calls, files and line numbers | 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 | other: function(curr) { | 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 | fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; | 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 | stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; | 305 | stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; |
205 | curr = curr.caller; | 306 | curr = curr.caller; |
206 | } | 307 | } |
207 | return stack; | 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 | stringifyArguments: function(args) { | 317 | stringifyArguments: function(args) { |
318 | + var result = []; | ||
319 | + var slice = Array.prototype.slice; | ||
217 | for (var i = 0; i < args.length; ++i) { | 320 | for (var i = 0; i < args.length; ++i) { |
218 | var arg = args[i]; | 321 | var arg = args[i]; |
219 | if (arg === undefined) { | 322 | if (arg === undefined) { |
220 | - args[i] = 'undefined'; | 323 | + result[i] = 'undefined'; |
221 | } else if (arg === null) { | 324 | } else if (arg === null) { |
222 | - args[i] = 'null'; | 325 | + result[i] = 'null'; |
223 | } else if (arg.constructor) { | 326 | } else if (arg.constructor) { |
224 | if (arg.constructor === Array) { | 327 | if (arg.constructor === Array) { |
225 | if (arg.length < 3) { | 328 | if (arg.length < 3) { |
226 | - args[i] = '[' + this.stringifyArguments(arg) + ']'; | 329 | + result[i] = '[' + this.stringifyArguments(arg) + ']'; |
227 | } else { | 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 | } else if (arg.constructor === Object) { | 333 | } else if (arg.constructor === Object) { |
231 | - args[i] = '#object'; | 334 | + result[i] = '#object'; |
232 | } else if (arg.constructor === Function) { | 335 | } else if (arg.constructor === Function) { |
233 | - args[i] = '#function'; | 336 | + result[i] = '#function'; |
234 | } else if (arg.constructor === String) { | 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 | sourceCache: {}, | 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 | ajax: function(url) { | 352 | ajax: function(url) { |
248 | var req = this.createXMLHTTPObject(); | 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 | * Try XHR methods in order and store XHR factory. | 369 | * Try XHR methods in order and store XHR factory. |
261 | * | 370 | * |
@@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = { | @@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = { | ||
279 | // Use memoization to cache the factory | 388 | // Use memoization to cache the factory |
280 | this.createXMLHTTPObject = XMLHttpFactories[i]; | 389 | this.createXMLHTTPObject = XMLHttpFactories[i]; |
281 | return xmlhttp; | 390 | return xmlhttp; |
282 | - } catch (e) {} | 391 | + } catch (e) { |
392 | + } | ||
283 | } | 393 | } |
284 | }, | 394 | }, |
285 | 395 | ||
@@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = { | @@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = { | ||
288 | * via Ajax). | 398 | * via Ajax). |
289 | * | 399 | * |
290 | * @param url <String> source url | 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 | isSameDomain: function(url) { | 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 | * Get source code from given URL if in the same domain. | 408 | * Get source code from given URL if in the same domain. |
299 | * | 409 | * |
@@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = { | @@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = { | ||
301 | * @return <Array> Array of source code lines | 411 | * @return <Array> Array of source code lines |
302 | */ | 412 | */ |
303 | getSource: function(url) { | 413 | getSource: function(url) { |
414 | + // TODO reuse source from script tags? | ||
304 | if (!(url in this.sourceCache)) { | 415 | if (!(url in this.sourceCache)) { |
305 | this.sourceCache[url] = this.ajax(url).split('\n'); | 416 | this.sourceCache[url] = this.ajax(url).split('\n'); |
306 | } | 417 | } |
307 | return this.sourceCache[url]; | 418 | return this.sourceCache[url]; |
308 | }, | 419 | }, |
309 | - | ||
310 | - guessFunctions: function(stack) { | 420 | + |
421 | + guessAnonymousFunctions: function(stack) { | ||
311 | for (var i = 0; i < stack.length; ++i) { | 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 | return stack; | 438 | return stack; |
323 | }, | 439 | }, |
324 | - | ||
325 | - guessFunctionName: function(url, lineNo) { | 440 | + |
441 | + guessAnonymousFunction: function(url, lineNo, charNo) { | ||
442 | + var ret; | ||
326 | try { | 443 | try { |
327 | - return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); | 444 | + ret = this.findFunctionName(this.getSource(url), lineNo); |
328 | } catch (e) { | 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 | for (var i = 0; i < maxLines; ++i) { | 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 | if (m && m[1]) { | 484 | if (m && m[1]) { |
344 | return m[1]; | 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,6 +515,11 @@ printStackTrace.implementation.prototype = { | ||
379 | '<project-root>{project_root}</project-root>' + | 515 | '<project-root>{project_root}</project-root>' + |
380 | '<environment-name>{environment}</environment-name>' + | 516 | '<environment-name>{environment}</environment-name>' + |
381 | '</server-environment>' + | 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 | '</notice>', | 523 | '</notice>', |
383 | REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>', | 524 | REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>', |
384 | REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>', | 525 | REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>', |
@@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = { | @@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = { | ||
411 | "rootDirectory": "{project_root}", | 552 | "rootDirectory": "{project_root}", |
412 | "action": "{request_action}", | 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 | "environment": {}, | 559 | "environment": {}, |
419 | //"session": "", | 560 | //"session": "", |
@@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = { | @@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = { | ||
683 | variable: 'outputFormat', | 824 | variable: 'outputFormat', |
684 | namespace: 'options' | 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 | methodName: 'setTrackJQ', | 836 | methodName: 'setTrackJQ', |
687 | variable: 'trackJQ', | 837 | variable: 'trackJQ', |
688 | namespace: 'options', | 838 | namespace: 'options', |