Last active
August 29, 2015 14:25
-
-
Save jlgrall/0b8871bb0d84dc119630 to your computer and use it in GitHub Desktop.
Find an object or a value recursively in a webpage
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
// https://gist.github.com/jlgrall/0b8871bb0d84dc119630 | |
function findVal(base, matcher, skip, filter, debug) { | |
"use strict"; | |
// Check the arguments: | |
var i = 2, | |
args = arguments; | |
skip = Array.isArray(args[i]) ? args[i++] : []; | |
filter = typeof args[i] === "function" ? args[i++] : function() { return true; }; | |
debug = args[i] === true; | |
// Init: | |
var hasOwn = Object.prototype.hasOwnProperty, | |
toString = Object.prototype.toString, | |
seenVals = {}; | |
[undefined, null, true, false, 0, -1, 1, ""].concat(skip).forEach(notSeen); | |
// Functions for discovering repetitions in an array: | |
function seqsEquals2(array, end1, end2, seqLen) { | |
while(seqLen-- > 0) { | |
if(array[end1 - seqLen] !== array[end2 - seqLen]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
function seqsEqualsN(array, end, n, seqLen) { | |
for(var i = n - 1; i > 0; i--) { | |
if(!seqsEquals2(array, end, end - (i * seqLen), seqLen)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
function findRepetitionsAtEnd(array, nbRepeat) { // nbRepeat === 1 means sequence is found 2 times | |
var arrLen = array.length; | |
if(arrLen < nbSeqs) return false; | |
var end = arrLen - 1, | |
nbSeqs = nbRepeat + 1, | |
maxSeqLength = Math.floor(end / nbSeqs); // The max length of a seq allowing nbSeqs to fit into array | |
// Try with all possible sequence lengths: | |
for(var seqLen = 1; seqLen <= maxSeqLength; seqLen++) { | |
if(seqsEqualsN(array, end, nbSeqs, seqLen)) { | |
return { | |
nbRepeat: nbRepeat, | |
nbSeqs: nbSeqs, | |
seqLen: seqLen, | |
start: end - (nbSeqs * seqLen) + 1, | |
end: end | |
}; | |
} | |
} | |
return false; | |
} | |
// Guard: where we monitor the processing | |
// We report informations and allow user control if the processing takes time. | |
var guardCounter = 0, // Count number of processed values | |
guardTimeCounter = 0, // Accumulate processing times | |
guardLastResume = Date.now(), | |
sec = 1000, // How many millisecs in a sec | |
guardRepetitions = [], // Store found repetitions informations | |
guardSTOP = false; // Set to true to force stop processings | |
// Config guard: | |
var guardPauseEvery = 5 * sec, // Regularly ask continuation confirmation to user | |
guardNbRepetitions = -1; // -1 to disable | |
function guardPause() { | |
var elapsedSinceLastPause = Date.now() - guardLastResume; | |
return guardTimeCounter += elapsedSinceLastPause; | |
} | |
function guardContinue() { | |
guardLastResume = Date.now(); | |
} | |
function guardConfirm(path, foundPaths) { | |
var guardStats = "Checked " + guardCounter + " objects in " + ((guardTimeCounter) / sec) + " secs.", | |
repetitions = "Skipped " + guardRepetitions.length + " possible reference cycles(s) (repetitions).", | |
found = "Found " + foundPaths.length + " match(es).", | |
continueStr = "=> Continue searching (for " + (guardPauseEvery / sec) + " secs) ?", | |
currentPath = "Current path (length: " + path.length + "): " + joinPath(path), | |
res = window.confirm(guardStats + (guardRepetitions.length ? "\n" + repetitions : "") + "\n" + found + "\n" + continueStr + "\n\n" + currentPath); | |
if(!res) guardSTOP = true; | |
return res; | |
} | |
function GuardRepetitionException(pathLength) { | |
this.message = "Sends a message higher in the stack trace."; | |
this.name = "GuardRepetitionException"; | |
this.pathLength = pathLength; | |
} | |
function guard(val, path, foundPaths) { | |
if(guardSTOP) return false; | |
var cont = true, | |
repetitionInfo = guardNbRepetitions > 0 ? findRepetitionsAtEnd(path, guardNbRepetitions) : false; | |
if(repetitionInfo) { | |
path = path.slice(); | |
repetitionInfo.path = path; | |
guardRepetitions.push(repetitionInfo); | |
throw new GuardRepetitionException(repetitionInfo.start + repetitionInfo.seqLen); | |
} | |
if(++guardCounter % 500 === 0) { | |
var elapsedSinceLastPause = Date.now() - guardLastResume; | |
if(elapsedSinceLastPause > guardPauseEvery) { | |
guardPause(); | |
if(!guardConfirm(path, foundPaths)) cont = false; | |
guardContinue(); | |
} | |
} | |
return cont; | |
} | |
// Uncomment to disable guard: | |
//function guard() { return true; } | |
// Takes a path as array and returns a | |
// valid JavaScript string representation of the path: | |
function joinPath(path) { | |
path = path.reduce(function(newPath, key) { | |
if(/^\d/.test(key)) { | |
var pos = newPath.length - 1; | |
if(pos === -1) newPath[++pos] = ""; | |
newPath[pos] += "[" + key + "]"; | |
} | |
else newPath.push(key); | |
return newPath; | |
}, []); | |
return path.join("."); | |
} | |
// Takes a val, returns true if val was already seen and remembers it: | |
function notSeen(val) { | |
var type = toString.call(val); | |
if(!seenVals[type]) seenVals[type] = []; | |
var notSeen = seenVals[type].indexOf(val) === -1; | |
if(notSeen) seenVals[type].push(val); | |
return notSeen; | |
} | |
// Main function that process a value and calls | |
// itself recursively if the value has properties: | |
function processVal(val, path, foundPaths) { | |
if(!guard(val, path, foundPaths)) return foundPaths; | |
try { | |
if(matcher(val, path)) foundPaths.push(path.slice()); | |
} catch(e) { | |
console.error(e, "Current path:", joinPath(path)); | |
} | |
if(notSeen(val) && typeof val !== "string") { | |
var length = path.length; | |
for(var key in val) { | |
if(hasOwn.call(val, key)) { | |
try { | |
var prop = val[key]; | |
path[length] = key; | |
if(filter(prop, path, key)) { | |
try { | |
processVal(prop, path, foundPaths); | |
} catch(e) { | |
if(e instanceof GuardRepetitionException | |
&& e.pathLength === length) { | |
path.length = length + 1; | |
} | |
else throw e; | |
} | |
} | |
} catch(e) { | |
if(e instanceof GuardRepetitionException) throw e; | |
var lastKey = path[length]; | |
console.error(e, "Current key:", key, "Last key:", lastKey, "Current path:", joinPath(path.slice(0, length))); | |
continue; | |
} | |
} | |
} | |
path.length = length; | |
} | |
return foundPaths; | |
} | |
// Start recursively processing the base: | |
var foundPaths = processVal(base, [], []); | |
// Before exiting, display informations: | |
if(debug) { | |
guardPause(); | |
var guardStats = "Checked " + guardCounter + " objects in " + ((guardTimeCounter) / sec) + " secs.", | |
found = "Found " + foundPaths.length + " match(es):"; | |
console.log(guardStats); | |
console.log("seenVals:", seenVals); | |
if(guardRepetitions.length > 0) { | |
console.log("Repetitions:", guardRepetitions.map(function(repetitionInfo) { | |
var path = repetitionInfo.path, | |
truncatedPath = path.slice(0, repetitionInfo.start); | |
return joinPath(path) + " => " + joinPath(truncatedPath); | |
})); | |
} | |
console.log(found, foundPaths.map(joinPath)); | |
} | |
if(guardSTOP) console.error("SEARCHING INTERRUPTED !"); | |
return foundPaths; | |
} | |
var foundPaths = findVal(window, function(val, path) { // Matcher: | |
return val && typeof val === "object" && "play" in val && "pause" in val; | |
}, [ // Skip: | |
window.clientInformation && window.clientInformation.mimeTypes, | |
window.clientInformation && window.clientInformation.plugins, | |
window.SVGPathSegClosePath, | |
window.jQuery && window.jQuery.cache | |
], | |
function(prop, path, key) { // Filter: | |
return !/^(next|previous)(Element)?Sibling$/.test(key); | |
}, true); // debug |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment