Path: blob/trunk/third_party/closure/goog/debug/errorhandler.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Error handling utilities.16*17*/1819goog.provide('goog.debug.ErrorHandler');20goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');2122goog.require('goog.Disposable');23goog.require('goog.asserts');24goog.require('goog.debug');25goog.require('goog.debug.EntryPointMonitor');26goog.require('goog.debug.Error');27goog.require('goog.debug.Trace');28293031/**32* The ErrorHandler can be used to to wrap functions with a try/catch33* statement. If an exception is thrown, the given error handler function will34* be called.35*36* When this object is disposed, it will stop handling exceptions and tracing.37* It will also try to restore window.setTimeout and window.setInterval38* if it wrapped them. Notice that in the general case, it is not technically39* possible to remove the wrapper, because functions have no knowledge of40* what they have been assigned to. So the app is responsible for other41* forms of unwrapping.42*43* @param {Function} handler Handler for exceptions.44* @constructor45* @extends {goog.Disposable}46* @implements {goog.debug.EntryPointMonitor}47*/48goog.debug.ErrorHandler = function(handler) {49goog.debug.ErrorHandler.base(this, 'constructor');5051/**52* Handler for exceptions, which can do logging, reporting, etc.53* @type {Function}54* @private55*/56this.errorHandlerFn_ = handler;5758/**59* Whether errors should be wrapped in60* goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.61* @type {boolean}62* @private63*/64this.wrapErrors_ = true; // TODO(user) Change default.6566/**67* Whether to add a prefix to all error messages. The prefix is68* goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option69* only has an effect if this.wrapErrors_ is set to false.70* @type {boolean}71* @private72*/73this.prefixErrorMessages_ = false;74};75goog.inherits(goog.debug.ErrorHandler, goog.Disposable);767778/**79* Whether to add tracers when instrumenting entry points.80* @type {boolean}81* @private82*/83goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;848586/**87* Enable tracers when instrumenting entry points.88* @param {boolean} newVal See above.89*/90goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions = function(91newVal) {92this.addTracersToProtectedFunctions_ = newVal;93};949596/** @override */97goog.debug.ErrorHandler.prototype.wrap = function(fn) {98return this.protectEntryPoint(goog.asserts.assertFunction(fn));99};100101102/** @override */103goog.debug.ErrorHandler.prototype.unwrap = function(fn) {104goog.asserts.assertFunction(fn);105return fn[this.getFunctionIndex_(false)] || fn;106};107108109/**110* Private helper function to return a span that can be clicked on to display111* an alert with the current stack trace. Newlines are replaced with a112* placeholder so that they will not be html-escaped.113* @param {string} stackTrace The stack trace to create a span for.114* @return {string} A span which can be clicked on to show the stack trace.115* @private116*/117goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {118var buffer = [];119buffer.push('##PE_STACK_START##');120buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));121buffer.push('##PE_STACK_END##');122return buffer.join('');123};124125126/**127* Get the index for a function. Used for internal indexing.128* @param {boolean} wrapper True for the wrapper; false for the wrapped.129* @return {string} The index where we should store the function in its130* wrapper/wrapped function.131* @private132*/133goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {134return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';135};136137138/**139* Installs exception protection for an entry point function. When an exception140* is thrown from a protected function, a handler will be invoked to handle it.141*142* @param {Function} fn An entry point function to be protected.143* @return {!Function} A protected wrapper function that calls the entry point144* function.145*/146goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {147var protectedFnName = this.getFunctionIndex_(true);148if (!fn[protectedFnName]) {149var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);150wrapper[this.getFunctionIndex_(false)] = fn;151}152return fn[protectedFnName];153};154155156/**157* Helps {@link #protectEntryPoint} by actually creating the protected158* wrapper function, after {@link #protectEntryPoint} determines that one does159* not already exist for the given function. Can be overriden by subclasses160* that may want to implement different error handling, or add additional161* entry point hooks.162* @param {!Function} fn An entry point function to be protected.163* @return {!Function} protected wrapper function.164* @protected165*/166goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {167var that = this;168var tracers = this.addTracersToProtectedFunctions_;169if (tracers) {170var stackTrace = goog.debug.getStacktraceSimple(15);171}172var googDebugErrorHandlerProtectedFunction = function() {173if (that.isDisposed()) {174return fn.apply(this, arguments);175}176177if (tracers) {178var tracer = goog.debug.Trace.startTracer(179'protectedEntryPoint: ' + that.getStackTraceHolder_(stackTrace));180}181try {182return fn.apply(this, arguments);183} catch (e) {184// Don't re-report errors that have already been handled by this code.185var MESSAGE_PREFIX =186goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX;187if ((e && typeof e === 'object' && e.message &&188e.message.indexOf(MESSAGE_PREFIX) == 0) ||189(typeof e === 'string' && e.indexOf(MESSAGE_PREFIX) == 0)) {190return;191}192that.errorHandlerFn_(e);193if (!that.wrapErrors_) {194// Add the prefix to the existing message.195if (that.prefixErrorMessages_) {196if (typeof e === 'object' && e && 'message' in e) {197e.message = MESSAGE_PREFIX + e.message;198} else {199e = MESSAGE_PREFIX + e;200}201}202if (goog.DEBUG) {203// Work around for https://code.google.com/p/v8/issues/detail?id=2625204// and https://code.google.com/p/chromium/issues/detail?id=237059205// Custom errors and errors with custom stack traces show the wrong206// stack trace207// If it has a stack and Error.captureStackTrace is supported (only208// supported in V8 as of May 2013) log the stack to the console.209if (e && e.stack && Error.captureStackTrace &&210goog.global['console']) {211goog.global['console']['error'](e.message, e.stack);212}213}214// Re-throw original error. This is great for debugging as it makes215// browser JS dev consoles show the correct error and stack trace.216throw e;217}218// Re-throw it since this may be expected by the caller.219throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);220} finally {221if (tracers) {222goog.debug.Trace.stopTracer(tracer);223}224}225};226googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;227return googDebugErrorHandlerProtectedFunction;228};229230231// TODO(mknichel): Allow these functions to take in the window to protect.232/**233* Installs exception protection for window.setTimeout to handle exceptions.234*/235goog.debug.ErrorHandler.prototype.protectWindowSetTimeout = function() {236this.protectWindowFunctionsHelper_('setTimeout');237};238239240/**241* Install exception protection for window.setInterval to handle exceptions.242*/243goog.debug.ErrorHandler.prototype.protectWindowSetInterval = function() {244this.protectWindowFunctionsHelper_('setInterval');245};246247248/**249* Install exception protection for window.requestAnimationFrame to handle250* exceptions.251*/252goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =253function() {254var win = goog.getObjectByName('window');255var fnNames = [256'requestAnimationFrame', 'mozRequestAnimationFrame', 'webkitAnimationFrame',257'msRequestAnimationFrame'258];259for (var i = 0; i < fnNames.length; i++) {260var fnName = fnNames[i];261if (fnNames[i] in win) {262this.protectWindowFunctionsHelper_(fnName);263}264}265};266267268/**269* Helper function for protecting a function that causes a function to be270* asynchronously called, for example setTimeout or requestAnimationFrame.271* @param {string} fnName The name of the function to protect.272* @private273*/274goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ = function(275fnName) {276var win = goog.getObjectByName('window');277var originalFn = win[fnName];278var that = this;279win[fnName] = function(fn, time) {280// Don't try to protect strings. In theory, we could try to globalEval281// the string, but this seems to lead to permission errors on IE6.282if (goog.isString(fn)) {283fn = goog.partial(goog.globalEval, fn);284}285arguments[0] = fn = that.protectEntryPoint(fn);286287// IE doesn't support .call for setInterval/setTimeout, but it288// also doesn't care what "this" is, so we can just call the289// original function directly290if (originalFn.apply) {291return originalFn.apply(this, arguments);292} else {293var callback = fn;294if (arguments.length > 2) {295var args = Array.prototype.slice.call(arguments, 2);296callback = function() { fn.apply(this, args); };297}298return originalFn(callback, time);299}300};301win[fnName][this.getFunctionIndex_(false)] = originalFn;302};303304305/**306* Set whether to wrap errors that occur in protected functions in a307* goog.debug.ErrorHandler.ProtectedFunctionError.308* @param {boolean} wrapErrors Whether to wrap errors.309*/310goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {311this.wrapErrors_ = wrapErrors;312};313314315/**316* Set whether to add a prefix to all error messages that occur in protected317* functions.318* @param {boolean} prefixErrorMessages Whether to add a prefix to error319* messages.320*/321goog.debug.ErrorHandler.prototype.setPrefixErrorMessages = function(322prefixErrorMessages) {323this.prefixErrorMessages_ = prefixErrorMessages;324};325326327/** @override */328goog.debug.ErrorHandler.prototype.disposeInternal = function() {329// Try to unwrap window.setTimeout and window.setInterval.330var win = goog.getObjectByName('window');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) {348var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +349(cause && cause.message ? String(cause.message) : String(cause));350goog.debug.ErrorHandler.ProtectedFunctionError.base(351this, 'constructor', message);352353/**354* The error thrown by the entry point.355* @type {*}356*/357this.cause = cause;358359var stack = cause && cause.stack;360if (stack && goog.isString(stack)) {361this.stack = /** @type {string} */ (stack);362}363};364goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);365366367/**368* Text to prefix the message with.369* @type {string}370*/371goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =372'Error in protected function: ';373374375