squash_javascript 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1132 @@
1
+ /**! @preserve
2
+ * Original CrashKit code Copyright (c) 2009 Andrey Tarantsov, YourSway LLC (http://crashkitapp.appspot.com/)
3
+ * Copyright (c) 2010 Colin Snover (http://zetafleet.com)
4
+ *
5
+ * Released under the ISC License.
6
+ * http://opensource.org/licenses/isc-license.txt
7
+ */
8
+ var TraceKit = {};
9
+
10
+ /**
11
+ * TraceKit.report: cross-browser processing of unhandled exceptions
12
+ *
13
+ * Syntax:
14
+ * TraceKit.report.subscribe(function(stackInfo) { ... })
15
+ * TraceKit.report.unsubscribe(function(stackInfo) { ... })
16
+ * TraceKit.report(exception)
17
+ * try { ...code... } catch(ex) { TraceKit.report(ex); }
18
+ *
19
+ * Supports:
20
+ * - Firefox: full stack trace with line numbers, plus column number
21
+ * on top frame; column number is not guaranteed
22
+ * - Opera: full stack trace with line and column numbers
23
+ * - Chrome: full stack trace with line and column numbers
24
+ * - Safari: line and column number for the top frame only; some frames
25
+ * may be missing, and column number is not guaranteed
26
+ * - IE: line and column number for the top frame only; some frames
27
+ * may be missing, and column number is not guaranteed
28
+ *
29
+ * In theory, TraceKit should work on all of the following versions:
30
+ * - IE5.5+ (only 8.0 tested)
31
+ * - Firefox 0.9+ (only 3.5+ tested)
32
+ * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
33
+ * Exceptions Have Stacktrace to be enabled in opera:config)
34
+ * - Safari 3+ (only 4+ tested)
35
+ * - Chrome 1+ (only 5+ tested)
36
+ * - Konqueror 3.5+ (untested)
37
+ *
38
+ * Requires TraceKit.computeStackTrace.
39
+ *
40
+ * Tries to catch all unhandled exceptions and report them to the
41
+ * subscribed handlers. Please note that TraceKit.report will rethrow the
42
+ * exception. This is REQUIRED in order to get a useful stack trace in IE.
43
+ * If the exception does not reach the top of the browser, you will only
44
+ * get a stack trace from the point where TraceKit.report was called.
45
+ *
46
+ * Handlers receive a stackInfo object as described in the
47
+ * TraceKit.computeStackTrace docs.
48
+ */
49
+ TraceKit.report = (function () {
50
+ var handlers = [],
51
+ lastException = null,
52
+ lastExceptionStack = null;
53
+
54
+ /**
55
+ * Add a crash handler.
56
+ * @param {Function} handler
57
+ */
58
+ function subscribe(handler) {
59
+ handlers.push(handler);
60
+ }
61
+
62
+ /**
63
+ * Remove a crash handler.
64
+ * @param {Function} handler
65
+ */
66
+ function unsubscribe(handler) {
67
+ for (var i = handlers.length - 1; i >= 0; --i) {
68
+ if (handlers[i] === handler) {
69
+ handlers.splice(i, 1);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Dispatch stack information to all handlers.
76
+ * @param {Object.<string, *>} stack
77
+ */
78
+ function notifyHandlers(stack) {
79
+ var exception = null;
80
+ for (var i in handlers) {
81
+ if (handlers.hasOwnProperty(i)) {
82
+ try {
83
+ handlers[i](stack);
84
+ }
85
+ catch (inner) {
86
+ exception = inner;
87
+ }
88
+ }
89
+ }
90
+
91
+ if (exception) {
92
+ throw exception;
93
+ }
94
+ }
95
+
96
+ var _oldOnerrorHandler = window.onerror;
97
+
98
+ /**
99
+ * Ensures all global unhandled exceptions are recorded.
100
+ * Supported by Gecko and IE.
101
+ * @param {string} message Error message.
102
+ * @param {string} url URL of script that generated the exception.
103
+ * @param {(number|string)} lineNo The line number at which the error
104
+ * occurred.
105
+ */
106
+ window.onerror = function (message, url, lineNo) {
107
+ var stack = null;
108
+
109
+ if (lastExceptionStack) {
110
+ TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
111
+ stack = lastExceptionStack;
112
+ lastExceptionStack = null;
113
+ lastException = null;
114
+ }
115
+ else {
116
+ var location = { 'url': url, 'line': lineNo };
117
+ location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
118
+ location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
119
+ stack = { 'mode': 'onerror', 'message': message, 'stack': [ location ] };
120
+ }
121
+
122
+ notifyHandlers(stack);
123
+
124
+ if (_oldOnerrorHandler) {
125
+ return _oldOnerrorHandler.apply(this, arguments);
126
+ }
127
+
128
+ return false;
129
+ };
130
+
131
+ /**
132
+ * Reports an unhandled Error to TraceKit.
133
+ * @param {Error} ex
134
+ */
135
+ function report(ex) {
136
+ if (lastExceptionStack) {
137
+ if (lastException === ex) {
138
+ return; // already caught by an inner catch block, ignore
139
+ }
140
+ else {
141
+ var s = lastExceptionStack;
142
+ lastExceptionStack = null;
143
+ lastException = null;
144
+ notifyHandlers(s);
145
+ }
146
+ }
147
+
148
+ var stack = TraceKit.computeStackTrace(ex);
149
+ lastExceptionStack = stack;
150
+ lastException = ex;
151
+
152
+ // If the stack trace is incomplete, wait for 2 seconds for
153
+ // slow slow IE to see if onerror occurs or not before reporting
154
+ // this exception; otherwise, we will end up with an incomplete
155
+ // stack trace
156
+ window.setTimeout(function () {
157
+ if (lastException === ex) {
158
+ lastExceptionStack = null;
159
+ lastException = null;
160
+ notifyHandlers(stack);
161
+ }
162
+ }, (stack.incomplete ? 2000 : 0));
163
+
164
+ throw ex; // re-throw to propagate to the top level (and cause window.onerror)
165
+ }
166
+
167
+ report.subscribe = subscribe;
168
+ report.unsubscribe = unsubscribe;
169
+ return report;
170
+ }());
171
+
172
+ /**
173
+ * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
174
+ *
175
+ * Syntax:
176
+ * s = TraceKit.computeStackTrace.ofCaller([depth])
177
+ * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
178
+ * Returns:
179
+ * s.name - exception name
180
+ * s.message - exception message
181
+ * s.stack[i].url - JavaScript or HTML file URL
182
+ * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
183
+ * s.stack[i].args - arguments passed to the function, if known
184
+ * s.stack[i].line - line number, if known
185
+ * s.stack[i].column - column number, if known
186
+ * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
187
+ * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace
188
+ *
189
+ * Supports:
190
+ * - Firefox: full stack trace with line numbers and unreliable column
191
+ * number on top frame
192
+ * - Opera 10: full stack trace with line and column numbers
193
+ * - Opera 9-: full stack trace with line numbers
194
+ * - Chrome: full stack trace with line and column numbers
195
+ * - Safari: line and column number for the topmost stacktrace element
196
+ * only
197
+ * - IE: no line numbers whatsoever
198
+ *
199
+ * Tries to guess names of anonymous functions by looking for assignments
200
+ * in the source code. In IE and Safari, we have to guess source file names
201
+ * by searching for function bodies inside all page scripts. This will not
202
+ * work for scripts that are loaded cross-domain.
203
+ * Here be dragons: some function names may be guessed incorrectly, and
204
+ * duplicate functions may be mismatched.
205
+ *
206
+ * TraceKit.computeStackTrace should only be used for tracing purposes.
207
+ * Logging of unhandled exceptions should be done with TraceKit.report,
208
+ * which builds on top of TraceKit.computeStackTrace and provides better
209
+ * IE support by utilizing the window.onerror event to retrieve information
210
+ * about the top of the stack.
211
+ *
212
+ * Note: In IE and Safari, no stack trace is recorded on the Error object,
213
+ * so computeStackTrace instead walks its *own* chain of callers.
214
+ * This means that:
215
+ * * in Safari, some methods may be missing from the stack trace;
216
+ * * in IE, the topmost function in the stack trace will always be the
217
+ * caller of computeStackTrace.
218
+ *
219
+ * This is okay for tracing (because you are likely to be calling
220
+ * computeStackTrace from the function you want to be the topmost element
221
+ * of the stack trace anyway), but not okay for logging unhandled
222
+ * exceptions (because your catch block will likely be far away from the
223
+ * inner function that actually caused the exception).
224
+ *
225
+ * Tracing example:
226
+ * function trace(message) {
227
+ * var stackInfo = TraceKit.computeStackTrace.ofCaller();
228
+ * var data = message + "\n";
229
+ * for(var i in stackInfo.stack) {
230
+ * var item = stackInfo.stack[i];
231
+ * data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
232
+ * }
233
+ * if (window.console)
234
+ * console.info(data);
235
+ * else
236
+ * alert(data);
237
+ * }
238
+ */
239
+ TraceKit.computeStackTrace = (function () {
240
+ var debug = false, sourceCache = {};
241
+
242
+ /**
243
+ * Attempts to retrieve source code via XMLHttpRequest, which is used
244
+ * to look up anonymous function names.
245
+ * @param {string} url URL of source code.
246
+ * @return {string} Source contents.
247
+ */
248
+ function loadSource(url) {
249
+ try {
250
+ if (XMLHttpRequest === undefined) { // IE 5.x-6.x:
251
+ XMLHttpRequest = function () {
252
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch(e) {}
253
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch(e) {}
254
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {}
255
+ try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {}
256
+ throw new Error('No XHR.');
257
+ };
258
+ }
259
+
260
+ var request = new XMLHttpRequest();
261
+ request.open('GET', url, false);
262
+ request.send('');
263
+ return request.responseText;
264
+ }
265
+ catch (e) {
266
+ return '';
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Retrieves source code from the source code cache.
272
+ * @param {string} url URL of source code.
273
+ * @return {Array.<string>} Source contents.
274
+ */
275
+ function getSource(url) {
276
+ if (!sourceCache.hasOwnProperty(url)) {
277
+ // URL needs to be able to fetched within the acceptable domain. Otherwise,
278
+ // cross-domain errors will be triggered.
279
+ var source;
280
+ var urlTokens = url.split('/');
281
+ if (urlTokens.length > 2 && urlTokens[2] === document.domain) {
282
+ source = loadSource(url);
283
+ } else {
284
+ source = [];
285
+ }
286
+ sourceCache[url] = source.length ? source.split("\n") : [];
287
+ }
288
+
289
+ return sourceCache[url];
290
+ }
291
+
292
+ /**
293
+ * Tries to use an externally loaded copy of source code to determine
294
+ * the name of a function by looking at the name of the variable it was
295
+ * assigned to, if any.
296
+ * @param {string} url URL of source code.
297
+ * @param {(string|number)} lineNo Line number in source code.
298
+ * @return {string} The function name, if discoverable.
299
+ */
300
+ function guessFunctionName(url, lineNo) {
301
+ var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
302
+ reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
303
+ line = '',
304
+ maxLines = 10,
305
+ source = getSource(url),
306
+ m;
307
+
308
+ if (!source.length) {
309
+ return '?';
310
+ }
311
+
312
+ // Walk backwards from the first line in the function until we find the line which
313
+ // matches the pattern above, which is the function definition
314
+ for (var i = 0; i < maxLines; ++i) {
315
+ line = source[lineNo - i] + line;
316
+
317
+ if (line !== undefined) {
318
+ if ((m = reGuessFunction.exec(line))) {
319
+ return m[1];
320
+ }
321
+ else if ((m = reFunctionArgNames.exec(line))) {
322
+ return m[1];
323
+ }
324
+ }
325
+ }
326
+
327
+ return '?';
328
+ }
329
+
330
+ /**
331
+ * Retrieves the surrounding lines from where an exception occurred.
332
+ * @param {string} url URL of source code.
333
+ * @param {(string|number)} line Line number in source code to centre
334
+ * around for context.
335
+ * @return {?Array.<string>} Lines of source code.
336
+ */
337
+ function gatherContext(url, line) {
338
+ var source = getSource(url),
339
+ context = [],
340
+ hasContext = false;
341
+
342
+ if (!source.length) {
343
+ return null;
344
+ }
345
+
346
+ line -= 1; // convert to 0-based index
347
+
348
+ for (var i = line - 2, j = line + 2; i < j; ++i) {
349
+ context.push(source[i]);
350
+ if (source[i] !== undefined) {
351
+ hasContext = true;
352
+ }
353
+ }
354
+
355
+ return hasContext ? context : null;
356
+ }
357
+
358
+ /**
359
+ * Escapes special characters, except for whitespace, in a string to be
360
+ * used inside a regular expression as a string literal.
361
+ * @param {string} text The string.
362
+ * @return {string} The escaped string literal.
363
+ */
364
+ function escapeRegExp(text) {
365
+ return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
366
+ }
367
+
368
+ /**
369
+ * Escapes special characters in a string to be used inside a regular
370
+ * expression as a string literal. Also ensures that HTML entities will
371
+ * be matched the same as their literal friends.
372
+ * @param {string} body The string.
373
+ * @return {string} The escaped string.
374
+ */
375
+ function escapeCodeAsRegExpForMatchingInsideHTML(body) {
376
+ return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)')
377
+ .replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
378
+ }
379
+
380
+ /**
381
+ * Determines where a code fragment occurs in the source code.
382
+ * @param {RegExp} re The function definition.
383
+ * @param {Array.<string>} urls A list of URLs to search.
384
+ * @return {?Object.<string, (string|number)>} An object containing
385
+ * the url, line, and column number of the defined function.
386
+ */
387
+ function findSourceInUrls(re, urls) {
388
+ var source, m;
389
+ for (var i = 0, j = urls.length; i < j; ++i) {
390
+ // console.log('searching', urls[i]);
391
+ if ((source = getSource(urls[i])).length) {
392
+ source = source.join("\n");
393
+ if ((m = re.exec(source))) {
394
+ // console.log('Found function in ' + urls[i]);
395
+
396
+ return {
397
+ 'url': urls[i],
398
+ 'line': source.substring(0, m.index).split("\n").length,
399
+ 'column': m.index - source.lastIndexOf("\n", m.index) - 1
400
+ };
401
+ }
402
+ }
403
+ }
404
+
405
+ // console.log('no match');
406
+
407
+ return null;
408
+ }
409
+
410
+ /**
411
+ * Determines at which column a code fragment occurs on a line of the
412
+ * source code.
413
+ * @param {string} fragment The code fragment.
414
+ * @param {string} url The URL to search.
415
+ * @param {(string|number)} line The line number to examine.
416
+ * @return {?number} The column number.
417
+ */
418
+ function findSourceInLine(fragment, url, line) {
419
+ var source = getSource(url),
420
+ re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
421
+ m;
422
+
423
+ line -= 1;
424
+
425
+ if (source && source.length > line && (m = re.exec(source[line]))) {
426
+ return m.index;
427
+ }
428
+
429
+ return null;
430
+ }
431
+
432
+ /**
433
+ * Determines where a function was defined within the source code.
434
+ * @param {(Function|string)} func A function reference or serialized
435
+ * function definition.
436
+ * @return {?Object.<string, (string|number)>} An object containing
437
+ * the url, line, and column number of the defined function.
438
+ */
439
+ function findSourceByFunctionBody(func) {
440
+ var urls = [ window.location.href ],
441
+ scripts = document.getElementsByTagName("script"),
442
+ body,
443
+ code = '' + func,
444
+ codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
445
+ eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
446
+ re,
447
+ parts,
448
+ result;
449
+
450
+ for (var i = 0; i < scripts.length; ++i) {
451
+ var script = scripts[i];
452
+ if (script.src) {
453
+ urls.push(script.src);
454
+ }
455
+ }
456
+
457
+ if (!(parts = codeRE.exec(code))) {
458
+ re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
459
+ }
460
+
461
+ // not sure if this is really necessary, but I don’t have a test
462
+ // corpus large enough to confirm that and it was in the original.
463
+ else {
464
+ var name = parts[1] ? '\\s+' + parts[1] : '',
465
+ args = parts[2].split(",").join("\\s*,\\s*");
466
+
467
+ body = escapeRegExp(parts[3])
468
+ .replace(/;$/, ';?') // semicolon is inserted if the function ends with a comment
469
+ .replace(/\s+/g, '\\s+');
470
+ re = new RegExp("function" + name + "\\s*\\(\\s*" + args + "\\s*\\)\\s*{\\s*" + body + "\\s*}");
471
+ }
472
+
473
+ // look for a normal function definition
474
+ if ((result = findSourceInUrls(re, urls))) {
475
+ return result;
476
+ }
477
+
478
+ // look for an old-school event handler function
479
+ if ((parts = eventRE.exec(code))) {
480
+ var event = parts[1];
481
+ body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
482
+
483
+ // look for a function defined in HTML as an onXXX handler
484
+ re = new RegExp("on" + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
485
+
486
+ if ((result = findSourceInUrls(re, urls[0]))) {
487
+ return result;
488
+ }
489
+
490
+ // look for ???
491
+ re = new RegExp(body);
492
+
493
+ if ((result = findSourceInUrls(re, urls))) {
494
+ return result;
495
+ }
496
+ }
497
+
498
+ return null;
499
+ }
500
+
501
+ // Contents of Exception in various browsers.
502
+ //
503
+ // SAFARI:
504
+ // ex.message = Can't find variable: qq
505
+ // ex.line = 59
506
+ // ex.sourceId = 580238192
507
+ // ex.sourceURL = http://...
508
+ // ex.expressionBeginOffset = 96
509
+ // ex.expressionCaretOffset = 98
510
+ // ex.expressionEndOffset = 98
511
+ // ex.name = ReferenceError
512
+ //
513
+ // FIREFOX:
514
+ // ex.message = qq is not defined
515
+ // ex.fileName = http://...
516
+ // ex.lineNumber = 59
517
+ // ex.stack = ...stack trace... (see the example below)
518
+ // ex.name = ReferenceError
519
+ //
520
+ // CHROME:
521
+ // ex.message = qq is not defined
522
+ // ex.name = ReferenceError
523
+ // ex.type = not_defined
524
+ // ex.arguments = ['aa']
525
+ // ex.stack = ...stack trace...
526
+ //
527
+ // INTERNET EXPLORER:
528
+ // ex.message = ...
529
+ // ex.name = ReferenceError
530
+ //
531
+ // OPERA:
532
+ // ex.message = ...message... (see the example below)
533
+ // ex.name = ReferenceError
534
+ // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
535
+ // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
536
+
537
+ /**
538
+ * Computes stack trace information from the stack property.
539
+ * Chrome and Gecko use this property.
540
+ * @param {Error} ex
541
+ * @return {?Object.<string, *>} Stack trace information.
542
+ */
543
+ function computeStackTraceFromStackProp(ex) {
544
+ if (!ex.stack) {
545
+ return null;
546
+ }
547
+
548
+ var chrome = /^\s*at (\S+) \(((?:file|http):.*?):(\d+)(?::(\d+))?\)\s*$/i,
549
+ gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http).*?):(\d+)(?::(\d+))?\s*$/i,
550
+ lines = ex.stack.split("\n"),
551
+ stack = [],
552
+ parts,
553
+ element,
554
+ reference = /^(.*) is undefined$/.exec(ex.message);
555
+
556
+ for (var i = 0, j = lines.length; i < j; ++i) {
557
+ if ((parts = gecko.exec(lines[i]))) {
558
+ element = { 'url': parts[3], 'func': parts[1], 'args': parts[2] ? parts[2].split(',') : '', 'line': +parts[4], 'column': parts[5] ? +parts[5] : null };
559
+ }
560
+ else if ((parts = chrome.exec(lines[i]))) {
561
+ element = { 'url': parts[2], 'func': parts[1], 'line': +parts[3], 'column': parts[4] ? +parts[4] : null };
562
+ }
563
+ else {
564
+ continue;
565
+ }
566
+
567
+ if (!element.func && element.line) {
568
+ element.func = guessFunctionName(element.url, element.line);
569
+ }
570
+
571
+ if (element.line) {
572
+ element.context = gatherContext(element.url, element.line);
573
+ }
574
+
575
+ stack.push(element);
576
+ }
577
+
578
+ if (stack[0] && stack[0].line && !stack[0].column && reference) {
579
+ stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
580
+ }
581
+
582
+ if (!stack.length) {
583
+ return null;
584
+ }
585
+
586
+ return {
587
+ 'mode': 'stack',
588
+ 'name': ex.name,
589
+ 'message': ex.message,
590
+ 'stack': stack
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Computes stack trace information from the stacktrace property.
596
+ * Opera 10 uses this property.
597
+ * @param {Error} ex
598
+ * @return {?Object.<string, *>} Stack trace information.
599
+ */
600
+ function computeStackTraceFromStacktraceProp(ex) {
601
+ // Access and store the stacktrace property before doing ANYTHING
602
+ // else to it because Opera is not very good at providing it
603
+ // reliably in other circumstances.
604
+ var stacktrace = ex.stacktrace;
605
+
606
+ var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,
607
+ lines = stacktrace.split("\n"),
608
+ stack = [],
609
+ parts;
610
+
611
+ for (var i = 0, j = lines.length; i < j; i += 2) {
612
+ if ((parts = testRE.exec(lines[i]))) {
613
+ var element = { 'line': +parts[1], 'column': +parts[2], 'func': parts[3] || parts[4], 'args': parts[5] ? parts[5].split(',') : [], 'url': parts[6] };
614
+
615
+ if (!element.func && element.line) {
616
+ element.func = guessFunctionName(element.url, element.line);
617
+ }
618
+ if (element.line) {
619
+ try {
620
+ element.context = gatherContext(element.url, element.line);
621
+ }
622
+ catch (exc) {}
623
+ }
624
+
625
+ if (!element.context) {
626
+ element.context = [ lines[i + 1] ];
627
+ }
628
+
629
+ stack.push(element);
630
+ }
631
+ }
632
+
633
+ if (!stack.length) {
634
+ return null;
635
+ }
636
+
637
+ return {
638
+ 'mode': 'stacktrace',
639
+ 'name': ex.name,
640
+ 'message': ex.message,
641
+ 'stack': stack
642
+ };
643
+ }
644
+
645
+ /**
646
+ * NOT TESTED.
647
+ * Computes stack trace information from an error message that includes
648
+ * the stack trace.
649
+ * Opera 9 and earlier use this method if the option to show stack
650
+ * traces is turned on in opera:config.
651
+ * @param {Error} ex
652
+ * @return {?Object.<string, *>} Stack information.
653
+ */
654
+ function computeStackTraceFromOperaMultiLineMessage(ex) {
655
+ // Opera includes a stack trace into the exception message. An example is:
656
+ //
657
+ // Statement on line 3: Undefined variable: undefinedFunc
658
+ // Backtrace:
659
+ // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
660
+ // undefinedFunc(a);
661
+ // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
662
+ // zzz(x, y, z);
663
+ // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
664
+ // yyy(a, a, a);
665
+ // Line 1 of function script
666
+ // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
667
+ // ...
668
+
669
+ var lines = ex.message.split('\n');
670
+ if (lines.length < 4) {
671
+ return null;
672
+ }
673
+
674
+ var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http)\S+)(?:: in function (\S+))?\s*$/i,
675
+ lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http)\S+)(?:: in function (\S+))?\s*$/i,
676
+ lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
677
+ stack = [],
678
+ scripts = document.getElementsByTagName('script'),
679
+ inlineScriptBlocks = [],
680
+ parts,
681
+ i,
682
+ len,
683
+ source;
684
+
685
+ for (i in scripts) {
686
+ if (scripts.hasOwnProperty(i) && !scripts[i].src) {
687
+ inlineScriptBlocks.push(scripts[i]);
688
+ }
689
+ }
690
+
691
+ for (i = 2, len = lines.length; i < len; i += 2) {
692
+ var item = null;
693
+ if ((parts = lineRE1.exec(lines[i]))) {
694
+ item = { 'url': parts[2], 'func': parts[3], 'line': +parts[1] };
695
+ }
696
+ else if ((parts = lineRE2.exec(lines[i]))) {
697
+ item = { 'url': parts[3], 'func': parts[4] };
698
+ var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
699
+ var script = inlineScriptBlocks[parts[2] - 1];
700
+ if (script) {
701
+ source = getSource(item.url);
702
+ if (source) {
703
+ source = source.join("\n");
704
+ var pos = source.indexOf(script.innerText);
705
+ if (pos >= 0) {
706
+ item.line = relativeLine + source.substring(0, pos).split("\n").length;
707
+ }
708
+ }
709
+ }
710
+ }
711
+ else if ((parts = lineRE3.exec(lines[i]))) {
712
+ var url = window.location.href.replace(/#.*$/, ''), line = parts[1];
713
+ var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1]));
714
+ source = findSourceInUrls(re, [url]);
715
+ item = { 'url': url, 'line': source ? source.line : line, 'func': '' };
716
+ }
717
+
718
+ if (item) {
719
+ if (!item.func) {
720
+ item.func = guessFunctionName(item.url, item.line);
721
+ }
722
+ var context = gatherContext(item.url, item.line);
723
+ var midline = (context ? context[Math.floor(context.length / 2)] : null);
724
+ if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) {
725
+ item.context = context;
726
+ }
727
+ else {
728
+ // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
729
+ item.context = [lines[i + 1]];
730
+ }
731
+ stack.push(item);
732
+ }
733
+ }
734
+ if (!stack.length) {
735
+ return null; // could not parse multiline exception message as Opera stack trace
736
+ }
737
+
738
+ return {
739
+ 'mode': 'multiline',
740
+ 'name': ex.name,
741
+ 'message': lines[0],
742
+ 'stack': stack
743
+ };
744
+ }
745
+
746
+ /**
747
+ * Adds information about the first frame to incomplete stack traces.
748
+ * Safari and IE require this to get complete data on the first frame.
749
+ * @param {Object.<string, *>} stackInfo Stack trace information from
750
+ * one of the compute* methods.
751
+ * @param {string} url The URL of the script that caused an error.
752
+ * @param {(number|string)} lineNo The line number of the script that
753
+ * caused an error.
754
+ * @param {string=} message The error generated by the browser, which
755
+ * hopefully contains the name of the object that caused the error.
756
+ * @return {boolean} Whether or not the stack information was
757
+ * augmented.
758
+ */
759
+ function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
760
+ var initial = {
761
+ 'url': url,
762
+ 'line': lineNo
763
+ };
764
+
765
+ if (initial.url && initial.line) {
766
+ stackInfo.incomplete = false;
767
+
768
+ if (!initial.func) {
769
+ initial.func = guessFunctionName(initial.url, initial.line);
770
+ }
771
+
772
+ if (!initial.context) {
773
+ initial.context = gatherContext(initial.url, initial.line);
774
+ }
775
+
776
+ var reference = / '([^']+)' /.exec(message);
777
+ if (reference) {
778
+ initial.column = findSourceInLine(reference[1], initial.url, initial.line);
779
+ }
780
+
781
+ if (stackInfo.stack.length > 0) {
782
+ if (stackInfo.stack[0].url === initial.url) {
783
+ if (stackInfo.stack[0].line === initial.line) {
784
+ return false; // already in stack trace
785
+ }
786
+ else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
787
+ stackInfo.stack[0].line = initial.line;
788
+ stackInfo.stack[0].context = initial.context;
789
+ return false;
790
+ }
791
+ }
792
+ }
793
+
794
+ stackInfo.stack.unshift(initial);
795
+ stackInfo.partial = true;
796
+ return true;
797
+ }
798
+ else {
799
+ stackInfo.incomplete = true;
800
+ }
801
+
802
+ return false;
803
+ }
804
+
805
+ /**
806
+ * Computes stack trace information by walking the arguments.caller
807
+ * chain at the time the exception occurred. This will cause earlier
808
+ * frames to be missed but is the only way to get any stack trace in
809
+ * Safari and IE. The top frame is restored by
810
+ * {@link augmentStackTraceWithInitialElement}.
811
+ * @param {Error} ex
812
+ * @return {?Object.<string, *>} Stack trace information.
813
+ */
814
+ function computeStackTraceByWalkingCallerChain(ex, depth) {
815
+ var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
816
+ stack = [],
817
+ funcs = {},
818
+ recursion = false,
819
+ parts,
820
+ item,
821
+ source;
822
+
823
+ for (var curr = arguments.callee.caller; curr && !recursion; curr = curr.caller) {
824
+ if (curr === computeStackTrace || curr === TraceKit.report) {
825
+ // console.log('skipping internal function');
826
+ continue;
827
+ }
828
+
829
+ item = {
830
+ 'url': null,
831
+ 'func': '?',
832
+ 'line': null,
833
+ 'column': null
834
+ };
835
+
836
+ if (curr.name) {
837
+ item.func = curr.name;
838
+ }
839
+ else if ((parts = functionName.exec(curr.toString()))) {
840
+ item.func = parts[1];
841
+ }
842
+
843
+ if ((source = findSourceByFunctionBody(curr))) {
844
+ item.url = source.url;
845
+ item.line = source.line;
846
+
847
+ if (item.func === '?') {
848
+ item.func = guessFunctionName(item.url, item.line);
849
+ }
850
+
851
+ var reference = / '([^']+)' /.exec(ex.message || ex.description);
852
+ if (reference) {
853
+ item.column = findSourceInLine(reference[1], source.url, source.line);
854
+ }
855
+ }
856
+
857
+ if (funcs['' + curr]) {
858
+ item.recursion = true;
859
+ }
860
+
861
+ stack.push(item);
862
+ }
863
+
864
+ if (depth) {
865
+ // console.log('depth is ' + depth);
866
+ // console.log('stack is ' + stack.length);
867
+ stack.splice(0, depth);
868
+ }
869
+
870
+ var result = { 'mode': 'callers', 'name': ex.name, 'message': ex.message, 'stack': stack };
871
+ augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
872
+ return result;
873
+ }
874
+
875
+ /**
876
+ * Computes a stack trace for an exception.
877
+ * @param {Error} ex
878
+ * @param {(string|number)=} depth
879
+ */
880
+ function computeStackTrace(ex, depth) {
881
+ var stack = null;
882
+ depth = (depth === undefined ? 0 : +depth);
883
+
884
+ try {
885
+ // This must be tried first because Opera 10 *destroys*
886
+ // its stacktrace property if you try to access the stack
887
+ // property first!!
888
+ stack = computeStackTraceFromStacktraceProp(ex);
889
+ if (stack) {
890
+ return stack;
891
+ }
892
+ }
893
+ catch (e) {
894
+ if (debug) {
895
+ throw e;
896
+ }
897
+ }
898
+
899
+ try {
900
+ stack = computeStackTraceFromStackProp(ex);
901
+ if (stack) {
902
+ return stack;
903
+ }
904
+ }
905
+ catch (e) {
906
+ if (debug) {
907
+ throw e;
908
+ }
909
+ }
910
+
911
+ try {
912
+ stack = computeStackTraceFromOperaMultiLineMessage(ex);
913
+ if (stack) {
914
+ return stack;
915
+ }
916
+ }
917
+ catch (e) {
918
+ if (debug) {
919
+ throw e;
920
+ }
921
+ }
922
+
923
+ try {
924
+ stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
925
+ if (stack) {
926
+ return stack;
927
+ }
928
+ }
929
+ catch (e) {
930
+ if (debug) {
931
+ throw e;
932
+ }
933
+ }
934
+
935
+ return { 'mode': 'failed' };
936
+ }
937
+
938
+ /**
939
+ * Logs a stacktrace starting from the previous call and working down.
940
+ * @param {(number|string)=} depth How many frames deep to trace.
941
+ * @return {Object.<string, *>} Stack trace information.
942
+ */
943
+ function computeStackTraceOfCaller(depth) {
944
+ depth = (depth === undefined ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
945
+ try {
946
+ (0)();
947
+ }
948
+ catch (ex) {
949
+ return computeStackTrace(ex, depth + 1);
950
+ }
951
+
952
+ return null;
953
+ }
954
+
955
+ computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
956
+ computeStackTrace.guessFunctionName = guessFunctionName;
957
+ computeStackTrace.gatherContext = gatherContext;
958
+ computeStackTrace.ofCaller = computeStackTraceOfCaller;
959
+
960
+ return computeStackTrace;
961
+ }());
962
+
963
+ /**
964
+ * Extends support for global error handling for asynchronous browser
965
+ * functions.
966
+ */
967
+ (function (w) {
968
+ var slice = Array.prototype.slice,
969
+ _oldSetTimeout = w.setTimeout;
970
+
971
+ w.setTimeout = function () {
972
+ var args = slice.call(arguments, 0),
973
+ _oldCallback = args[0];
974
+
975
+ args[0] = function () {
976
+ try {
977
+ _oldCallback.apply(this, arguments);
978
+ }
979
+ catch (e) {
980
+ TraceKit.report(e);
981
+ throw e;
982
+ }
983
+ };
984
+
985
+ return _oldSetTimeout.apply(this, args);
986
+ };
987
+
988
+ // If you are reading this, you should know that setInterval is
989
+ // bad! Don’t use it!
990
+ // http://zetafleet.com/blog/why-i-consider-setinterval-harmful
991
+ var _oldSetInterval = w.setInterval;
992
+ w.setInterval = function () {
993
+ var args = slice.call(arguments, 0),
994
+ _oldCallback = args[0];
995
+
996
+ args[0] = function () {
997
+ try {
998
+ _oldCallback.apply(this, arguments);
999
+ }
1000
+ catch (e) {
1001
+ TraceKit.report(e);
1002
+ throw e;
1003
+ }
1004
+ };
1005
+
1006
+ return _oldSetInterval.apply(this, args);
1007
+ };
1008
+ }(window));
1009
+
1010
+ /**
1011
+ * Extended support for backtraces and global error handling for most
1012
+ * asynchronous jQuery functions.
1013
+ */
1014
+
1015
+ /* HACK - disabled because it causes inexplicable behavior
1016
+ (function ($) {
1017
+
1018
+ // quit if jQuery isn't on the page
1019
+ if (!$) {
1020
+ return;
1021
+ }
1022
+
1023
+ var _oldEventAdd = $.event.add;
1024
+ $.event.add = function (elem, types, handler, data) {
1025
+ var _handler;
1026
+
1027
+ if (handler.handler) {
1028
+ _handler = handler.handler;
1029
+ handler.handler = function () {
1030
+ try {
1031
+ return _handler.apply(this, arguments);
1032
+ }
1033
+ catch (e) {
1034
+ TraceKit.report(e);
1035
+ throw e;
1036
+ }
1037
+ };
1038
+ }
1039
+ else {
1040
+ _handler = handler;
1041
+ handler = function () {
1042
+ try {
1043
+ return _handler.apply(this, arguments);
1044
+ }
1045
+ catch (e) {
1046
+ TraceKit.report(e);
1047
+ throw e;
1048
+ }
1049
+ };
1050
+ }
1051
+
1052
+ // If the handler we are attaching doesn’t have the same guid as
1053
+ // the original, it will never be removed when someone tries to
1054
+ // unbind the original function later. Technically as a result of
1055
+ // this our guids are no longer globally unique, but whatever, that
1056
+ // never hurt anybody RIGHT?!
1057
+ if (_handler.guid) {
1058
+ handler.guid = _handler.guid;
1059
+ }
1060
+ else {
1061
+ handler.guid = _handler.guid = $.guid++;
1062
+ }
1063
+
1064
+ return _oldEventAdd.call(this, elem, types, handler, data);
1065
+ };
1066
+
1067
+ var _oldReady = $.fn.ready;
1068
+ $.fn.ready = function (fn) {
1069
+ var _fn = function () {
1070
+ try {
1071
+ return fn.apply(this, arguments);
1072
+ }
1073
+ catch (e) {
1074
+ TraceKit.report(e);
1075
+ throw e;
1076
+ }
1077
+ };
1078
+
1079
+ return _oldReady.call(this, _fn);
1080
+ };
1081
+
1082
+ var _oldAjax = $.ajax;
1083
+ $.fn.ajax = function (s) {
1084
+ if ($.isFunction(s.complete)) {
1085
+ var _oldComplete = s.complete;
1086
+ s.complete = function () {
1087
+ try {
1088
+ return _oldComplete.apply(this, arguments);
1089
+ }
1090
+ catch (e) {
1091
+ TraceKit.report(e);
1092
+ throw e;
1093
+ }
1094
+ };
1095
+ }
1096
+
1097
+ if ($.isFunction(s.error)) {
1098
+ var _oldError = s.error;
1099
+ s.error = function () {
1100
+ try {
1101
+ return _oldError.apply(this, arguments);
1102
+ }
1103
+ catch (e) {
1104
+ TraceKit.report(e);
1105
+ throw e;
1106
+ }
1107
+ };
1108
+ }
1109
+
1110
+ if ($.isFunction(s.success)) {
1111
+ var _oldSuccess = s.success;
1112
+ s.success = function () {
1113
+ try {
1114
+ return _oldSuccess.apply(this, arguments);
1115
+ }
1116
+ catch (e) {
1117
+ TraceKit.report(e);
1118
+ throw e;
1119
+ }
1120
+ };
1121
+ }
1122
+
1123
+ try {
1124
+ return _oldAjax.call(this, s);
1125
+ }
1126
+ catch (e) {
1127
+ TraceKit.report(e);
1128
+ throw e;
1129
+ }
1130
+ };
1131
+ }(window.jQuery));
1132
+ */