123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- /**
- * Memory leak hunter. Walks a tree of objects looking for DOM nodes.
- * Usage:
- * leakHunt({
- * thing: thing,
- * otherthing: otherthing
- * });
- */
- function leakHunt(root) {
- let path = [];
- let seen = [];
- try {
- let output = leakHunt.inner(root, path, seen);
- output.forEach(function (line) {
- dump(line + "\n");
- });
- } catch (ex) {
- dump(ex + "\n");
- }
- }
- leakHunt.inner = function (root, path, seen) {
- let prefix = new Array(path.length).join(" ");
- let reply = [];
- function log(msg) {
- reply.push(msg);
- }
- let direct;
- try {
- direct = Object.keys(root);
- } catch (ex) {
- log(prefix + " Error enumerating: " + ex);
- return reply;
- }
- try {
- let index = 0;
- for (let data of root) {
- let prop = "" + index;
- leakHunt.digProperty(prop, data, path, seen, direct, log);
- index++;
- }
- } catch (ex) {
- /* Ignore things that are not enumerable */
- }
- for (let prop in root) {
- let data;
- try {
- data = root[prop];
- } catch (ex) {
- log(prefix + " " + prop + " = Error: " + ex.toString().substring(0, 30));
- continue;
- }
- leakHunt.digProperty(prop, data, path, seen, direct, log);
- }
- return reply;
- };
- leakHunt.hide = [ /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/ ];
- leakHunt.noRecurse = [
- /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/,
- /^Window$/, /^Document$/,
- /^XULDocument$/, /^XULElement$/,
- /^DOMWindow$/, /^HTMLDocument$/, /^HTML.*Element$/, /^ChromeWindow$/
- ];
- leakHunt.digProperty = function (prop, data, path, seen, direct, log) {
- let newPath = path.slice();
- newPath.push(prop);
- let prefix = new Array(newPath.length).join(" ");
- let recurse = true;
- let message = leakHunt.getType(data);
- if (leakHunt.matchesAnyPattern(message, leakHunt.hide)) {
- return;
- }
- if (message === "function" && direct.indexOf(prop) == -1) {
- return;
- }
- if (message === "string") {
- let extra = data.length > 10 ? data.substring(0, 9) + "_" : data;
- message += ' "' + extra.replace(/\n/g, "|") + '"';
- recurse = false;
- } else if (leakHunt.matchesAnyPattern(message, leakHunt.noRecurse)) {
- message += " (no recurse)";
- recurse = false;
- } else if (seen.indexOf(data) !== -1) {
- message += " (already seen)";
- recurse = false;
- }
- if (recurse) {
- seen.push(data);
- let lines = leakHunt.inner(data, newPath, seen);
- if (lines.length == 0) {
- if (message !== "function") {
- log(prefix + prop + " = " + message + " { }");
- }
- } else {
- log(prefix + prop + " = " + message + " {");
- lines.forEach(function (line) {
- log(line);
- });
- log(prefix + "}");
- }
- } else {
- log(prefix + prop + " = " + message);
- }
- };
- leakHunt.matchesAnyPattern = function (str, patterns) {
- let match = false;
- patterns.forEach(function (pattern) {
- if (str.match(pattern)) {
- match = true;
- }
- });
- return match;
- };
- leakHunt.getType = function (data) {
- if (data === null) {
- return "null";
- }
- if (data === undefined) {
- return "undefined";
- }
- let type = typeof data;
- if (type === "object" || type === "Object") {
- type = leakHunt.getCtorName(data);
- }
- return type;
- };
- leakHunt.getCtorName = function (obj) {
- try {
- if (obj.constructor && obj.constructor.name) {
- return obj.constructor.name;
- }
- } catch (ex) {
- return "UnknownObject";
- }
- // If that fails, use Objects toString which sometimes gives something
- // better than 'Object', and at least defaults to Object if nothing better
- return Object.prototype.toString.call(obj).slice(8, -1);
- };
|