Skip to content

Instantly share code, notes, and snippets.

@pilate
Forked from stracker-phil/global-search.js
Last active July 21, 2021 14:27
Show Gist options
  • Save pilate/4eeaa5766ace769a71dc5e7486e726fa to your computer and use it in GitHub Desktop.
Save pilate/4eeaa5766ace769a71dc5e7486e726fa to your computer and use it in GitHub Desktop.
Recursively searches the entire object tree for a given value
/**
* Recursively searches the startObject for the given value.
*
* All matches are displayed in the browser console and stored in the global variable "gsResults"
* The function tries to simplify DOM element names by using their ID, when possible.
*
* Usage samples:
*
* globalSearch( document, 'someValue' ); // Search entire DOM document for the string value.
* globalSearch( document, '^start' ); // Simple regex search (function recognizes prefix/suffix patterns: "^..." or "...$").
* globalSearch( document, new Regex('[a|b]') ); // Advanced Regex search.
* globalSearch( 'myObj', 'value', 'key' ); // Searches all keys with the name "value" inside the object window.myObj
* globalSearch( window, 'value', 'key', 3 ); // Ends the search after 3 results were found.
* globalSearch( window, 'value', 'all', 3 ); // Finds the first three occurances of "value" in either a object key or value.
*
* globalSearch( document, 'some_value', 'key', 20 )
* Output:
*
* 1. Match: KEY
* Value: [string] on
* Address: window.gsResults[1]
* document.getElementById("the-id").childNodes[1].__reactInternalInstance$v9o4el5z24e.alternate..memoizedProps._owner.alternate.memoizedState.attrs.some_value
*
* @param {string|object} startObject The object to search. Either an object, or the global object name as string.
* @param {mixed} value The value to find. Can be any type, or a Regex string.
* @param {string} searchField Either of [value|key|all].
* @param {int} limit Max results to return. Default is -1, which means "unlimited".
*/
function globalSearch(startObject, value, searchField = 'value', limit = -1) {
var startName = '';
if ('string' === typeof startObject) {
startName = startObject;
startObject = eval(startObject);
} else if (window === startObject) {
startName = 'window';
} else if (document === startObject) {
startName = 'document';
}
var stack = [[startObject, startName, startName]];
var searched = [];
var found = 0;
var count = 1;
var isRegex = 'string' === typeof value && (-1 !== value.indexOf('*') || '^' === value[0] || '$' === value[value.length-1]);
window.gsResults = [];
if (isRegex) {
value = new RegExp(value);
} else if ('object' === typeof value && value instanceof RegExp) {
isRegex = true;
}
if (!searchField) {
searchField = 'value';
}
if (-1 === ['value', 'key', 'all'].indexOf(searchField)) {
console.error('The "searchField" parameter must be either of [value|key|all]. Found:', searchField);
return;
}
function isArray(test) {
var type = Object.prototype.toString.call(test);
return '[object Array]' === type || '[object NodeList]' === type;
}
function isElement(o){
try {
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
} catch (e) {
return false;
}
}
function isMatch(item) {
if (isRegex) {
return value.test(item);
} else {
return item === value;
}
}
function result(type, address, shortAddr, value) {
var msg = [];
found++;
window.gsResults[found] = {
match: type,
value: value,
pathOrig: address,
pathShort: shortAddr
};
msg.push(found + ". Match: \t" + type.toUpperCase()),
msg.push(" Value: \t[" + (typeof value ) + '] ' + value);
msg.push(" Address: \twindow.gsResults[" + found + ']');
msg.push('%c' + shortAddr);
console.log(msg.join("\n"), 'background:Highlight;color:HighlightText;margin-left:12px');
}
function skip(obj, key) {
var traversing = [
'firstChild',
'previousSibling',
'nextSibling',
'lastChild',
'previousElementSibling',
'nextElementSibling',
'firstEffect',
'nextEffect',
'lastEffect'
];
var scopeChange = [
'ownerDocument',
];
var deprecatedDOM = [
'webkitStorageInfo',
];
if (-1 !== traversing.indexOf(key)) { return true; }
if (-1 !== scopeChange.indexOf(key)) { return true; }
if (-1 !== deprecatedDOM.indexOf(key)) { return true; }
var isInvalid = false;
try {
obj[key]
} catch(ex) {
isInvalid = true;
}
return isInvalid;
}
while (stack.length) {
if (limit > 0 && found >= limit) { break; }
var fromStack = stack.pop();
var obj = fromStack[0];
var address = fromStack[1];
var display = fromStack[2];
if ('key' !== searchField && isMatch(obj)) {
result( 'value', address, display, obj);
if (limit > 0 && found >= limit) { break; }
}
if (obj && typeof obj == 'object' && -1 === searched.indexOf(obj)) {
var objIsArray = isArray(obj);
if ( isElement(obj) && obj.id ) {
display = 'document.getElementById("' + obj.id + '")';
}
for (i in obj) {
if (skip(obj, i)) { continue; }
var subAddr = (objIsArray || 'number' === typeof i) ? '[' + i + ']' : '.' + i;
var addr = address + subAddr;
var displayAddr = display + subAddr;
stack.push([obj[i], addr, displayAddr]);
count++;
if ('value' !== searchField && isMatch(i)) {
result( 'key', address, displayAddr, obj[i]);
if (limit > 0 && found >= limit) { break; }
}
}
searched.push(obj);
}
}
searched = null;
console.log('-----');
console.log('All Done!');
console.log('Searched', count.toLocaleString(), 'items');
console.log('Found', found.toLocaleString(), 'results');
return found;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment