tracekit.js 44 KB

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