squash_javascript 1.0.3 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  */