Skip to content

Instantly share code, notes, and snippets.

@jlgrall
Last active August 29, 2015 14:25
Show Gist options
  • Save jlgrall/0b8871bb0d84dc119630 to your computer and use it in GitHub Desktop.
Save jlgrall/0b8871bb0d84dc119630 to your computer and use it in GitHub Desktop.
Find an object or a value recursively in a webpage
// 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