Q = {};
Q.globalNames = Object.keys(window); // snapshot baseline
Q.globalNamesAdded = function () {
const current = Object.keys(window);
const baseline = Q.globalNames;
const added = [];
for (let i = 0; i < current.length; i++) {
if (!baseline.includes(current[i])) {
added.push(current[i]);
}
}
return added;
};
Q.walkGlobalsAsync = function (filterFn, options = {}) {
const seen = new WeakSet();
const found = new Set();
const pathMap = new WeakMap();
const maxDepth = options.maxDepth || 5;
const includeStack = options.includeStack || false;
const logEvery = options.logEvery || 100;
const startingKeys = Q.globalNamesAdded
? Q.globalNamesAdded()
: Object.keys(window);
let totalChecked = 0;
let matchesFound = 0;
function walk(obj, path = 'window', depth = 0) {
if (!obj || typeof obj !== 'object') return;
if (seen.has(obj)) return;
seen.add(obj);
totalChecked++;
if (totalChecked % logEvery === 0) {
console.log(`Checked ${totalChecked} objects, found ${matchesFound}`);
}
if (filterFn(obj)) {
found.add(obj);
matchesFound++;
if (includeStack) {
pathMap.set(obj, path);
console.log(`[FOUND] ${path}`, obj);
} else {
console.log(`[FOUND]`, obj);
}
}
if (depth >= maxDepth) return;
const skipKeys = obj instanceof HTMLElement
? new Set([
'parentNode', 'parentElement', 'nextSibling', 'previousSibling',
'firstChild', 'lastChild', 'children', 'childNodes',
'ownerDocument', 'style', 'classList', 'dataset',
'attributes', 'innerHTML', 'outerHTML',
'nextElementSibling', 'previousElementSibling'
])
: null;
for (const key in obj) {
if (skipKeys && skipKeys.has(key)) continue;
try {
walk(obj[key], path + '.' + key, depth + 1);
} catch (e) {}
}
}
let i = 0;
function nextBatch() {
const batchSize = 10;
const end = Math.min(i + batchSize, startingKeys.length);
for (; i < end; i++) {
try {
walk(window[startingKeys[i]], 'window.' + startingKeys[i], 0);
} catch (e) {}
}
if (i < startingKeys.length) {
setTimeout(nextBatch, 0); // Schedule next batch
} else {
console.log(`Done. Found ${matchesFound} retained objects.`);
if (includeStack) {
console.log([...found].map(obj => ({
object: obj,
path: pathMap.get(obj)
})));
} else {
console.log([...found]);
}
}
}
nextBatch();
};
Here is how you use it: Q.walkGlobalsAsync(
obj => obj instanceof HTMLElement && !document.contains(obj),
{ includeStack: true, maxDepth: 4, logEvery: 50 }
);
However -- note that this will NOT find objects retained by closures, even if you can find the closures themselves you're going to have to check their code manually.
1 comments