tracekit.js 38 KB

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