debug.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. // Copyright 2006 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 Logging and debugging utilities.
  16. *
  17. * @see ../demos/debug.html
  18. */
  19. goog.provide('goog.debug');
  20. goog.require('goog.array');
  21. goog.require('goog.userAgent');
  22. /** @define {boolean} Whether logging should be enabled. */
  23. goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
  24. /** @define {boolean} Whether to force "sloppy" stack building. */
  25. goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);
  26. /**
  27. * Catches onerror events fired by windows and similar objects.
  28. * @param {function(Object)} logFunc The function to call with the error
  29. * information.
  30. * @param {boolean=} opt_cancel Whether to stop the error from reaching the
  31. * browser.
  32. * @param {Object=} opt_target Object that fires onerror events.
  33. */
  34. goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
  35. var target = opt_target || goog.global;
  36. var oldErrorHandler = target.onerror;
  37. var retVal = !!opt_cancel;
  38. // Chrome interprets onerror return value backwards (http://crbug.com/92062)
  39. // until it was fixed in webkit revision r94061 (Webkit 535.3). This
  40. // workaround still needs to be skipped in Safari after the webkit change
  41. // gets pushed out in Safari.
  42. // See https://bugs.webkit.org/show_bug.cgi?id=67119
  43. if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.3')) {
  44. retVal = !retVal;
  45. }
  46. /**
  47. * New onerror handler for this target. This onerror handler follows the spec
  48. * according to
  49. * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
  50. * The spec was changed in August 2013 to support receiving column information
  51. * and an error object for all scripts on the same origin or cross origin
  52. * scripts with the proper headers. See
  53. * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
  54. *
  55. * @param {string} message The error message. For cross-origin errors, this
  56. * will be scrubbed to just "Script error.". For new browsers that have
  57. * updated to follow the latest spec, errors that come from origins that
  58. * have proper cross origin headers will not be scrubbed.
  59. * @param {string} url The URL of the script that caused the error. The URL
  60. * will be scrubbed to "" for cross origin scripts unless the script has
  61. * proper cross origin headers and the browser has updated to the latest
  62. * spec.
  63. * @param {number} line The line number in the script that the error
  64. * occurred on.
  65. * @param {number=} opt_col The optional column number that the error
  66. * occurred on. Only browsers that have updated to the latest spec will
  67. * include this.
  68. * @param {Error=} opt_error The optional actual error object for this
  69. * error that should include the stack. Only browsers that have updated
  70. * to the latest spec will inlude this parameter.
  71. * @return {boolean} Whether to prevent the error from reaching the browser.
  72. */
  73. target.onerror = function(message, url, line, opt_col, opt_error) {
  74. if (oldErrorHandler) {
  75. oldErrorHandler(message, url, line, opt_col, opt_error);
  76. }
  77. logFunc({
  78. message: message,
  79. fileName: url,
  80. line: line,
  81. col: opt_col,
  82. error: opt_error
  83. });
  84. return retVal;
  85. };
  86. };
  87. /**
  88. * Creates a string representing an object and all its properties.
  89. * @param {Object|null|undefined} obj Object to expose.
  90. * @param {boolean=} opt_showFn Show the functions as well as the properties,
  91. * default is false.
  92. * @return {string} The string representation of {@code obj}.
  93. */
  94. goog.debug.expose = function(obj, opt_showFn) {
  95. if (typeof obj == 'undefined') {
  96. return 'undefined';
  97. }
  98. if (obj == null) {
  99. return 'NULL';
  100. }
  101. var str = [];
  102. for (var x in obj) {
  103. if (!opt_showFn && goog.isFunction(obj[x])) {
  104. continue;
  105. }
  106. var s = x + ' = ';
  107. try {
  108. s += obj[x];
  109. } catch (e) {
  110. s += '*** ' + e + ' ***';
  111. }
  112. str.push(s);
  113. }
  114. return str.join('\n');
  115. };
  116. /**
  117. * Creates a string representing a given primitive or object, and for an
  118. * object, all its properties and nested objects. NOTE: The output will include
  119. * Uids on all objects that were exposed. Any added Uids will be removed before
  120. * returning.
  121. * @param {*} obj Object to expose.
  122. * @param {boolean=} opt_showFn Also show properties that are functions (by
  123. * default, functions are omitted).
  124. * @return {string} A string representation of {@code obj}.
  125. */
  126. goog.debug.deepExpose = function(obj, opt_showFn) {
  127. var str = [];
  128. // Track any objects where deepExpose added a Uid, so they can be cleaned up
  129. // before return. We do this globally, rather than only on ancestors so that
  130. // if the same object appears in the output, you can see it.
  131. var uidsToCleanup = [];
  132. var ancestorUids = {};
  133. var helper = function(obj, space) {
  134. var nestspace = space + ' ';
  135. var indentMultiline = function(str) {
  136. return str.replace(/\n/g, '\n' + space);
  137. };
  138. try {
  139. if (!goog.isDef(obj)) {
  140. str.push('undefined');
  141. } else if (goog.isNull(obj)) {
  142. str.push('NULL');
  143. } else if (goog.isString(obj)) {
  144. str.push('"' + indentMultiline(obj) + '"');
  145. } else if (goog.isFunction(obj)) {
  146. str.push(indentMultiline(String(obj)));
  147. } else if (goog.isObject(obj)) {
  148. // Add a Uid if needed. The struct calls implicitly adds them.
  149. if (!goog.hasUid(obj)) {
  150. uidsToCleanup.push(obj);
  151. }
  152. var uid = goog.getUid(obj);
  153. if (ancestorUids[uid]) {
  154. str.push('*** reference loop detected (id=' + uid + ') ***');
  155. } else {
  156. ancestorUids[uid] = true;
  157. str.push('{');
  158. for (var x in obj) {
  159. if (!opt_showFn && goog.isFunction(obj[x])) {
  160. continue;
  161. }
  162. str.push('\n');
  163. str.push(nestspace);
  164. str.push(x + ' = ');
  165. helper(obj[x], nestspace);
  166. }
  167. str.push('\n' + space + '}');
  168. delete ancestorUids[uid];
  169. }
  170. } else {
  171. str.push(obj);
  172. }
  173. } catch (e) {
  174. str.push('*** ' + e + ' ***');
  175. }
  176. };
  177. helper(obj, '');
  178. // Cleanup any Uids that were added by the deepExpose.
  179. for (var i = 0; i < uidsToCleanup.length; i++) {
  180. goog.removeUid(uidsToCleanup[i]);
  181. }
  182. return str.join('');
  183. };
  184. /**
  185. * Recursively outputs a nested array as a string.
  186. * @param {Array<?>} arr The array.
  187. * @return {string} String representing nested array.
  188. */
  189. goog.debug.exposeArray = function(arr) {
  190. var str = [];
  191. for (var i = 0; i < arr.length; i++) {
  192. if (goog.isArray(arr[i])) {
  193. str.push(goog.debug.exposeArray(arr[i]));
  194. } else {
  195. str.push(arr[i]);
  196. }
  197. }
  198. return '[ ' + str.join(', ') + ' ]';
  199. };
  200. /**
  201. * Normalizes the error/exception object between browsers.
  202. * @param {*} err Raw error object.
  203. * @return {!{
  204. * message: (?|undefined),
  205. * name: (?|undefined),
  206. * lineNumber: (?|undefined),
  207. * fileName: (?|undefined),
  208. * stack: (?|undefined)
  209. * }} Normalized error object.
  210. */
  211. goog.debug.normalizeErrorObject = function(err) {
  212. var href = goog.getObjectByName('window.location.href');
  213. if (goog.isString(err)) {
  214. return {
  215. 'message': err,
  216. 'name': 'Unknown error',
  217. 'lineNumber': 'Not available',
  218. 'fileName': href,
  219. 'stack': 'Not available'
  220. };
  221. }
  222. var lineNumber, fileName;
  223. var threwError = false;
  224. try {
  225. lineNumber = err.lineNumber || err.line || 'Not available';
  226. } catch (e) {
  227. // Firefox 2 sometimes throws an error when accessing 'lineNumber':
  228. // Message: Permission denied to get property UnnamedClass.lineNumber
  229. lineNumber = 'Not available';
  230. threwError = true;
  231. }
  232. try {
  233. fileName = err.fileName || err.filename || err.sourceURL ||
  234. // $googDebugFname may be set before a call to eval to set the filename
  235. // that the eval is supposed to present.
  236. goog.global['$googDebugFname'] || href;
  237. } catch (e) {
  238. // Firefox 2 may also throw an error when accessing 'filename'.
  239. fileName = 'Not available';
  240. threwError = true;
  241. }
  242. // The IE Error object contains only the name and the message.
  243. // The Safari Error object uses the line and sourceURL fields.
  244. if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
  245. !err.message || !err.name) {
  246. return {
  247. 'message': err.message || 'Not available',
  248. 'name': err.name || 'UnknownError',
  249. 'lineNumber': lineNumber,
  250. 'fileName': fileName,
  251. 'stack': err.stack || 'Not available'
  252. };
  253. }
  254. // Standards error object
  255. // Typed !Object. Should be a subtype of the return type, but it's not.
  256. return /** @type {?} */ (err);
  257. };
  258. /**
  259. * Converts an object to an Error using the object's toString if it's not
  260. * already an Error, adds a stacktrace if there isn't one, and optionally adds
  261. * an extra message.
  262. * @param {*} err The original thrown error, object, or string.
  263. * @param {string=} opt_message optional additional message to add to the
  264. * error.
  265. * @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
  266. * it is converted to an Error which is enhanced and returned.
  267. */
  268. goog.debug.enhanceError = function(err, opt_message) {
  269. var error;
  270. if (!(err instanceof Error)) {
  271. error = Error(err);
  272. if (Error.captureStackTrace) {
  273. // Trim this function off the call stack, if we can.
  274. Error.captureStackTrace(error, goog.debug.enhanceError);
  275. }
  276. } else {
  277. error = err;
  278. }
  279. if (!error.stack) {
  280. error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
  281. }
  282. if (opt_message) {
  283. // find the first unoccupied 'messageX' property
  284. var x = 0;
  285. while (error['message' + x]) {
  286. ++x;
  287. }
  288. error['message' + x] = String(opt_message);
  289. }
  290. return error;
  291. };
  292. /**
  293. * Gets the current stack trace. Simple and iterative - doesn't worry about
  294. * catching circular references or getting the args.
  295. * @param {number=} opt_depth Optional maximum depth to trace back to.
  296. * @return {string} A string with the function names of all functions in the
  297. * stack, separated by \n.
  298. * @suppress {es5Strict}
  299. */
  300. goog.debug.getStacktraceSimple = function(opt_depth) {
  301. if (!goog.debug.FORCE_SLOPPY_STACKS) {
  302. var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
  303. if (stack) {
  304. return stack;
  305. }
  306. // NOTE: browsers that have strict mode support also have native "stack"
  307. // properties. Fall-through for legacy browser support.
  308. }
  309. var sb = [];
  310. var fn = arguments.callee.caller;
  311. var depth = 0;
  312. while (fn && (!opt_depth || depth < opt_depth)) {
  313. sb.push(goog.debug.getFunctionName(fn));
  314. sb.push('()\n');
  315. try {
  316. fn = fn.caller;
  317. } catch (e) {
  318. sb.push('[exception trying to get caller]\n');
  319. break;
  320. }
  321. depth++;
  322. if (depth >= goog.debug.MAX_STACK_DEPTH) {
  323. sb.push('[...long stack...]');
  324. break;
  325. }
  326. }
  327. if (opt_depth && depth >= opt_depth) {
  328. sb.push('[...reached max depth limit...]');
  329. } else {
  330. sb.push('[end]');
  331. }
  332. return sb.join('');
  333. };
  334. /**
  335. * Max length of stack to try and output
  336. * @type {number}
  337. */
  338. goog.debug.MAX_STACK_DEPTH = 50;
  339. /**
  340. * @param {Function} fn The function to start getting the trace from.
  341. * @return {?string}
  342. * @private
  343. */
  344. goog.debug.getNativeStackTrace_ = function(fn) {
  345. var tempErr = new Error();
  346. if (Error.captureStackTrace) {
  347. Error.captureStackTrace(tempErr, fn);
  348. return String(tempErr.stack);
  349. } else {
  350. // IE10, only adds stack traces when an exception is thrown.
  351. try {
  352. throw tempErr;
  353. } catch (e) {
  354. tempErr = e;
  355. }
  356. var stack = tempErr.stack;
  357. if (stack) {
  358. return String(stack);
  359. }
  360. }
  361. return null;
  362. };
  363. /**
  364. * Gets the current stack trace, either starting from the caller or starting
  365. * from a specified function that's currently on the call stack.
  366. * @param {?Function=} fn If provided, when collecting the stack trace all
  367. * frames above the topmost call to this function, including that call,
  368. * will be left out of the stack trace.
  369. * @return {string} Stack trace.
  370. * @suppress {es5Strict}
  371. */
  372. goog.debug.getStacktrace = function(fn) {
  373. var stack;
  374. if (!goog.debug.FORCE_SLOPPY_STACKS) {
  375. // Try to get the stack trace from the environment if it is available.
  376. var contextFn = fn || goog.debug.getStacktrace;
  377. stack = goog.debug.getNativeStackTrace_(contextFn);
  378. }
  379. if (!stack) {
  380. // NOTE: browsers that have strict mode support also have native "stack"
  381. // properties. This function will throw in strict mode.
  382. stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);
  383. }
  384. return stack;
  385. };
  386. /**
  387. * Private helper for getStacktrace().
  388. * @param {?Function} fn If provided, when collecting the stack trace all
  389. * frames above the topmost call to this function, including that call,
  390. * will be left out of the stack trace.
  391. * @param {Array<!Function>} visited List of functions visited so far.
  392. * @return {string} Stack trace starting from function fn.
  393. * @suppress {es5Strict}
  394. * @private
  395. */
  396. goog.debug.getStacktraceHelper_ = function(fn, visited) {
  397. var sb = [];
  398. // Circular reference, certain functions like bind seem to cause a recursive
  399. // loop so we need to catch circular references
  400. if (goog.array.contains(visited, fn)) {
  401. sb.push('[...circular reference...]');
  402. // Traverse the call stack until function not found or max depth is reached
  403. } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
  404. sb.push(goog.debug.getFunctionName(fn) + '(');
  405. var args = fn.arguments;
  406. // Args may be null for some special functions such as host objects or eval.
  407. for (var i = 0; args && i < args.length; i++) {
  408. if (i > 0) {
  409. sb.push(', ');
  410. }
  411. var argDesc;
  412. var arg = args[i];
  413. switch (typeof arg) {
  414. case 'object':
  415. argDesc = arg ? 'object' : 'null';
  416. break;
  417. case 'string':
  418. argDesc = arg;
  419. break;
  420. case 'number':
  421. argDesc = String(arg);
  422. break;
  423. case 'boolean':
  424. argDesc = arg ? 'true' : 'false';
  425. break;
  426. case 'function':
  427. argDesc = goog.debug.getFunctionName(arg);
  428. argDesc = argDesc ? argDesc : '[fn]';
  429. break;
  430. case 'undefined':
  431. default:
  432. argDesc = typeof arg;
  433. break;
  434. }
  435. if (argDesc.length > 40) {
  436. argDesc = argDesc.substr(0, 40) + '...';
  437. }
  438. sb.push(argDesc);
  439. }
  440. visited.push(fn);
  441. sb.push(')\n');
  442. try {
  443. sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
  444. } catch (e) {
  445. sb.push('[exception trying to get caller]\n');
  446. }
  447. } else if (fn) {
  448. sb.push('[...long stack...]');
  449. } else {
  450. sb.push('[end]');
  451. }
  452. return sb.join('');
  453. };
  454. /**
  455. * Set a custom function name resolver.
  456. * @param {function(Function): string} resolver Resolves functions to their
  457. * names.
  458. */
  459. goog.debug.setFunctionResolver = function(resolver) {
  460. goog.debug.fnNameResolver_ = resolver;
  461. };
  462. /**
  463. * Gets a function name
  464. * @param {Function} fn Function to get name of.
  465. * @return {string} Function's name.
  466. */
  467. goog.debug.getFunctionName = function(fn) {
  468. if (goog.debug.fnNameCache_[fn]) {
  469. return goog.debug.fnNameCache_[fn];
  470. }
  471. if (goog.debug.fnNameResolver_) {
  472. var name = goog.debug.fnNameResolver_(fn);
  473. if (name) {
  474. goog.debug.fnNameCache_[fn] = name;
  475. return name;
  476. }
  477. }
  478. // Heuristically determine function name based on code.
  479. var functionSource = String(fn);
  480. if (!goog.debug.fnNameCache_[functionSource]) {
  481. var matches = /function ([^\(]+)/.exec(functionSource);
  482. if (matches) {
  483. var method = matches[1];
  484. goog.debug.fnNameCache_[functionSource] = method;
  485. } else {
  486. goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
  487. }
  488. }
  489. return goog.debug.fnNameCache_[functionSource];
  490. };
  491. /**
  492. * Makes whitespace visible by replacing it with printable characters.
  493. * This is useful in finding diffrences between the expected and the actual
  494. * output strings of a testcase.
  495. * @param {string} string whose whitespace needs to be made visible.
  496. * @return {string} string whose whitespace is made visible.
  497. */
  498. goog.debug.makeWhitespaceVisible = function(string) {
  499. return string.replace(/ /g, '[_]')
  500. .replace(/\f/g, '[f]')
  501. .replace(/\n/g, '[n]\n')
  502. .replace(/\r/g, '[r]')
  503. .replace(/\t/g, '[t]');
  504. };
  505. /**
  506. * Returns the type of a value. If a constructor is passed, and a suitable
  507. * string cannot be found, 'unknown type name' will be returned.
  508. *
  509. * <p>Forked rather than moved from {@link goog.asserts.getType_}
  510. * to avoid adding a dependency to goog.asserts.
  511. * @param {*} value A constructor, object, or primitive.
  512. * @return {string} The best display name for the value, or 'unknown type name'.
  513. */
  514. goog.debug.runtimeType = function(value) {
  515. if (value instanceof Function) {
  516. return value.displayName || value.name || 'unknown type name';
  517. } else if (value instanceof Object) {
  518. return value.constructor.displayName || value.constructor.name ||
  519. Object.prototype.toString.call(value);
  520. } else {
  521. return value === null ? 'null' : typeof value;
  522. }
  523. };
  524. /**
  525. * Hash map for storing function names that have already been looked up.
  526. * @type {Object}
  527. * @private
  528. */
  529. goog.debug.fnNameCache_ = {};
  530. /**
  531. * Resolves functions to their names. Resolved function names will be cached.
  532. * @type {function(Function):string}
  533. * @private
  534. */
  535. goog.debug.fnNameResolver_;