leakhunt.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. /**
  6. * Memory leak hunter. Walks a tree of objects looking for DOM nodes.
  7. * Usage:
  8. * leakHunt({
  9. * thing: thing,
  10. * otherthing: otherthing
  11. * });
  12. */
  13. function leakHunt(root) {
  14. let path = [];
  15. let seen = [];
  16. try {
  17. let output = leakHunt.inner(root, path, seen);
  18. output.forEach(function (line) {
  19. dump(line + "\n");
  20. });
  21. } catch (ex) {
  22. dump(ex + "\n");
  23. }
  24. }
  25. leakHunt.inner = function (root, path, seen) {
  26. let prefix = new Array(path.length).join(" ");
  27. let reply = [];
  28. function log(msg) {
  29. reply.push(msg);
  30. }
  31. let direct;
  32. try {
  33. direct = Object.keys(root);
  34. } catch (ex) {
  35. log(prefix + " Error enumerating: " + ex);
  36. return reply;
  37. }
  38. try {
  39. let index = 0;
  40. for (let data of root) {
  41. let prop = "" + index;
  42. leakHunt.digProperty(prop, data, path, seen, direct, log);
  43. index++;
  44. }
  45. } catch (ex) {
  46. /* Ignore things that are not enumerable */
  47. }
  48. for (let prop in root) {
  49. let data;
  50. try {
  51. data = root[prop];
  52. } catch (ex) {
  53. log(prefix + " " + prop + " = Error: " + ex.toString().substring(0, 30));
  54. continue;
  55. }
  56. leakHunt.digProperty(prop, data, path, seen, direct, log);
  57. }
  58. return reply;
  59. };
  60. leakHunt.hide = [ /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/ ];
  61. leakHunt.noRecurse = [
  62. /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/,
  63. /^Window$/, /^Document$/,
  64. /^XULDocument$/, /^XULElement$/,
  65. /^DOMWindow$/, /^HTMLDocument$/, /^HTML.*Element$/, /^ChromeWindow$/
  66. ];
  67. leakHunt.digProperty = function (prop, data, path, seen, direct, log) {
  68. let newPath = path.slice();
  69. newPath.push(prop);
  70. let prefix = new Array(newPath.length).join(" ");
  71. let recurse = true;
  72. let message = leakHunt.getType(data);
  73. if (leakHunt.matchesAnyPattern(message, leakHunt.hide)) {
  74. return;
  75. }
  76. if (message === "function" && direct.indexOf(prop) == -1) {
  77. return;
  78. }
  79. if (message === "string") {
  80. let extra = data.length > 10 ? data.substring(0, 9) + "_" : data;
  81. message += ' "' + extra.replace(/\n/g, "|") + '"';
  82. recurse = false;
  83. } else if (leakHunt.matchesAnyPattern(message, leakHunt.noRecurse)) {
  84. message += " (no recurse)";
  85. recurse = false;
  86. } else if (seen.indexOf(data) !== -1) {
  87. message += " (already seen)";
  88. recurse = false;
  89. }
  90. if (recurse) {
  91. seen.push(data);
  92. let lines = leakHunt.inner(data, newPath, seen);
  93. if (lines.length == 0) {
  94. if (message !== "function") {
  95. log(prefix + prop + " = " + message + " { }");
  96. }
  97. } else {
  98. log(prefix + prop + " = " + message + " {");
  99. lines.forEach(function (line) {
  100. log(line);
  101. });
  102. log(prefix + "}");
  103. }
  104. } else {
  105. log(prefix + prop + " = " + message);
  106. }
  107. };
  108. leakHunt.matchesAnyPattern = function (str, patterns) {
  109. let match = false;
  110. patterns.forEach(function (pattern) {
  111. if (str.match(pattern)) {
  112. match = true;
  113. }
  114. });
  115. return match;
  116. };
  117. leakHunt.getType = function (data) {
  118. if (data === null) {
  119. return "null";
  120. }
  121. if (data === undefined) {
  122. return "undefined";
  123. }
  124. let type = typeof data;
  125. if (type === "object" || type === "Object") {
  126. type = leakHunt.getCtorName(data);
  127. }
  128. return type;
  129. };
  130. leakHunt.getCtorName = function (obj) {
  131. try {
  132. if (obj.constructor && obj.constructor.name) {
  133. return obj.constructor.name;
  134. }
  135. } catch (ex) {
  136. return "UnknownObject";
  137. }
  138. // If that fails, use Objects toString which sometimes gives something
  139. // better than 'Object', and at least defaults to Object if nothing better
  140. return Object.prototype.toString.call(obj).slice(8, -1);
  141. };