Last active
November 25, 2024 21:46
-
-
Save stracker-phil/e5b3bbd5d5eb4ffb2acdcda90d8bd04f to your computer and use it in GitHub Desktop.
Recursively searches the entire object tree for a given value
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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){ | |
return ( | |
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 | |
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" | |
); | |
} | |
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
Thanks heaps for the script Phil, and to Tom for the original StackOverflow answer I found this Gist from! https://stackoverflow.com/questions/12102425/recursively-search-for-a-value-in-global-variables-and-its-properties/12103127#12103127
Couple issues I had:
skip
function should check ifobj === window.gsResults
and mark that asisInvalid = true;
because obviously we are going to see our search string in the global results.isElement
may throw aDOMException: Blocked a frame with origin
if this script tries to inspect iframe objects that it does not have access to. Simplist solution for this is just add atry{}catch
andreturn false;
to theisElement
function. It's possible that this could still be an issue elsewhere in the code, I've not looked hard enough.Further thoughts:
It would be nice if the results could be clicked to immediately inspect that object. I know Chrome supplies some cool console logging tools but I've not investigated further.
I see
searched
prevents multiple visits to the same parent, anddisplay
shows a shorter address via a document.getElementById. But the address fromstartObject
is lost. It would be nice to keep the shortest path to the same parent by keeping a track of the path each time an alreadysearched
element is visited again and updating theaddr
. This way, when the results are displayed you get both thedocument.getEleme...
and the shortest full address found from thestartObject
.