查看全局属性
// ==UserScript==
// @name 查看全局属性
// @description 一键查看挂载到window上的非原生属性,并注入一个$searchKey函数搜索属性名。
// @namespace cxxjackie
// @author cxxjackie
// @version 2.0.4
// @match *://*/*
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @run-at document-idle
// @homepage https://bbs.tampermonkey.net.cn/thread-916-1-1.html
// @supportURL https://bbs.tampermonkey.net.cn/thread-916-1-1.html
// ==/UserScript==
(function() {
'use strict';
// 最大搜索深度,0不设限。此参数影响注入时间和内存占用,谨慎修改
const MAX_DEPTH = 10;
// iframe的id
const IFRAME_ID = 'iframe_for_test';
// 搜索函数名
const SEARCH_FUNCTION_NAME = '$searchKey';
// 忽略的属性
const IGNORE_PROPS = ['length', 'arguments', 'caller', 'prototype', 'constructor'];
// vue额外忽略的属性
const VUE_IGNORE_PROPS = [];
// react额外忽略的属性
const REACT_IGNORE_PROPS = ['memoizedState', 'updateQueue'];
const document = unsafeWindow.document;
const tag = unsafeWindow === unsafeWindow.top ? 'top' : location.origin + location.pathname;
GM_registerMenuCommand(tag, async () => {
let iframe = document.getElementById(IFRAME_ID);
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = IFRAME_ID;
iframe.style.display = 'none';
document.body.appendChild(iframe); // iframe不能移除
}
const iWindow = iframe.contentWindow;
// 反劫持
const {Object, String, Array, Set, Map, WeakMap, RegExp, Promise, console} = iWindow;
// 获取类型
const objToString = Object.prototype.toString;
function getType(item) {
const str = new String(objToString.call(item));
return str.slice(8, -1).toLowerCase();
}
// 获取全属性,包括原型链上的
const objProto = unsafeWindow.Object.prototype;
const funcProto = unsafeWindow.Function.prototype;
function getAllProps(obj) {
const props = new Set();
let proto = obj;
while (proto !== null && proto !== objProto && proto !== funcProto) {
Object.getOwnPropertyNames(proto).forEach(prop => props.add(prop));
proto = Object.getPrototypeOf(proto);
}
return props;
}
// 是否为纯数字
const numRE = new RegExp(/^\d+$/);
function isNum(str) {
return numRE.test(str);
}
// 空函数
function emptyFn() {}
// 获取所有元素和注释节点
function getAllNodes() {
const result = new Array(document.documentElement);
function getChildren(node) {
for (const child of node.childNodes) {
if (child.nodeType === 1 || child.nodeType === 8) {
result.push(child);
getChildren(child);
}
}
}
getChildren(document.documentElement);
return result;
}
// 属性收集类
class KeyCollector {
constructor(ignoreProps) {
this.ignoreProps = ignoreProps;
this.allKeys = new Map();
this.taskList = new Array();
this._init();
}
_init() {
this.refs = new WeakMap([[unsafeWindow, {path: 'window', root: 'window'}]]);
this.tempKeys = new Map();
this.discardKeys = new Map();
}
_calcDepth(obj) {
let _obj = obj;
let item = this.refs.get(obj);
let depth = 0;
while (item.parent) {
_obj = item.parent;
item = this.refs.get(_obj);
depth++;
}
return depth;
}
async _collectKeys(obj, item, recordDiscard = true, depth = 0) {
if (obj === null || (typeof obj !== 'function' && typeof obj !== 'object')) return;
if (obj instanceof Node) return;
if (MAX_DEPTH > 0 && depth >= MAX_DEPTH) {
if (recordDiscard) this.discardKeys.set(obj, item);
return;
}
if (this.refs.has(obj)) {
if (depth < this._calcDepth(obj)) this.refs.set(obj, item);
return;
}
this.refs.set(obj, item);
const keys = getAllProps(obj);
for (const key of keys) {
if (!this.ignoreProps.has(key)) {
let value;
try {
value = obj[key];
} catch (e) {
continue;
}
if (value instanceof Promise) {
value.catch(emptyFn);
continue;
}
const val = this.tempKeys.get(key) || new Set();
val.add({
value: value,
parent: obj
});
this.tempKeys.set(key, val);
const _item = {
root: item.root,
parent: obj,
key: key,
extra: item.extra
};
await this._collectKeys(value, _item, recordDiscard, depth + 1);
}
}
}
_generatePath(obj) {
if (obj.path) return;
if (!obj.parent) {
obj.path = obj.root;
return;
}
const parent = this.refs.get(obj.parent);
obj.extra = parent.extra;
this._generatePath(parent);
obj.path = parent.path + (isNum(obj.key) ? `[${obj.key}]` : `['${obj.key}']`);
}
_generateAllPaths() {
for (const [key, val] of this.tempKeys) {
for (const obj of val) {
const item = this.refs.get(obj.value);
if (item && item.key === key) {
if (!item.added) {
this._generatePath(item);
this.addKey(key, item.path, item.extra);
item.added = true;
}
} else {
const parent = this.refs.get(obj.parent);
this._generatePath(parent);
const path = isNum(key) ? `${parent.path}[${key}]` : `${parent.path}['${key}']`;
this.addKey(key, path, parent.extra);
}
}
}
}
addKey(key, path, extra = null) {
const arr = this.allKeys.get(key) || new Set();
arr.add(extra ? {path, ...extra} : path);
this.allKeys.set(key, arr);
}
collect(obj, root, extra = null) {
let key;
if (extra) {
key = extra.prop;
} else {
const keys = String.prototype.match.call(root, /(?<=[.\[]['"]?)[^'".\[\]]+/g);
if (keys) key = keys.pop();
}
this.taskList.push(this._collectKeys(obj, {root, key, extra}));
}
async getAllKeys() {
await Promise.all(this.taskList);
for (const [obj, item] of this.discardKeys) {
const depth = this._calcDepth(item.parent) + 1;
if (depth < MAX_DEPTH) {
await this._collectKeys(obj, item, false, depth);
}
}
this._generateAllPaths();
this._init();
return this.allKeys;
}
}
// 获取全局属性
const globalProps = new Object();
const wKeys = Object.getOwnPropertyNames(unsafeWindow);
const iKeys = Object.getOwnPropertyNames(iWindow);
for (const key of wKeys) {
if (!isNum(key) && !iKeys.includes(key)) {
const type = getType(unsafeWindow[key]);
globalProps[type] = globalProps[type] || new Array();
globalProps[type].push(key);
}
}
console.log(`${tag} 全局属性:\n%o`, globalProps);
// 注入函数
const kc = new KeyCollector(new Set(IGNORE_PROPS));
for (const type in globalProps) {
for (const key of globalProps[type]) {
const path = `window['${key}']`;
kc.addKey(key, path);
kc.collect(unsafeWindow[key], path);
}
}
const globalKeys = await kc.getAllKeys();
const vkc = new KeyCollector(new Set([...IGNORE_PROPS, ...VUE_IGNORE_PROPS]));
const rkc = new KeyCollector(new Set([...IGNORE_PROPS, ...REACT_IGNORE_PROPS]));
for (const node of getAllNodes()) {
for (const prop of Object.getOwnPropertyNames(node)) {
if (prop.startsWith('__vue')) {
vkc.collect(node[prop], `node['${prop}']`, {node});
}
if (prop.startsWith('__react')) {
rkc.collect(node[prop], `node['${prop}']`, {node});
}
}
}
const vueKeys = await vkc.getAllKeys();
const reactKeys = await rkc.getAllKeys();
unsafeWindow[SEARCH_FUNCTION_NAME] = (key, fuzzy) => {
if (fuzzy) {
const result = new Array();
const lowerKey = key.toLowerCase();
for (const _key of globalKeys.keys()) {
if (_key.toLowerCase().includes(lowerKey)) {
result.push(...globalKeys.get(_key));
}
}
for (const _key of vueKeys.keys()) {
if (_key.toLowerCase().includes(lowerKey)) {
result.push(...vueKeys.get(_key));
}
}
for (const _key of reactKeys.keys()) {
if (_key.toLowerCase().includes(lowerKey)) {
result.push(...reactKeys.get(_key));
}
}
console.log(result);
} else {
const globalResult = globalKeys.get(key) || [];
const vueResult = vueKeys.get(key) || [];
const reactResult = reactKeys.get(key) || [];
console.log([...globalResult, ...vueResult, ...reactResult]);
}
};
console.log(`${SEARCH_FUNCTION_NAME}函数已注入!`);
});
})();