errorhandler.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Error handling utilities.
  16. *
  17. */
  18. goog.provide('goog.debug.ErrorHandler');
  19. goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');
  20. goog.require('goog.Disposable');
  21. goog.require('goog.asserts');
  22. goog.require('goog.debug');
  23. goog.require('goog.debug.EntryPointMonitor');
  24. goog.require('goog.debug.Error');
  25. goog.require('goog.debug.Trace');
  26. /**
  27. * The ErrorHandler can be used to to wrap functions with a try/catch
  28. * statement. If an exception is thrown, the given error handler function will
  29. * be called.
  30. *
  31. * When this object is disposed, it will stop handling exceptions and tracing.
  32. * It will also try to restore window.setTimeout and window.setInterval
  33. * if it wrapped them. Notice that in the general case, it is not technically
  34. * possible to remove the wrapper, because functions have no knowledge of
  35. * what they have been assigned to. So the app is responsible for other
  36. * forms of unwrapping.
  37. *
  38. * @param {Function} handler Handler for exceptions.
  39. * @constructor
  40. * @extends {goog.Disposable}
  41. * @implements {goog.debug.EntryPointMonitor}
  42. */
  43. goog.debug.ErrorHandler = function(handler) {
  44. goog.debug.ErrorHandler.base(this, 'constructor');
  45. /**
  46. * Handler for exceptions, which can do logging, reporting, etc.
  47. * @type {Function}
  48. * @private
  49. */
  50. this.errorHandlerFn_ = handler;
  51. /**
  52. * Whether errors should be wrapped in
  53. * goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.
  54. * @type {boolean}
  55. * @private
  56. */
  57. this.wrapErrors_ = true; // TODO(user) Change default.
  58. /**
  59. * Whether to add a prefix to all error messages. The prefix is
  60. * goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option
  61. * only has an effect if this.wrapErrors_ is set to false.
  62. * @type {boolean}
  63. * @private
  64. */
  65. this.prefixErrorMessages_ = false;
  66. };
  67. goog.inherits(goog.debug.ErrorHandler, goog.Disposable);
  68. /**
  69. * Whether to add tracers when instrumenting entry points.
  70. * @type {boolean}
  71. * @private
  72. */
  73. goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;
  74. /**
  75. * Enable tracers when instrumenting entry points.
  76. * @param {boolean} newVal See above.
  77. */
  78. goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions = function(
  79. newVal) {
  80. this.addTracersToProtectedFunctions_ = newVal;
  81. };
  82. /** @override */
  83. goog.debug.ErrorHandler.prototype.wrap = function(fn) {
  84. return this.protectEntryPoint(goog.asserts.assertFunction(fn));
  85. };
  86. /** @override */
  87. goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
  88. goog.asserts.assertFunction(fn);
  89. return fn[this.getFunctionIndex_(false)] || fn;
  90. };
  91. /**
  92. * Private helper function to return a span that can be clicked on to display
  93. * an alert with the current stack trace. Newlines are replaced with a
  94. * placeholder so that they will not be html-escaped.
  95. * @param {string} stackTrace The stack trace to create a span for.
  96. * @return {string} A span which can be clicked on to show the stack trace.
  97. * @private
  98. */
  99. goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {
  100. var buffer = [];
  101. buffer.push('##PE_STACK_START##');
  102. buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));
  103. buffer.push('##PE_STACK_END##');
  104. return buffer.join('');
  105. };
  106. /**
  107. * Get the index for a function. Used for internal indexing.
  108. * @param {boolean} wrapper True for the wrapper; false for the wrapped.
  109. * @return {string} The index where we should store the function in its
  110. * wrapper/wrapped function.
  111. * @private
  112. */
  113. goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
  114. return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
  115. };
  116. /**
  117. * Installs exception protection for an entry point function. When an exception
  118. * is thrown from a protected function, a handler will be invoked to handle it.
  119. *
  120. * @param {Function} fn An entry point function to be protected.
  121. * @return {!Function} A protected wrapper function that calls the entry point
  122. * function.
  123. */
  124. goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
  125. var protectedFnName = this.getFunctionIndex_(true);
  126. if (!fn[protectedFnName]) {
  127. var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
  128. wrapper[this.getFunctionIndex_(false)] = fn;
  129. }
  130. return fn[protectedFnName];
  131. };
  132. /**
  133. * Helps {@link #protectEntryPoint} by actually creating the protected
  134. * wrapper function, after {@link #protectEntryPoint} determines that one does
  135. * not already exist for the given function. Can be overriden by subclasses
  136. * that may want to implement different error handling, or add additional
  137. * entry point hooks.
  138. * @param {!Function} fn An entry point function to be protected.
  139. * @return {!Function} protected wrapper function.
  140. * @protected
  141. */
  142. goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
  143. var that = this;
  144. var tracers = this.addTracersToProtectedFunctions_;
  145. if (tracers) {
  146. var stackTrace = goog.debug.getStacktraceSimple(15);
  147. }
  148. var googDebugErrorHandlerProtectedFunction = function() {
  149. if (that.isDisposed()) {
  150. return fn.apply(this, arguments);
  151. }
  152. if (tracers) {
  153. var tracer = goog.debug.Trace.startTracer(
  154. 'protectedEntryPoint: ' + that.getStackTraceHolder_(stackTrace));
  155. }
  156. try {
  157. return fn.apply(this, arguments);
  158. } catch (e) {
  159. // Don't re-report errors that have already been handled by this code.
  160. var MESSAGE_PREFIX =
  161. goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX;
  162. if ((e && typeof e === 'object' && e.message &&
  163. e.message.indexOf(MESSAGE_PREFIX) == 0) ||
  164. (typeof e === 'string' && e.indexOf(MESSAGE_PREFIX) == 0)) {
  165. return;
  166. }
  167. that.errorHandlerFn_(e);
  168. if (!that.wrapErrors_) {
  169. // Add the prefix to the existing message.
  170. if (that.prefixErrorMessages_) {
  171. if (typeof e === 'object' && e && 'message' in e) {
  172. e.message = MESSAGE_PREFIX + e.message;
  173. } else {
  174. e = MESSAGE_PREFIX + e;
  175. }
  176. }
  177. if (goog.DEBUG) {
  178. // Work around for https://code.google.com/p/v8/issues/detail?id=2625
  179. // and https://code.google.com/p/chromium/issues/detail?id=237059
  180. // Custom errors and errors with custom stack traces show the wrong
  181. // stack trace
  182. // If it has a stack and Error.captureStackTrace is supported (only
  183. // supported in V8 as of May 2013) log the stack to the console.
  184. if (e && e.stack && Error.captureStackTrace &&
  185. goog.global['console']) {
  186. goog.global['console']['error'](e.message, e.stack);
  187. }
  188. }
  189. // Re-throw original error. This is great for debugging as it makes
  190. // browser JS dev consoles show the correct error and stack trace.
  191. throw e;
  192. }
  193. // Re-throw it since this may be expected by the caller.
  194. throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
  195. } finally {
  196. if (tracers) {
  197. goog.debug.Trace.stopTracer(tracer);
  198. }
  199. }
  200. };
  201. googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
  202. return googDebugErrorHandlerProtectedFunction;
  203. };
  204. // TODO(mknichel): Allow these functions to take in the window to protect.
  205. /**
  206. * Installs exception protection for window.setTimeout to handle exceptions.
  207. */
  208. goog.debug.ErrorHandler.prototype.protectWindowSetTimeout = function() {
  209. this.protectWindowFunctionsHelper_('setTimeout');
  210. };
  211. /**
  212. * Install exception protection for window.setInterval to handle exceptions.
  213. */
  214. goog.debug.ErrorHandler.prototype.protectWindowSetInterval = function() {
  215. this.protectWindowFunctionsHelper_('setInterval');
  216. };
  217. /**
  218. * Install exception protection for window.requestAnimationFrame to handle
  219. * exceptions.
  220. */
  221. goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =
  222. function() {
  223. var win = goog.getObjectByName('window');
  224. var fnNames = [
  225. 'requestAnimationFrame', 'mozRequestAnimationFrame', 'webkitAnimationFrame',
  226. 'msRequestAnimationFrame'
  227. ];
  228. for (var i = 0; i < fnNames.length; i++) {
  229. var fnName = fnNames[i];
  230. if (fnNames[i] in win) {
  231. this.protectWindowFunctionsHelper_(fnName);
  232. }
  233. }
  234. };
  235. /**
  236. * Helper function for protecting a function that causes a function to be
  237. * asynchronously called, for example setTimeout or requestAnimationFrame.
  238. * @param {string} fnName The name of the function to protect.
  239. * @private
  240. */
  241. goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ = function(
  242. fnName) {
  243. var win = goog.getObjectByName('window');
  244. var originalFn = win[fnName];
  245. var that = this;
  246. win[fnName] = function(fn, time) {
  247. // Don't try to protect strings. In theory, we could try to globalEval
  248. // the string, but this seems to lead to permission errors on IE6.
  249. if (goog.isString(fn)) {
  250. fn = goog.partial(goog.globalEval, fn);
  251. }
  252. arguments[0] = fn = that.protectEntryPoint(fn);
  253. // IE doesn't support .call for setInterval/setTimeout, but it
  254. // also doesn't care what "this" is, so we can just call the
  255. // original function directly
  256. if (originalFn.apply) {
  257. return originalFn.apply(this, arguments);
  258. } else {
  259. var callback = fn;
  260. if (arguments.length > 2) {
  261. var args = Array.prototype.slice.call(arguments, 2);
  262. callback = function() { fn.apply(this, args); };
  263. }
  264. return originalFn(callback, time);
  265. }
  266. };
  267. win[fnName][this.getFunctionIndex_(false)] = originalFn;
  268. };
  269. /**
  270. * Set whether to wrap errors that occur in protected functions in a
  271. * goog.debug.ErrorHandler.ProtectedFunctionError.
  272. * @param {boolean} wrapErrors Whether to wrap errors.
  273. */
  274. goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {
  275. this.wrapErrors_ = wrapErrors;
  276. };
  277. /**
  278. * Set whether to add a prefix to all error messages that occur in protected
  279. * functions.
  280. * @param {boolean} prefixErrorMessages Whether to add a prefix to error
  281. * messages.
  282. */
  283. goog.debug.ErrorHandler.prototype.setPrefixErrorMessages = function(
  284. prefixErrorMessages) {
  285. this.prefixErrorMessages_ = prefixErrorMessages;
  286. };
  287. /** @override */
  288. goog.debug.ErrorHandler.prototype.disposeInternal = function() {
  289. // Try to unwrap window.setTimeout and window.setInterval.
  290. var win = goog.getObjectByName('window');
  291. win.setTimeout = this.unwrap(win.setTimeout);
  292. win.setInterval = this.unwrap(win.setInterval);
  293. goog.debug.ErrorHandler.base(this, 'disposeInternal');
  294. };
  295. /**
  296. * Error thrown to the caller of a protected entry point if the entry point
  297. * throws an error.
  298. * @param {*} cause The error thrown by the entry point.
  299. * @constructor
  300. * @extends {goog.debug.Error}
  301. * @final
  302. */
  303. goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
  304. var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
  305. (cause && cause.message ? String(cause.message) : String(cause));
  306. goog.debug.ErrorHandler.ProtectedFunctionError.base(
  307. this, 'constructor', message);
  308. /**
  309. * The error thrown by the entry point.
  310. * @type {*}
  311. */
  312. this.cause = cause;
  313. var stack = cause && cause.stack;
  314. if (stack && goog.isString(stack)) {
  315. this.stack = /** @type {string} */ (stack);
  316. }
  317. };
  318. goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);
  319. /**
  320. * Text to prefix the message with.
  321. * @type {string}
  322. */
  323. goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
  324. 'Error in protected function: ';