Skip to content

Instantly share code, notes, and snippets.

@Ceiridge
Last active August 18, 2022 05:40
Show Gist options
  • Save Ceiridge/0f38c8c91c5c56781f8140e80f3a391c to your computer and use it in GitHub Desktop.
Save Ceiridge/0f38c8c91c5c56781f8140e80f3a391c to your computer and use it in GitHub Desktop.
Log/Hook all native JavaScript functions recursively
// This hooks all functions recursively from window to log all native JavaScript functions
// The best way to use this is to inject this script at document-start via TamperMonkey.
// Default ignores (should be most likely extended):
window.__IGNORE_CALLTREE = new Set(["::requestAnimationFrame", "::getComputedStyle", "document::getElementsByTagName", "::setTimeout", "::clearTimeout", "document::getElementById", "Math::random", "Element.prototype::getAttribute", "::parseFloat", "::parseInt", "CSSStyleDeclaration.prototype::getPropertyValue"]);
(function () {
// You can ignore methods the following way:
// window.__IGNORE_CALLTREE = new Set();
// window.__IGNORE_CALLTREE.add("document::yourMethod");
const conLog = console.log;
const conTrace = console.trace;
const conGroupOpen = console.group;
const conGroup = console.groupCollapsed;
const conGroupEnd = console.groupEnd;
const isNative = (fn) => /\{\s+\[native code\]/.test(Function.prototype.toString.call(fn));
const hookFunction = (obj, name, calltree) => {
const original = obj[name];
if (calltree.endsWith(".")) {
calltree = calltree.substring(0, calltree.length - 1);
}
const callTreeName = calltree + "::" + name;
obj[name] = function () {
let result;
let errResult = null;
try {
if (calltree.endsWith(".prototype.constructor.")) {
result = new original(...arguments);
} else {
result = original.apply(this, arguments);
}
} catch (err) {
if (err.message.includes("Failed to construct") && err.message.includes("Please use the 'new' operator")) {
try {
result = new original(...arguments);
} catch (err2) {
errResult = err2;
}
} else {
console.error(err);
errResult = err;
}
}
if (window.__IGNORE_CALLTREE ? (!window.__IGNORE_CALLTREE.has(callTreeName)) : true) {
conGroup(calltree + "::%c" + name + ` %c[${arguments.length}]${result === undefined ? "" : (" => " + (result === null ? "null" : (typeof result)))}`, "color: orange", "color: gray");
conGroupOpen("%cArguments:", "color: gray");
for (const arg of arguments) {
conLog(arg);
}
conGroupEnd();
conGroupOpen("%cReturn Value:", "color: gray");
conLog(result);
conGroupEnd();
conGroup("%cStacktrace:", "color: gray");
conTrace();
conGroupEnd();
conGroupEnd();
}
if (errResult) {
throw errResult;
}
return result;
};
};
const WIN_FORBIDDEN = new Set(["window", "frames", "frameElement", "opener", "parent", "top", "self", "Object", "Function", "Array", "String", "Symbol", "this", "RegExp", "Number", "Intl", "Boolean", "Set"]);
// const DOC_FORBIDDEN = new Set(["doctype", "documentElement", "activeElement", "anchors", "applets", "body", "embeds", "forms", "head", "images", "implementation", "links", "scripts", "title", "scrollingElement", "children"]);
const hookAll = (obj, calltree, previous) => {
if (previous.some(prev => prev === obj)) {
return;
}
// conLog(calltree);
previous = [...previous, obj];
for (const name of Object.getOwnPropertyNames(obj)) {
try {
if (obj === window && (WIN_FORBIDDEN.has(name) || (typeof name === "number"))) {
continue;
}
const found = obj[name];
const typ = typeof found;
if (found === null || found === undefined) {
continue;
}
if (obj === document && (typ !== "function")) {
continue;
}
if (typ === "function") {
if (isNative(found) && (found.prototype ? (found.prototype.constructor !== found) : true)) {
hookFunction(obj, name, calltree);
}
hookAll(found, calltree + name + ".", previous);
} else if (typ === "object") {
if (found.prototype) {
// conLog(found);
hookAll(found.prototype, calltree + "_", previous);
} else {
hookAll(found, calltree + name + ".", previous);
}
}
} catch (_) {}
}
};
hookAll(window, "", []);
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment