Path: blob/trunk/third_party/closure/goog/debug/debug.js
2868 views
// Copyright 2006 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 Logging and debugging utilities.16*17* @see ../demos/debug.html18*/1920goog.provide('goog.debug');2122goog.require('goog.array');23goog.require('goog.userAgent');242526/** @define {boolean} Whether logging should be enabled. */27goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);282930/** @define {boolean} Whether to force "sloppy" stack building. */31goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);323334/**35* Catches onerror events fired by windows and similar objects.36* @param {function(Object)} logFunc The function to call with the error37* information.38* @param {boolean=} opt_cancel Whether to stop the error from reaching the39* browser.40* @param {Object=} opt_target Object that fires onerror events.41*/42goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {43var target = opt_target || goog.global;44var oldErrorHandler = target.onerror;45var retVal = !!opt_cancel;4647// Chrome interprets onerror return value backwards (http://crbug.com/92062)48// until it was fixed in webkit revision r94061 (Webkit 535.3). This49// workaround still needs to be skipped in Safari after the webkit change50// gets pushed out in Safari.51// See https://bugs.webkit.org/show_bug.cgi?id=6711952if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.3')) {53retVal = !retVal;54}5556/**57* New onerror handler for this target. This onerror handler follows the spec58* according to59* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors60* The spec was changed in August 2013 to support receiving column information61* and an error object for all scripts on the same origin or cross origin62* scripts with the proper headers. See63* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror64*65* @param {string} message The error message. For cross-origin errors, this66* will be scrubbed to just "Script error.". For new browsers that have67* updated to follow the latest spec, errors that come from origins that68* have proper cross origin headers will not be scrubbed.69* @param {string} url The URL of the script that caused the error. The URL70* will be scrubbed to "" for cross origin scripts unless the script has71* proper cross origin headers and the browser has updated to the latest72* spec.73* @param {number} line The line number in the script that the error74* occurred on.75* @param {number=} opt_col The optional column number that the error76* occurred on. Only browsers that have updated to the latest spec will77* include this.78* @param {Error=} opt_error The optional actual error object for this79* error that should include the stack. Only browsers that have updated80* to the latest spec will inlude this parameter.81* @return {boolean} Whether to prevent the error from reaching the browser.82*/83target.onerror = function(message, url, line, opt_col, opt_error) {84if (oldErrorHandler) {85oldErrorHandler(message, url, line, opt_col, opt_error);86}87logFunc({88message: message,89fileName: url,90line: line,91col: opt_col,92error: opt_error93});94return retVal;95};96};979899/**100* Creates a string representing an object and all its properties.101* @param {Object|null|undefined} obj Object to expose.102* @param {boolean=} opt_showFn Show the functions as well as the properties,103* default is false.104* @return {string} The string representation of {@code obj}.105*/106goog.debug.expose = function(obj, opt_showFn) {107if (typeof obj == 'undefined') {108return 'undefined';109}110if (obj == null) {111return 'NULL';112}113var str = [];114115for (var x in obj) {116if (!opt_showFn && goog.isFunction(obj[x])) {117continue;118}119var s = x + ' = ';120121try {122s += obj[x];123} catch (e) {124s += '*** ' + e + ' ***';125}126str.push(s);127}128return str.join('\n');129};130131132/**133* Creates a string representing a given primitive or object, and for an134* object, all its properties and nested objects. NOTE: The output will include135* Uids on all objects that were exposed. Any added Uids will be removed before136* returning.137* @param {*} obj Object to expose.138* @param {boolean=} opt_showFn Also show properties that are functions (by139* default, functions are omitted).140* @return {string} A string representation of {@code obj}.141*/142goog.debug.deepExpose = function(obj, opt_showFn) {143var str = [];144145// Track any objects where deepExpose added a Uid, so they can be cleaned up146// before return. We do this globally, rather than only on ancestors so that147// if the same object appears in the output, you can see it.148var uidsToCleanup = [];149var ancestorUids = {};150151var helper = function(obj, space) {152var nestspace = space + ' ';153154var indentMultiline = function(str) {155return str.replace(/\n/g, '\n' + space);156};157158159try {160if (!goog.isDef(obj)) {161str.push('undefined');162} else if (goog.isNull(obj)) {163str.push('NULL');164} else if (goog.isString(obj)) {165str.push('"' + indentMultiline(obj) + '"');166} else if (goog.isFunction(obj)) {167str.push(indentMultiline(String(obj)));168} else if (goog.isObject(obj)) {169// Add a Uid if needed. The struct calls implicitly adds them.170if (!goog.hasUid(obj)) {171uidsToCleanup.push(obj);172}173var uid = goog.getUid(obj);174if (ancestorUids[uid]) {175str.push('*** reference loop detected (id=' + uid + ') ***');176} else {177ancestorUids[uid] = true;178str.push('{');179for (var x in obj) {180if (!opt_showFn && goog.isFunction(obj[x])) {181continue;182}183str.push('\n');184str.push(nestspace);185str.push(x + ' = ');186helper(obj[x], nestspace);187}188str.push('\n' + space + '}');189delete ancestorUids[uid];190}191} else {192str.push(obj);193}194} catch (e) {195str.push('*** ' + e + ' ***');196}197};198199helper(obj, '');200201// Cleanup any Uids that were added by the deepExpose.202for (var i = 0; i < uidsToCleanup.length; i++) {203goog.removeUid(uidsToCleanup[i]);204}205206return str.join('');207};208209210/**211* Recursively outputs a nested array as a string.212* @param {Array<?>} arr The array.213* @return {string} String representing nested array.214*/215goog.debug.exposeArray = function(arr) {216var str = [];217for (var i = 0; i < arr.length; i++) {218if (goog.isArray(arr[i])) {219str.push(goog.debug.exposeArray(arr[i]));220} else {221str.push(arr[i]);222}223}224return '[ ' + str.join(', ') + ' ]';225};226227228/**229* Normalizes the error/exception object between browsers.230* @param {*} err Raw error object.231* @return {!{232* message: (?|undefined),233* name: (?|undefined),234* lineNumber: (?|undefined),235* fileName: (?|undefined),236* stack: (?|undefined)237* }} Normalized error object.238*/239goog.debug.normalizeErrorObject = function(err) {240var href = goog.getObjectByName('window.location.href');241if (goog.isString(err)) {242return {243'message': err,244'name': 'Unknown error',245'lineNumber': 'Not available',246'fileName': href,247'stack': 'Not available'248};249}250251var lineNumber, fileName;252var threwError = false;253254try {255lineNumber = err.lineNumber || err.line || 'Not available';256} catch (e) {257// Firefox 2 sometimes throws an error when accessing 'lineNumber':258// Message: Permission denied to get property UnnamedClass.lineNumber259lineNumber = 'Not available';260threwError = true;261}262263try {264fileName = err.fileName || err.filename || err.sourceURL ||265// $googDebugFname may be set before a call to eval to set the filename266// that the eval is supposed to present.267goog.global['$googDebugFname'] || href;268} catch (e) {269// Firefox 2 may also throw an error when accessing 'filename'.270fileName = 'Not available';271threwError = true;272}273274// The IE Error object contains only the name and the message.275// The Safari Error object uses the line and sourceURL fields.276if (threwError || !err.lineNumber || !err.fileName || !err.stack ||277!err.message || !err.name) {278return {279'message': err.message || 'Not available',280'name': err.name || 'UnknownError',281'lineNumber': lineNumber,282'fileName': fileName,283'stack': err.stack || 'Not available'284};285}286287// Standards error object288// Typed !Object. Should be a subtype of the return type, but it's not.289return /** @type {?} */ (err);290};291292293/**294* Converts an object to an Error using the object's toString if it's not295* already an Error, adds a stacktrace if there isn't one, and optionally adds296* an extra message.297* @param {*} err The original thrown error, object, or string.298* @param {string=} opt_message optional additional message to add to the299* error.300* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,301* it is converted to an Error which is enhanced and returned.302*/303goog.debug.enhanceError = function(err, opt_message) {304var error;305if (!(err instanceof Error)) {306error = Error(err);307if (Error.captureStackTrace) {308// Trim this function off the call stack, if we can.309Error.captureStackTrace(error, goog.debug.enhanceError);310}311} else {312error = err;313}314315if (!error.stack) {316error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);317}318if (opt_message) {319// find the first unoccupied 'messageX' property320var x = 0;321while (error['message' + x]) {322++x;323}324error['message' + x] = String(opt_message);325}326return error;327};328329330/**331* Gets the current stack trace. Simple and iterative - doesn't worry about332* catching circular references or getting the args.333* @param {number=} opt_depth Optional maximum depth to trace back to.334* @return {string} A string with the function names of all functions in the335* stack, separated by \n.336* @suppress {es5Strict}337*/338goog.debug.getStacktraceSimple = function(opt_depth) {339if (!goog.debug.FORCE_SLOPPY_STACKS) {340var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);341if (stack) {342return stack;343}344// NOTE: browsers that have strict mode support also have native "stack"345// properties. Fall-through for legacy browser support.346}347348var sb = [];349var fn = arguments.callee.caller;350var depth = 0;351352while (fn && (!opt_depth || depth < opt_depth)) {353sb.push(goog.debug.getFunctionName(fn));354sb.push('()\n');355356try {357fn = fn.caller;358} catch (e) {359sb.push('[exception trying to get caller]\n');360break;361}362depth++;363if (depth >= goog.debug.MAX_STACK_DEPTH) {364sb.push('[...long stack...]');365break;366}367}368if (opt_depth && depth >= opt_depth) {369sb.push('[...reached max depth limit...]');370} else {371sb.push('[end]');372}373374return sb.join('');375};376377378/**379* Max length of stack to try and output380* @type {number}381*/382goog.debug.MAX_STACK_DEPTH = 50;383384385/**386* @param {Function} fn The function to start getting the trace from.387* @return {?string}388* @private389*/390goog.debug.getNativeStackTrace_ = function(fn) {391var tempErr = new Error();392if (Error.captureStackTrace) {393Error.captureStackTrace(tempErr, fn);394return String(tempErr.stack);395} else {396// IE10, only adds stack traces when an exception is thrown.397try {398throw tempErr;399} catch (e) {400tempErr = e;401}402var stack = tempErr.stack;403if (stack) {404return String(stack);405}406}407return null;408};409410411/**412* Gets the current stack trace, either starting from the caller or starting413* from a specified function that's currently on the call stack.414* @param {?Function=} fn If provided, when collecting the stack trace all415* frames above the topmost call to this function, including that call,416* will be left out of the stack trace.417* @return {string} Stack trace.418* @suppress {es5Strict}419*/420goog.debug.getStacktrace = function(fn) {421var stack;422if (!goog.debug.FORCE_SLOPPY_STACKS) {423// Try to get the stack trace from the environment if it is available.424var contextFn = fn || goog.debug.getStacktrace;425stack = goog.debug.getNativeStackTrace_(contextFn);426}427if (!stack) {428// NOTE: browsers that have strict mode support also have native "stack"429// properties. This function will throw in strict mode.430stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);431}432return stack;433};434435436/**437* Private helper for getStacktrace().438* @param {?Function} fn If provided, when collecting the stack trace all439* frames above the topmost call to this function, including that call,440* will be left out of the stack trace.441* @param {Array<!Function>} visited List of functions visited so far.442* @return {string} Stack trace starting from function fn.443* @suppress {es5Strict}444* @private445*/446goog.debug.getStacktraceHelper_ = function(fn, visited) {447var sb = [];448449// Circular reference, certain functions like bind seem to cause a recursive450// loop so we need to catch circular references451if (goog.array.contains(visited, fn)) {452sb.push('[...circular reference...]');453454// Traverse the call stack until function not found or max depth is reached455} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {456sb.push(goog.debug.getFunctionName(fn) + '(');457var args = fn.arguments;458// Args may be null for some special functions such as host objects or eval.459for (var i = 0; args && i < args.length; i++) {460if (i > 0) {461sb.push(', ');462}463var argDesc;464var arg = args[i];465switch (typeof arg) {466case 'object':467argDesc = arg ? 'object' : 'null';468break;469470case 'string':471argDesc = arg;472break;473474case 'number':475argDesc = String(arg);476break;477478case 'boolean':479argDesc = arg ? 'true' : 'false';480break;481482case 'function':483argDesc = goog.debug.getFunctionName(arg);484argDesc = argDesc ? argDesc : '[fn]';485break;486487case 'undefined':488default:489argDesc = typeof arg;490break;491}492493if (argDesc.length > 40) {494argDesc = argDesc.substr(0, 40) + '...';495}496sb.push(argDesc);497}498visited.push(fn);499sb.push(')\n');500501try {502sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));503} catch (e) {504sb.push('[exception trying to get caller]\n');505}506507} else if (fn) {508sb.push('[...long stack...]');509} else {510sb.push('[end]');511}512return sb.join('');513};514515516/**517* Set a custom function name resolver.518* @param {function(Function): string} resolver Resolves functions to their519* names.520*/521goog.debug.setFunctionResolver = function(resolver) {522goog.debug.fnNameResolver_ = resolver;523};524525526/**527* Gets a function name528* @param {Function} fn Function to get name of.529* @return {string} Function's name.530*/531goog.debug.getFunctionName = function(fn) {532if (goog.debug.fnNameCache_[fn]) {533return goog.debug.fnNameCache_[fn];534}535if (goog.debug.fnNameResolver_) {536var name = goog.debug.fnNameResolver_(fn);537if (name) {538goog.debug.fnNameCache_[fn] = name;539return name;540}541}542543// Heuristically determine function name based on code.544var functionSource = String(fn);545if (!goog.debug.fnNameCache_[functionSource]) {546var matches = /function ([^\(]+)/.exec(functionSource);547if (matches) {548var method = matches[1];549goog.debug.fnNameCache_[functionSource] = method;550} else {551goog.debug.fnNameCache_[functionSource] = '[Anonymous]';552}553}554555return goog.debug.fnNameCache_[functionSource];556};557558559/**560* Makes whitespace visible by replacing it with printable characters.561* This is useful in finding diffrences between the expected and the actual562* output strings of a testcase.563* @param {string} string whose whitespace needs to be made visible.564* @return {string} string whose whitespace is made visible.565*/566goog.debug.makeWhitespaceVisible = function(string) {567return string.replace(/ /g, '[_]')568.replace(/\f/g, '[f]')569.replace(/\n/g, '[n]\n')570.replace(/\r/g, '[r]')571.replace(/\t/g, '[t]');572};573574575/**576* Returns the type of a value. If a constructor is passed, and a suitable577* string cannot be found, 'unknown type name' will be returned.578*579* <p>Forked rather than moved from {@link goog.asserts.getType_}580* to avoid adding a dependency to goog.asserts.581* @param {*} value A constructor, object, or primitive.582* @return {string} The best display name for the value, or 'unknown type name'.583*/584goog.debug.runtimeType = function(value) {585if (value instanceof Function) {586return value.displayName || value.name || 'unknown type name';587} else if (value instanceof Object) {588return value.constructor.displayName || value.constructor.name ||589Object.prototype.toString.call(value);590} else {591return value === null ? 'null' : typeof value;592}593};594595596/**597* Hash map for storing function names that have already been looked up.598* @type {Object}599* @private600*/601goog.debug.fnNameCache_ = {};602603604/**605* Resolves functions to their names. Resolved function names will be cached.606* @type {function(Function):string}607* @private608*/609goog.debug.fnNameResolver_;610611612