2012-08-14 18:51:48 +04:00
|
|
|
/* 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/. */
|
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
"use strict";
|
|
|
|
|
2012-08-14 18:51:48 +04:00
|
|
|
/**
|
|
|
|
* Memory leak hunter. Walks a tree of objects looking for DOM nodes.
|
|
|
|
* Usage:
|
|
|
|
* leakHunt({
|
|
|
|
* thing: thing,
|
|
|
|
* otherthing: otherthing
|
|
|
|
* });
|
|
|
|
*/
|
2012-11-30 12:07:59 +04:00
|
|
|
function leakHunt(root) {
|
2016-11-11 12:09:50 +03:00
|
|
|
let path = [];
|
|
|
|
let seen = [];
|
2012-08-14 18:51:48 +04:00
|
|
|
|
|
|
|
try {
|
2016-11-11 12:09:50 +03:00
|
|
|
let output = leakHunt.inner(root, path, seen);
|
2016-05-17 21:25:54 +03:00
|
|
|
output.forEach(function (line) {
|
|
|
|
dump(line + "\n");
|
2012-08-14 18:51:48 +04:00
|
|
|
});
|
2016-11-11 12:09:50 +03:00
|
|
|
} catch (ex) {
|
2016-05-17 21:25:54 +03:00
|
|
|
dump(ex + "\n");
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
leakHunt.inner = function (root, path, seen) {
|
|
|
|
let prefix = new Array(path.length).join(" ");
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
let reply = [];
|
2012-08-14 18:51:48 +04:00
|
|
|
function log(msg) {
|
|
|
|
reply.push(msg);
|
|
|
|
}
|
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
let direct;
|
2012-08-14 18:51:48 +04:00
|
|
|
try {
|
|
|
|
direct = Object.keys(root);
|
2016-11-11 12:09:50 +03:00
|
|
|
} catch (ex) {
|
2016-05-17 21:25:54 +03:00
|
|
|
log(prefix + " Error enumerating: " + ex);
|
2012-08-14 18:51:48 +04:00
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2012-11-30 12:07:59 +04:00
|
|
|
try {
|
2016-11-11 12:09:50 +03:00
|
|
|
let index = 0;
|
|
|
|
for (let data of root) {
|
|
|
|
let prop = "" + index;
|
2012-11-30 12:07:59 +04:00
|
|
|
leakHunt.digProperty(prop, data, path, seen, direct, log);
|
|
|
|
index++;
|
|
|
|
}
|
2016-11-11 12:09:50 +03:00
|
|
|
} catch (ex) {
|
|
|
|
/* Ignore things that are not enumerable */
|
2012-11-30 12:07:59 +04:00
|
|
|
}
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
for (let prop in root) {
|
|
|
|
let data;
|
2012-08-14 18:51:48 +04:00
|
|
|
try {
|
|
|
|
data = root[prop];
|
2016-11-11 12:09:50 +03:00
|
|
|
} catch (ex) {
|
2016-05-17 21:25:54 +03:00
|
|
|
log(prefix + " " + prop + " = Error: " + ex.toString().substring(0, 30));
|
2012-08-14 18:51:48 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-11-30 12:07:59 +04:00
|
|
|
leakHunt.digProperty(prop, data, path, seen, direct, log);
|
|
|
|
}
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2012-11-30 12:07:59 +04:00
|
|
|
return reply;
|
2016-05-17 21:25:54 +03:00
|
|
|
};
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2012-11-30 12:07:59 +04:00
|
|
|
leakHunt.hide = [ /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/ ];
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2012-11-30 12:07:59 +04:00
|
|
|
leakHunt.noRecurse = [
|
|
|
|
/^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/,
|
|
|
|
/^Window$/, /^Document$/,
|
|
|
|
/^XULDocument$/, /^XULElement$/,
|
|
|
|
/^DOMWindow$/, /^HTMLDocument$/, /^HTML.*Element$/, /^ChromeWindow$/
|
|
|
|
];
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
leakHunt.digProperty = function (prop, data, path, seen, direct, log) {
|
|
|
|
let newPath = path.slice();
|
2012-11-30 12:07:59 +04:00
|
|
|
newPath.push(prop);
|
2016-11-11 12:09:50 +03:00
|
|
|
let prefix = new Array(newPath.length).join(" ");
|
2012-11-30 12:07:59 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
let recurse = true;
|
|
|
|
let message = leakHunt.getType(data);
|
2012-11-30 12:07:59 +04:00
|
|
|
|
|
|
|
if (leakHunt.matchesAnyPattern(message, leakHunt.hide)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-01 22:45:22 +03:00
|
|
|
if (message === "function" && !direct.includes(prop)) {
|
2012-11-30 12:07:59 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-17 21:25:54 +03:00
|
|
|
if (message === "string") {
|
2016-11-11 12:09:50 +03:00
|
|
|
let extra = data.length > 10 ? data.substring(0, 9) + "_" : data;
|
2012-11-30 12:07:59 +04:00
|
|
|
message += ' "' + extra.replace(/\n/g, "|") + '"';
|
|
|
|
recurse = false;
|
2016-11-11 12:09:50 +03:00
|
|
|
} else if (leakHunt.matchesAnyPattern(message, leakHunt.noRecurse)) {
|
2016-05-17 21:25:54 +03:00
|
|
|
message += " (no recurse)";
|
2012-11-30 12:07:59 +04:00
|
|
|
recurse = false;
|
2018-02-01 22:45:22 +03:00
|
|
|
} else if (seen.includes(data)) {
|
2016-05-17 21:25:54 +03:00
|
|
|
message += " (already seen)";
|
2012-11-30 12:07:59 +04:00
|
|
|
recurse = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recurse) {
|
|
|
|
seen.push(data);
|
2016-11-11 12:09:50 +03:00
|
|
|
let lines = leakHunt.inner(data, newPath, seen);
|
2012-11-30 12:07:59 +04:00
|
|
|
if (lines.length == 0) {
|
2016-05-17 21:25:54 +03:00
|
|
|
if (message !== "function") {
|
|
|
|
log(prefix + prop + " = " + message + " { }");
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
2016-11-11 12:09:50 +03:00
|
|
|
} else {
|
2016-05-17 21:25:54 +03:00
|
|
|
log(prefix + prop + " = " + message + " {");
|
|
|
|
lines.forEach(function (line) {
|
2012-11-30 12:07:59 +04:00
|
|
|
log(line);
|
|
|
|
});
|
2016-05-17 21:25:54 +03:00
|
|
|
log(prefix + "}");
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
2016-11-11 12:09:50 +03:00
|
|
|
} else {
|
2016-05-17 21:25:54 +03:00
|
|
|
log(prefix + prop + " = " + message);
|
2012-11-30 12:07:59 +04:00
|
|
|
}
|
|
|
|
};
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
leakHunt.matchesAnyPattern = function (str, patterns) {
|
|
|
|
let match = false;
|
2016-05-17 21:25:54 +03:00
|
|
|
patterns.forEach(function (pattern) {
|
2012-08-14 18:51:48 +04:00
|
|
|
if (str.match(pattern)) {
|
|
|
|
match = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return match;
|
2012-11-30 12:07:59 +04:00
|
|
|
};
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
leakHunt.getType = function (data) {
|
2012-08-14 18:51:48 +04:00
|
|
|
if (data === null) {
|
2016-05-17 21:25:54 +03:00
|
|
|
return "null";
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
|
|
|
if (data === undefined) {
|
2016-05-17 21:25:54 +03:00
|
|
|
return "undefined";
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
let type = typeof data;
|
2016-05-17 21:25:54 +03:00
|
|
|
if (type === "object" || type === "Object") {
|
2012-11-30 12:07:59 +04:00
|
|
|
type = leakHunt.getCtorName(data);
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return type;
|
2012-11-30 12:07:59 +04:00
|
|
|
};
|
2012-08-14 18:51:48 +04:00
|
|
|
|
2016-11-11 12:09:50 +03:00
|
|
|
leakHunt.getCtorName = function (obj) {
|
2012-08-14 18:51:48 +04:00
|
|
|
try {
|
2016-11-11 12:09:50 +03:00
|
|
|
if (obj.constructor && obj.constructor.name) {
|
|
|
|
return obj.constructor.name;
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
2016-11-11 12:09:50 +03:00
|
|
|
} catch (ex) {
|
2016-05-17 21:25:54 +03:00
|
|
|
return "UnknownObject";
|
2012-08-14 18:51:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// If that fails, use Objects toString which sometimes gives something
|
|
|
|
// better than 'Object', and at least defaults to Object if nothing better
|
2016-11-11 12:09:50 +03:00
|
|
|
return Object.prototype.toString.call(obj).slice(8, -1);
|
2012-11-30 12:07:59 +04:00
|
|
|
};
|