Path: blob/trunk/third_party/closure/goog/debug/debug.js
3991 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Logging and debugging utilities.8*9* @see ../demos/debug.html10*/1112goog.provide('goog.debug');1314goog.require('goog.array');15goog.require('goog.debug.errorcontext');1617goog.require('goog.utils');181920/** @define {boolean} Whether logging should be enabled. */21goog.debug.LOGGING_ENABLED =22goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);232425/** @define {boolean} Whether to force "sloppy" stack building. */26goog.debug.FORCE_SLOPPY_STACKS =27goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);282930/**31* @define {boolean} TODO(user): Remove this hack once bug is resolved.32*/33goog.debug.CHECK_FOR_THROWN_EVENT =34goog.define('goog.debug.CHECK_FOR_THROWN_EVENT', false);35363738/**39* Catches onerror events fired by windows and similar objects.40* @param {function(Object)} logFunc The function to call with the error41* information.42* @param {boolean=} opt_cancel Whether to stop the error from reaching the43* browser.44* @param {Object=} opt_target Object that fires onerror events.45* @suppress {strictMissingProperties} onerror is not defined as a property46* on Object.47*/48goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {49'use strict';50var target = opt_target || goog.global;51var oldErrorHandler = target.onerror;52var retVal = !!opt_cancel;5354/**55* New onerror handler for this target. This onerror handler follows the spec56* according to57* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors58* The spec was changed in August 2013 to support receiving column information59* and an error object for all scripts on the same origin or cross origin60* scripts with the proper headers. See61* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror62*63* @param {string} message The error message. For cross-origin errors, this64* will be scrubbed to just "Script error.". For new browsers that have65* updated to follow the latest spec, errors that come from origins that66* have proper cross origin headers will not be scrubbed.67* @param {string} url The URL of the script that caused the error. The URL68* will be scrubbed to "" for cross origin scripts unless the script has69* proper cross origin headers and the browser has updated to the latest70* spec.71* @param {number} line The line number in the script that the error72* occurred on.73* @param {number=} opt_col The optional column number that the error74* occurred on. Only browsers that have updated to the latest spec will75* include this.76* @param {Error=} opt_error The optional actual error object for this77* error that should include the stack. Only browsers that have updated78* to the latest spec will inlude this parameter.79* @return {boolean} Whether to prevent the error from reaching the browser.80*/81target.onerror = function(message, url, line, opt_col, opt_error) {82'use strict';83if (oldErrorHandler) {84oldErrorHandler(message, url, line, opt_col, opt_error);85}86logFunc({87message: message,88fileName: url,89line: line,90lineNumber: 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 `obj`.105*/106goog.debug.expose = function(obj, opt_showFn) {107'use strict';108if (typeof obj == 'undefined') {109return 'undefined';110}111if (obj == null) {112return 'NULL';113}114var str = [];115116for (var x in obj) {117if (!opt_showFn && typeof obj[x] === 'function') {118continue;119}120var s = x + ' = ';121122try {123s += obj[x];124} catch (e) {125s += '*** ' + e + ' ***';126}127str.push(s);128}129return str.join('\n');130};131132133/**134* Creates a string representing a given primitive or object, and for an135* object, all its properties and nested objects. NOTE: The output will include136* Uids on all objects that were exposed. Any added Uids will be removed before137* returning.138* @param {*} obj Object to expose.139* @param {boolean=} opt_showFn Also show properties that are functions (by140* default, functions are omitted).141* @return {string} A string representation of `obj`.142*/143goog.debug.deepExpose = function(obj, opt_showFn) {144'use strict';145var str = [];146147// Track any objects where deepExpose added a Uid, so they can be cleaned up148// before return. We do this globally, rather than only on ancestors so that149// if the same object appears in the output, you can see it.150var uidsToCleanup = [];151var ancestorUids = {};152153var helper = function(obj, space) {154'use strict';155var nestspace = space + ' ';156157var indentMultiline = function(str) {158'use strict';159return str.replace(/\n/g, '\n' + space);160};161162163try {164if (obj === undefined) {165str.push('undefined');166} else if (obj === null) {167str.push('NULL');168} else if (typeof obj === 'string') {169str.push('"' + indentMultiline(obj) + '"');170} else if (typeof obj === 'function') {171str.push(indentMultiline(String(obj)));172} else if (goog.utils.isObject(obj)) {173// Add a Uid if needed. The struct calls implicitly adds them.174if (!goog.utils.hasUid(obj)) {175uidsToCleanup.push(obj);176}177var uid = goog.utils.getUid(obj);178if (ancestorUids[uid]) {179str.push('*** reference loop detected (id=' + uid + ') ***');180} else {181ancestorUids[uid] = true;182str.push('{');183for (var x in obj) {184if (!opt_showFn && typeof obj[x] === 'function') {185continue;186}187str.push('\n');188str.push(nestspace);189str.push(x + ' = ');190helper(obj[x], nestspace);191}192str.push('\n' + space + '}');193delete ancestorUids[uid];194}195} else {196str.push(obj);197}198} catch (e) {199str.push('*** ' + e + ' ***');200}201};202203helper(obj, '');204205// Cleanup any Uids that were added by the deepExpose.206for (var i = 0; i < uidsToCleanup.length; i++) {207goog.utils.removeUid(uidsToCleanup[i]);208}209210return str.join('');211};212213214/**215* Recursively outputs a nested array as a string.216* @param {Array<?>} arr The array.217* @return {string} String representing nested array.218*/219goog.debug.exposeArray = function(arr) {220'use strict';221var str = [];222for (var i = 0; i < arr.length; i++) {223if (Array.isArray(arr[i])) {224str.push(goog.debug.exposeArray(arr[i]));225} else {226str.push(arr[i]);227}228}229return '[ ' + str.join(', ') + ' ]';230};231232233/**234* Normalizes the error/exception object between browsers.235* @param {*} err Raw error object.236* @return {{237* message: (?|undefined),238* name: (?|undefined),239* lineNumber: (?|undefined),240* fileName: (?|undefined),241* stack: (?|undefined)242* }} Representation of err as an Object. It will never return err.243* @suppress {strictMissingProperties} properties not defined on err244*/245goog.debug.normalizeErrorObject = function(err) {246'use strict';247var href = goog.getObjectByName('window.location.href');248if (err == null) {249err = 'Unknown Error of type "null/undefined"';250}251if (typeof err === 'string') {252return {253'message': err,254'name': 'Unknown error',255'lineNumber': 'Not available',256'fileName': href,257'stack': 'Not available'258};259}260261var lineNumber, fileName;262var threwError = false;263264try {265lineNumber = err.lineNumber || err.line || 'Not available';266} catch (e) {267// Firefox 2 sometimes throws an error when accessing 'lineNumber':268// Message: Permission denied to get property UnnamedClass.lineNumber269lineNumber = 'Not available';270threwError = true;271}272273try {274fileName = err.fileName || err.filename || err.sourceURL ||275// $googDebugFname may be set before a call to eval to set the filename276// that the eval is supposed to present.277goog.global['$googDebugFname'] || href;278} catch (e) {279// Firefox 2 may also throw an error when accessing 'filename'.280fileName = 'Not available';281threwError = true;282}283284var stack = goog.debug.serializeErrorStack_(err);285286// The IE Error object contains only the name and the message.287// The Safari Error object uses the line and sourceURL fields.288if (threwError || !err.lineNumber || !err.fileName || !err.stack ||289!err.message || !err.name) {290var message = err.message;291if (message == null) {292if (err.constructor && err.constructor instanceof Function) {293var ctorName = err.constructor.name ?294err.constructor.name :295goog.debug.getFunctionName(err.constructor);296message = 'Unknown Error of type "' + ctorName + '"';297// TODO(user): Remove this hack once bug is resolved.298if (goog.debug.CHECK_FOR_THROWN_EVENT && ctorName == 'Event') {299try {300message = message + ' with Event.type "' + (err.type || '') + '"';301} catch (e) {302// Just give up on getting more information out of the error object.303}304}305} else {306message = 'Unknown Error of unknown type';307}308309// Avoid TypeError since toString could be missing from the instance310// (e.g. if created Object.create(null)).311if (typeof err.toString === 'function' &&312Object.prototype.toString !== err.toString) {313message += ': ' + err.toString();314}315}316return {317'message': message,318'name': err.name || 'UnknownError',319'lineNumber': lineNumber,320'fileName': fileName,321'stack': stack || 'Not available'322};323}324// Standards error object325// Typed !Object. Should be a subtype of the return type, but it's not.326err.stack = stack;327328// Return non-standard error to allow for consistent result (eg. enumerable).329return {330'message': err.message,331'name': err.name,332'lineNumber': err.lineNumber,333'fileName': err.fileName,334'stack': err.stack335};336};337338339/**340* Serialize stack by including the cause chain of the exception if it exists.341*342*343* @param {*} e an exception that may have a cause344* @param {!Object=} seen set of cause that have already been serialized345* @return {string}346* @private347* @suppress {missingProperties} properties not defined on cause and e348*/349goog.debug.serializeErrorStack_ = function(e, seen) {350'use strict';351if (!seen) {352seen = {};353}354seen[goog.debug.serializeErrorAsKey_(e)] = true;355356var stack = e['stack'] || '';357358// Add cause if exists.359var cause = e.cause;360if (cause && !seen[goog.debug.serializeErrorAsKey_(cause)]) {361stack += '\nCaused by: ';362// Some browsers like Chrome add the error message as the first frame of the363// stack, In this case we don't need to add it. Note: we don't use364// String.startsWith method because it might have to be polyfilled.365if (!cause.stack || cause.stack.indexOf(cause.toString()) != 0) {366stack += (typeof cause === 'string') ? cause : cause.message + '\n';367}368stack += goog.debug.serializeErrorStack_(cause, seen);369}370371return stack;372};373374/**375* Serialize an error to a string key.376* @param {*} e an exception377* @return {string}378* @private379*/380goog.debug.serializeErrorAsKey_ = function(e) {381'use strict';382var keyPrefix = '';383384if (typeof e.toString === 'function') {385keyPrefix = '' + e;386}387388return keyPrefix + e['stack'];389};390391392/**393* Converts an object to an Error using the object's toString if it's not394* already an Error, adds a stacktrace if there isn't one, and optionally adds395* an extra message.396* @param {*} err The original thrown error, object, or string.397* @param {string=} opt_message optional additional message to add to the398* error.399* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,400* it is converted to an Error which is enhanced and returned.401*/402goog.debug.enhanceError = function(err, opt_message) {403'use strict';404var error;405if (!(err instanceof Error)) {406error = Error(err);407if (Error.captureStackTrace) {408// Trim this function off the call stack, if we can.409Error.captureStackTrace(error, goog.debug.enhanceError);410}411} else {412error = err;413}414415if (!error.stack) {416error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);417}418if (opt_message) {419// find the first unoccupied 'messageX' property420var x = 0;421while (error['message' + x]) {422++x;423}424error['message' + x] = String(opt_message);425}426return error;427};428429430/**431* Converts an object to an Error using the object's toString if it's not432* already an Error, adds a stacktrace if there isn't one, and optionally adds433* context to the Error, which is reported by the closure error reporter.434* @param {*} err The original thrown error, object, or string.435* @param {!Object<string, string>=} opt_context Key-value context to add to the436* Error.437* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,438* it is converted to an Error which is enhanced and returned.439*/440goog.debug.enhanceErrorWithContext = function(err, opt_context) {441'use strict';442var error = goog.debug.enhanceError(err);443if (opt_context) {444for (var key in opt_context) {445goog.debug.errorcontext.addErrorContext(error, key, opt_context[key]);446}447}448return error;449};450451452/**453* Gets the current stack trace. Simple and iterative - doesn't worry about454* catching circular references or getting the args.455* @param {number=} opt_depth Optional maximum depth to trace back to.456* @return {string} A string with the function names of all functions in the457* stack, separated by \n.458* @suppress {es5Strict}459*/460goog.debug.getStacktraceSimple = function(opt_depth) {461'use strict';462if (!goog.debug.FORCE_SLOPPY_STACKS) {463var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);464if (stack) {465return stack;466}467// NOTE: browsers that have strict mode support also have native "stack"468// properties. Fall-through for legacy browser support.469}470471var sb = [];472var fn = arguments.callee.caller;473var depth = 0;474475while (fn && (!opt_depth || depth < opt_depth)) {476sb.push(goog.debug.getFunctionName(fn));477sb.push('()\n');478479try {480fn = fn.caller;481} catch (e) {482sb.push('[exception trying to get caller]\n');483break;484}485depth++;486if (depth >= goog.debug.MAX_STACK_DEPTH) {487sb.push('[...long stack...]');488break;489}490}491if (opt_depth && depth >= opt_depth) {492sb.push('[...reached max depth limit...]');493} else {494sb.push('[end]');495}496497return sb.join('');498};499500501/**502* Max length of stack to try and output503* @type {number}504*/505goog.debug.MAX_STACK_DEPTH = 50;506507508/**509* @param {Function} fn The function to start getting the trace from.510* @return {?string}511* @private512*/513goog.debug.getNativeStackTrace_ = function(fn) {514'use strict';515var tempErr = new Error();516if (Error.captureStackTrace) {517Error.captureStackTrace(tempErr, fn);518return String(tempErr.stack);519} else {520// IE10, only adds stack traces when an exception is thrown.521try {522throw tempErr;523} catch (e) {524tempErr = e;525}526var stack = tempErr.stack;527if (stack) {528return String(stack);529}530}531return null;532};533534535/**536* Gets the current stack trace, either starting from the caller or starting537* from a specified function that's currently on the call stack.538* @param {?Function=} fn If provided, when collecting the stack trace all539* frames above the topmost call to this function, including that call,540* will be left out of the stack trace.541* @return {string} Stack trace.542* @suppress {es5Strict}543*/544goog.debug.getStacktrace = function(fn) {545'use strict';546var stack;547if (!goog.debug.FORCE_SLOPPY_STACKS) {548// Try to get the stack trace from the environment if it is available.549var contextFn = fn || goog.debug.getStacktrace;550stack = goog.debug.getNativeStackTrace_(contextFn);551}552if (!stack) {553// NOTE: browsers that have strict mode support also have native "stack"554// properties. This function will throw in strict mode.555stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);556}557return stack;558};559560561/**562* Private helper for getStacktrace().563* @param {?Function} fn If provided, when collecting the stack trace all564* frames above the topmost call to this function, including that call,565* will be left out of the stack trace.566* @param {Array<!Function>} visited List of functions visited so far.567* @return {string} Stack trace starting from function fn.568* @suppress {es5Strict}569* @private570*/571goog.debug.getStacktraceHelper_ = function(fn, visited) {572'use strict';573var sb = [];574575// Circular reference, certain functions like bind seem to cause a recursive576// loop so we need to catch circular references577if (goog.array.contains(visited, fn)) {578sb.push('[...circular reference...]');579580// Traverse the call stack until function not found or max depth is reached581} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {582sb.push(goog.debug.getFunctionName(fn) + '(');583var args = fn.arguments;584// Args may be null for some special functions such as host objects or eval.585for (var i = 0; args && i < args.length; i++) {586if (i > 0) {587sb.push(', ');588}589var argDesc;590var arg = args[i];591switch (typeof arg) {592case 'object':593argDesc = arg ? 'object' : 'null';594break;595596case 'string':597argDesc = arg;598break;599600case 'number':601argDesc = String(arg);602break;603604case 'boolean':605argDesc = arg ? 'true' : 'false';606break;607608case 'function':609argDesc = goog.debug.getFunctionName(arg);610argDesc = argDesc ? argDesc : '[fn]';611break;612613case 'undefined':614default:615argDesc = typeof arg;616break;617}618619if (argDesc.length > 40) {620argDesc = argDesc.slice(0, 40) + '...';621}622sb.push(argDesc);623}624visited.push(fn);625sb.push(')\n');626627try {628sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));629} catch (e) {630sb.push('[exception trying to get caller]\n');631}632633} else if (fn) {634sb.push('[...long stack...]');635} else {636sb.push('[end]');637}638return sb.join('');639};640641642/**643* Gets a function name644* @param {Function} fn Function to get name of.645* @return {string} Function's name.646*/647goog.debug.getFunctionName = function(fn) {648'use strict';649if (goog.debug.fnNameCache_[fn]) {650return goog.debug.fnNameCache_[fn];651}652653// Heuristically determine function name based on code.654var functionSource = String(fn);655if (!goog.debug.fnNameCache_[functionSource]) {656var matches = /function\s+([^\(]+)/m.exec(functionSource);657if (matches) {658var method = matches[1];659goog.debug.fnNameCache_[functionSource] = method;660} else {661goog.debug.fnNameCache_[functionSource] = '[Anonymous]';662}663}664665return goog.debug.fnNameCache_[functionSource];666};667668669/**670* Makes whitespace visible by replacing it with printable characters.671* This is useful in finding diffrences between the expected and the actual672* output strings of a testcase.673* @param {string} string whose whitespace needs to be made visible.674* @return {string} string whose whitespace is made visible.675*/676goog.debug.makeWhitespaceVisible = function(string) {677'use strict';678return string.replace(/ /g, '[_]')679.replace(/\f/g, '[f]')680.replace(/\n/g, '[n]\n')681.replace(/\r/g, '[r]')682.replace(/\t/g, '[t]');683};684685686/**687* Returns the type of a value. If a constructor is passed, and a suitable688* string cannot be found, 'unknown type name' will be returned.689*690* <p>Forked rather than moved from {@link goog.asserts.getType_}691* to avoid adding a dependency to goog.asserts.692* @param {*} value A constructor, object, or primitive.693* @return {string} The best display name for the value, or 'unknown type name'.694*/695goog.debug.runtimeType = function(value) {696'use strict';697if (value instanceof Function) {698return value.displayName || value.name || 'unknown type name';699} else if (value instanceof Object) {700return /** @type {string} */ (value.constructor.displayName) ||701value.constructor.name || Object.prototype.toString.call(value);702} else {703return value === null ? 'null' : typeof value;704}705};706707708/**709* Hash map for storing function names that have already been looked up.710* @type {Object}711* @private712*/713goog.debug.fnNameCache_ = {};714715716/**717* Private internal function to support goog.debug.freeze.718* @param {T} arg719* @return {T}720* @template T721* @private722*/723goog.debug.freezeInternal_ = goog.DEBUG && Object.freeze || function(arg) {724'use strict';725return arg;726};727728729/**730* Freezes the given object, but only in debug mode (and in browsers that731* support it). Note that this is a shallow freeze, so for deeply nested732* objects it must be called at every level to ensure deep immutability.733* @param {T} arg734* @return {T}735* @template T736*/737goog.debug.freeze = function(arg) {738'use strict';739// NOTE: this compiles to nothing, but hides the possible side effect of740// freezeInternal_ from the compiler so that the entire call can be741// removed if the result is not used.742return {743valueOf: function() {744'use strict';745return goog.debug.freezeInternal_(arg);746}747}.valueOf();748};749750751