Path: blob/trunk/third_party/closure/goog/debug/errorhandler.js
3987 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Error handling utilities.8*/910goog.provide('goog.debug.ErrorHandler');11goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');1213goog.require('goog.Disposable');14goog.require('goog.asserts');15goog.require('goog.debug.EntryPointMonitor');16goog.require('goog.debug.Error');17181920/**21* The ErrorHandler can be used to to wrap functions with a try/catch22* statement. If an exception is thrown, the given error handler function will23* be called.24*25* When this object is disposed, it will stop handling exceptions and tracing.26* It will also try to restore window.setTimeout and window.setInterval27* if it wrapped them. Notice that in the general case, it is not technically28* possible to remove the wrapper, because functions have no knowledge of29* what they have been assigned to. So the app is responsible for other30* forms of unwrapping.31*32* @param {Function} handler Handler for exceptions.33* @constructor34* @extends {goog.Disposable}35* @implements {goog.debug.EntryPointMonitor}36*/37goog.debug.ErrorHandler = function(handler) {38'use strict';39goog.debug.ErrorHandler.base(this, 'constructor');4041/**42* Handler for exceptions, which can do logging, reporting, etc.43* @type {Function}44* @private45*/46this.errorHandlerFn_ = handler;4748/**49* Whether errors should be wrapped in50* goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.51* @type {boolean}52* @private53*/54this.wrapErrors_ = true; // TODO(malteubl) Change default.5556/**57* Whether to add a prefix to all error messages. The prefix is58* goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option59* only has an effect if this.wrapErrors_ is set to false.60* @type {boolean}61* @private62*/63this.prefixErrorMessages_ = false;64};65goog.inherits(goog.debug.ErrorHandler, goog.Disposable);666768/** @override */69goog.debug.ErrorHandler.prototype.wrap = function(fn) {70'use strict';71return this.protectEntryPoint(goog.asserts.assertFunction(fn));72};737475/** @override */76goog.debug.ErrorHandler.prototype.unwrap = function(fn) {77'use strict';78goog.asserts.assertFunction(fn);79return fn[this.getFunctionIndex_(false)] || fn;80};818283/**84* Get the index for a function. Used for internal indexing.85* @param {boolean} wrapper True for the wrapper; false for the wrapped.86* @return {string} The index where we should store the function in its87* wrapper/wrapped function.88* @private89*/90goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {91'use strict';92return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';93};949596/**97* Installs exception protection for an entry point function. When an exception98* is thrown from a protected function, a handler will be invoked to handle it.99*100* @param {!Function} fn An entry point function to be protected.101* @return {!Function} A protected wrapper function that calls the entry point102* function.103*/104goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {105'use strict';106var protectedFnName = this.getFunctionIndex_(true);107if (!fn[protectedFnName]) {108var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);109wrapper[this.getFunctionIndex_(false)] = fn;110}111return fn[protectedFnName];112};113114115/**116* Helps {@link #protectEntryPoint} by actually creating the protected117* wrapper function, after {@link #protectEntryPoint} determines that one does118* not already exist for the given function. Can be overridden by subclasses119* that may want to implement different error handling, or add additional120* entry point hooks.121* @param {!Function} fn An entry point function to be protected.122* @return {!Function} protected wrapper function.123* @protected124*/125goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {126'use strict';127var that = this;128var googDebugErrorHandlerProtectedFunction = function() {129'use strict';130var self = /** @type {?} */ (this);131if (that.isDisposed()) {132return fn.apply(self, arguments);133}134135try {136return fn.apply(self, arguments);137} catch (e) {138that.handleError_(e);139}140};141googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;142return googDebugErrorHandlerProtectedFunction;143};144145146/**147* Internal error handler.148* @param {?} e The error string or an Error-like object.149* @private150*/151goog.debug.ErrorHandler.prototype.handleError_ = function(e) {152'use strict';153// Don't re-report errors that have already been handled by this code.154var MESSAGE_PREFIX =155goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX;156if ((e && typeof e === 'object' && typeof e.message === 'string' &&157e.message.indexOf(MESSAGE_PREFIX) == 0) ||158(typeof e === 'string' && e.indexOf(MESSAGE_PREFIX) == 0)) {159return;160}161this.errorHandlerFn_(e);162if (!this.wrapErrors_) {163// Add the prefix to the existing message.164if (this.prefixErrorMessages_) {165if (typeof e === 'object' && e && typeof e.message === 'string') {166/** @type {{message}} */ (e).message = MESSAGE_PREFIX + e.message;167} else {168e = MESSAGE_PREFIX + e;169}170}171if (goog.DEBUG) {172// Work around for https://code.google.com/p/v8/issues/detail?id=2625173// and https://code.google.com/p/chromium/issues/detail?id=237059174// Custom errors and errors with custom stack traces show the wrong175// stack trace176// If it has a stack and Error.captureStackTrace is supported (only177// supported in V8 as of May 2013) log the stack to the console.178if (e && typeof e.stack === 'string' && Error.captureStackTrace &&179goog.global['console']) {180goog.global['console']['error'](e.message, e.stack);181}182}183// Re-throw original error. This is great for debugging as it makes184// browser JS dev consoles show the correct error and stack trace.185throw e;186}187// Re-throw it since this may be expected by the caller.188throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);189};190191192// TODO(mknichel): Allow these functions to take in the window to protect.193/**194* Installs exception protection for window.setTimeout to handle exceptions.195*/196goog.debug.ErrorHandler.prototype.protectWindowSetTimeout = function() {197'use strict';198this.protectWindowFunctionsHelper_('setTimeout');199};200201202/**203* Install exception protection for window.setInterval to handle exceptions.204*/205goog.debug.ErrorHandler.prototype.protectWindowSetInterval = function() {206'use strict';207this.protectWindowFunctionsHelper_('setInterval');208};209210211/**212* Install an unhandledrejection event listener that reports rejected promises.213* Note: this will only work with Chrome 49+ and friends, but so far is the only214* way to report uncaught errors in aysnc/await functions.215* @param {!Window=} win the window to instrument, defaults to current window216*/217goog.debug.ErrorHandler.prototype.catchUnhandledRejections = function(win) {218'use strict';219win = win || goog.global['window'] || goog.global['globalThis'];220if ('onunhandledrejection' in win) {221win.onunhandledrejection = (event) => {222// event.reason contains the rejection reason. When an Error is223// thrown, this is the Error object. If it is undefined, create a new224// error object.225const e =226event && event.reason ? event.reason : new Error('uncaught error');227this.handleError_(e);228};229}230};231232233/**234* Install exception protection for window.requestAnimationFrame to handle235* exceptions.236*/237goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =238function() {239'use strict';240const win = goog.global['window'] || goog.global['globalThis'];241var fnNames = [242'requestAnimationFrame', 'mozRequestAnimationFrame', 'webkitAnimationFrame',243'msRequestAnimationFrame'244];245for (var i = 0; i < fnNames.length; i++) {246var fnName = fnNames[i];247if (fnNames[i] in win) {248this.protectWindowFunctionsHelper_(fnName);249}250}251};252253254/**255* Helper function for protecting a function that causes a function to be256* asynchronously called, for example setTimeout or requestAnimationFrame.257* @param {string} fnName The name of the function to protect.258* @private259*/260goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ = function(261fnName) {262'use strict';263const win = goog.global['window'] || goog.global['globalThis'];264var originalFn = win[fnName];265if (!originalFn) throw new Error(fnName + ' not on global?');266var that = this;267win[fnName] = function(fn, time) {268'use strict';269if (typeof fn === 'string') {270fn = goog.partial(goog.globalEval, fn);271}272// The first arg (function to call) might be undefined or null, and273// protectEntryPoint doesn't like this.274// If the fn was a string, the call to goog.partial above always returns a275// function, so they will always be called protected.276// (e.g. setTimeout(undefined, 1000))277if (fn) {278arguments[0] = fn = that.protectEntryPoint(fn);279}280281// IE doesn't support .call for setInterval/setTimeout, but it282// also doesn't care what "this" is, so we can just call the283// original function directly284if (originalFn.apply) {285return originalFn.apply(/** @type {?} */ (this), arguments);286} else {287var callback = fn;288if (arguments.length > 2) {289var args = Array.prototype.slice.call(arguments, 2);290callback = function() {291'use strict';292fn.apply(/** @type {?} */ (this), args);293};294}295return originalFn(callback, time);296}297};298win[fnName][this.getFunctionIndex_(false)] = originalFn;299};300301302/**303* Set whether to wrap errors that occur in protected functions in a304* goog.debug.ErrorHandler.ProtectedFunctionError.305* @param {boolean} wrapErrors Whether to wrap errors.306*/307goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {308'use strict';309this.wrapErrors_ = wrapErrors;310};311312313/**314* Set whether to add a prefix to all error messages that occur in protected315* functions.316* @param {boolean} prefixErrorMessages Whether to add a prefix to error317* messages.318*/319goog.debug.ErrorHandler.prototype.setPrefixErrorMessages = function(320prefixErrorMessages) {321'use strict';322this.prefixErrorMessages_ = prefixErrorMessages;323};324325326/** @override */327goog.debug.ErrorHandler.prototype.disposeInternal = function() {328'use strict';329// Try to unwrap window.setTimeout and window.setInterval.330const win = goog.global['window'] || goog.global['globalThis'];331win.setTimeout = this.unwrap(win.setTimeout);332win.setInterval = this.unwrap(win.setInterval);333334goog.debug.ErrorHandler.base(this, 'disposeInternal');335};336337338339/**340* Error thrown to the caller of a protected entry point if the entry point341* throws an error.342* @param {*} cause The error thrown by the entry point.343* @constructor344* @extends {goog.debug.Error}345* @final346*/347goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {348'use strict';349/** @suppress {missingProperties} message may not be defined. */350var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +351(cause && cause.message ? String(cause.message) : String(cause));352goog.debug.ErrorHandler.ProtectedFunctionError.base(353this, 'constructor', message, /** @type {?} */ (cause));354355/** @suppress {missingProperties} stack may not be defined. */356var stack = cause && cause.stack;357if (stack && typeof stack === 'string') {358this.stack = /** @type {string} */ (stack);359}360};361goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);362363364/**365* Text to prefix the message with.366* @type {string}367*/368goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =369'Error in protected function: ';370371372