squash_javascript 1.0.3 → 2.0.5

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