// ==UserScript==
// @name Everything-Hook
// @namespace https://gitee.com/HGJing/everthing-hook/
// @updateURL https://gitee.com/HGJing/everthing-hook/raw/master/src/everything-hook.js
// @version 0.2.7003
// @include *
// @description it can hook everything
// @author Cangshi
// @match http://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
/**
* ---------------------------
* Time: 2017/9/20 18:33.
* Author: Cangshi
* View: http://palerock.cn
* ---------------------------
*/
// 'use strict';
~function (utils) {
var _global = this;
var EHook = function () {
var _autoId = 1;
var _hookedMap = {};
this._getHookedMap = function () {
return _hookedMap;
};
this._getAutoStrId = function () {
return '__auto__' + _autoId++;
};
this._getAutoId = function () {
return _autoId++;
};
};
EHook.prototype = {
/**
* 获取一个对象的劫持id,若没有则创建一个
* @param context
* @return {*}
* @private
*/
_getHookedId: function (context) {
var hookedId = context.___hookedId;
if (hookedId == null) {
hookedId = context.___hookedId = this._getAutoStrId();
}
return hookedId;
},
/**
* 获取一个对象的劫持方法映射,若没有则创建一个
* @param context
* @return {*}
* @private
*/
_getHookedMethodMap: function (context) {
var hookedId = this._getHookedId(context);
var hookedMap = this._getHookedMap();
var thisTask = hookedMap[hookedId];
if (!utils.isExistObject(thisTask)) {
thisTask = hookedMap[hookedId] = {};
}
return thisTask;
},
/**
* 获取对应方法的hook原型任务对象,若没有则初始化一个。
* @param context
* @param methodName
* @private
*/
_getHookedMethodTask: function (context, methodName) {
var thisMethodMap = this._getHookedMethodMap(context);
var thisMethod = thisMethodMap[methodName];
if (!utils.isExistObject(thisMethod)) {
thisMethod = thisMethodMap[methodName] = {
original: undefined,
replace: undefined,
task: {
before: [],
current: undefined,
after: []
}
};
}
return thisMethod;
},
/**
* 执行多个方法并注入一个方法和参数集合
* @param context
* @param methods
* @param args
* @return result 最后一次执行方法的有效返回值
* @private
*/
_invokeMethods: function (context, methods, args) {
if (!utils.isArray(methods)) {
return;
}
var result = null;
utils.ergodicArrayObject(context, methods, function (_method) {
if (!utils.isFunction(_method.method)) {
return;
}
var r = _method.method.apply(this, args);
if (r != null) {
result = r;
}
});
return result;
},
/**
* 生成和替换劫持方法
* @param parent
* @param context
* @param methodName {string}
* @private
*/
_hook: function (parent, methodName, context) {
if (context === undefined) {
context = parent;
}
var method = parent[methodName];
var methodTask = this._getHookedMethodTask(parent, methodName);
if (!methodTask.original) {
methodTask.original = method;
}
if (utils.isExistObject(methodTask.replace) && utils.isFunction(methodTask.replace.method)) {
parent[methodName] = methodTask.replace.method(methodTask.original);
return;
}
var invokeMethods = this._invokeMethods;
// 组装劫持函数
var resultMethod = (function () {
var result = undefined;
if (methodTask.task.before.length > 0) {
if (utils.isExistObject(methodTask.task.current) && utils.isFunction(methodTask.task.current.method)) {
if (methodTask.task.after.length > 0) {
return function () {
var result = undefined;
invokeMethods(context, methodTask.task.before, [methodTask.original, arguments]);
result = methodTask.task.current.method.call(context, parent, methodTask.original, arguments);
var args = [];
args.push(methodTask.original);
args.push(arguments);
args.push(result);
var r = invokeMethods(context, methodTask.task.after, args);
result = (r != null ? r : result);
return result;
}
} else {
return function () {
var result = undefined;
invokeMethods(context, methodTask.task.before, [methodTask.original, arguments]);
result = methodTask.original.apply(context, arguments);
return result;
}
}
} else {
if (methodTask.task.after.length > 0) {
return function () {
var result = undefined;
invokeMethods(context, methodTask.task.before, [methodTask.original, arguments]);
result = methodTask.original.apply(context, arguments);
var args = [];
args.push(methodTask.original);
args.push(arguments);
args.push(result);
var r = invokeMethods(context, methodTask.task.after, args);
result = (r != null ? r : result);
return result;
}
} else {
return function () {
var result = undefined;
invokeMethods(context, methodTask.task.before, [methodTask.original, arguments]);
result = methodTask.original.apply(context, arguments);
return result;
}
}
}
} else {
if (utils.isExistObject(methodTask.task.current) && utils.isFunction(methodTask.task.current.method)) {
if (methodTask.task.after.length > 0) {
return function () {
var result = undefined;
result = methodTask.task.current.method.call(context, parent, methodTask.original, arguments);
var args = [];
args.push(methodTask.original);
args.push(arguments);
args.push(result);
var r = invokeMethods(context, methodTask.task.after, args);
result = (r != null ? r : result);
return result;
}
} else {
return function () {
var result = undefined;
result = methodTask.original.apply(context, arguments);
return result;
}
}
} else {
if (methodTask.task.after.length > 0) {
return function () {
var result = undefined;
result = methodTask.original.apply(context, arguments);
var args = [];
args.push(methodTask.original);
args.push(arguments);
args.push(result);
var r = invokeMethods(context, methodTask.task.after, args);
result = (r != null ? r : result);
return result;
}
} else {
return function () {
var result = undefined;
result = methodTask.original.apply(context, arguments);
return result;
}
}
}
}
}());
// var methodStr = '(function(){\n';
// methodStr = methodStr + 'var result = undefined;\n';
// if (methodTask.task.before.length > 0) {
// methodStr = methodStr + 'invokeMethods(context, methodTask.task.before,[methodTask.original, arguments]);\n';
// }
// if (utils.isExistObject(methodTask.task.current) && utils.isFunction(methodTask.task.current.method)) {
// methodStr = methodStr + 'result = methodTask.task.current.method.call(context, parent, methodTask.original, arguments);\n';
// } else {
// methodStr = methodStr + 'result = methodTask.original.apply(context, arguments);\n';
// }
// if (methodTask.task.after.length > 0) {
// methodStr = methodStr + 'var args = [];args.push(methodTask.original);args.push(arguments);args.push(result);\n';
// methodStr = methodStr + 'var r = invokeMethods(context, methodTask.task.after, args);result = (r!=null?r:result);\n';
// }
// methodStr = methodStr + 'return result;\n})';
// 绑定劫持函数
parent[methodName] = resultMethod;
},
/**
* 劫持一个方法
* @param parent
* @param methodName {string}
* @param config
*/
hook: function (parent, methodName, config) {
var hookedFailure = -1;
// 调用方法的上下文
var context = config.context !== undefined ? config.context : parent;
if (parent[methodName] == null) {
parent[methodName] = function () {
}
}
if (!utils.isFunction(parent[methodName])) {
return hookedFailure;
}
var methodTask = this._getHookedMethodTask(parent, methodName);
var id = this._getAutoId();
if (utils.isFunction(config.replace)) {
methodTask.replace = {
id: id,
method: config.replace
};
hookedFailure = 0;
}
if (utils.isFunction(config.before)) {
methodTask.task.before.push({
id: id,
method: config.before
});
hookedFailure = 0;
}
if (utils.isFunction(config.current)) {
methodTask.task.current = {
id: id,
method: config.current
};
hookedFailure = 0;
}
if (utils.isFunction(config.after)) {
methodTask.task.after.push({
id: id,
method: config.after
});
hookedFailure = 0;
}
if (hookedFailure === 0) {
this._hook(parent, methodName, context);
return id;
} else {
return hookedFailure;
}
},
/**
* 劫持替换一个方法
* @param parent
* @param context
* @param methodName {string}
* @param replace {function}
* @return {number} 该次劫持的id
*/
hookReplace: function (parent, methodName, replace, context) {
return this.hook(parent, methodName, {
replace: replace,
context: context
});
},
hookBefore: function (parent, methodName, before, context) {
return this.hook(parent, methodName, {
before: before,
context: context
});
},
hookCurrent: function (parent, methodName, current, context) {
return this.hook(parent, methodName, {
current: current,
context: context
});
},
hookAfter: function (parent, methodName, after, context) {
return this.hook(parent, methodName, {
after: after,
context: context
});
},
/**
* 劫持全局ajax
* @param methods {object} 劫持的方法
* @return {*|number} 劫持的id
*/
hookAjax: function (methods) {
var _this = this;
var hookMethod = function (methodName) {
if (utils.isFunction(methods[methodName])) {
// 在执行方法之前hook原方法
_this.hookBefore(this.xhr, methodName, methods[methodName]);
}
// 返回方法调用内部的xhr
return this.xhr[methodName].bind(this.xhr);
};
var getProperty = function (attr) {
return function () {
return this.hasOwnProperty(attr + "_") ? this[attr + "_"] : this.xhr[attr];
};
};
var setProperty = function (attr) {
return function (f) {
var xhr = this.xhr;
var that = this;
if (attr.indexOf("on") !== 0) {
this[attr + "_"] = f;
return;
}
if (methods[attr]) {
xhr[attr] = function () {
f.apply(xhr, arguments);
};
// on方法在set时劫持
_this.hookBefore(xhr, attr, methods[attr]);
// console.log(1,attr);
// xhr[attr] = function () {
// methods[attr](that) || f.apply(xhr, arguments);
// }
} else {
xhr[attr] = f;
}
};
};
return this.hookReplace(_global, 'XMLHttpRequest', function (XMLHttpRequest) {
return function () {
this.xhr = new XMLHttpRequest();
for (var propertyName in this.xhr) {
var property = this.xhr[propertyName];
if (utils.isFunction(property)) {
// hook 原方法
this[propertyName] = hookMethod.bind(this)(propertyName);
} else {
Object.defineProperty(this, propertyName, {
get: getProperty(propertyName),
set: setProperty(propertyName)
});
}
}
// 定义外部xhr可以在内部访问
this.xhr.xhr = this;
};
});
},
/**
* 解除劫持
* @param context 上下文
* @param methodName 方法名
* @param isDeeply {boolean=} 是否深度解除[默认为false]
* @param eqId {number=} todo 解除指定id的劫持[可选]
*/
unHook: function (context, methodName, isDeeply, eqId) {
if (!context[methodName] || !utils.isFunction(context[methodName])) {
return;
}
var methodTask = this._getHookedMethodTask(context, methodName);
context[methodName] = methodTask.original;
if (isDeeply) {
delete this._getHookedMethodMap(context)[methodName];
}
},
/**
* 保护一个对象使之不会被篡改
* @param parent
* @param methodName
*/
protect: function (parent, methodName) {
Object.defineProperty(parent, methodName, {
configurable: false,
writable: false
});
},
/**
* 装载插件
* @param option
*/
plugins: function (option) {
if (utils.isFunction(option.mount)) {
var result = option.mount.call(this, utils);
if (typeof option.name === 'string') {
_global[option.name] = result;
}
}
}
};
var eHook = new EHook();
var AHook = function () {
this.isHooked = false;
var autoId = 1;
this._urlDispatcherList = [];
this._getAutoId = function () {
return autoId++;
};
};
AHook.prototype = {
/**
* 执行配置列表中的指定方法组
* @param xhr
* @param methodName
* @param args
* @private
*/
_invokeAimMethods: function (xhr, methodName, args) {
var configs = utils.parseArrayByProperty(xhr.patcherList, 'config');
var methods = [];
utils.ergodicArrayObject(xhr, configs, function (config) {
if (utils.isFunction(config[methodName])) {
methods.push(config[methodName]);
}
});
return utils.invokeMethods(xhr, methods, args);
},
/**
* 根据url获取配置列表
* @param url
* @return {Array}
* @private
*/
_urlPatcher: function (url) {
var patcherList = [];
utils.ergodicArrayObject(this, this._urlDispatcherList, function (patcherMap, i) {
if (utils.urlUtils.urlMatching(url, patcherMap.patcher)) {
patcherList.push(patcherMap);
}
});
return patcherList;
},
/**
* 根据xhr对象分发回调请求
* @param xhr
* @param fullUrl
* @private
*/
_xhrDispatcher: function (xhr, fullUrl) {
var url = utils.urlUtils.getUrlWithoutParam(fullUrl);
xhr.patcherList = this._urlPatcher(url);
},
/**
* 转换响应事件
* @param e
* @param xhr
* @private
*/
_parseEvent: function (e, xhr) {
try {
Object.defineProperties(e, {
target: {
get: function () {
return xhr;
}
},
srcElement: {
get: function () {
return xhr;
}
}
});
} catch (error) {
console.warn('重定义返回事件失败,劫持响应可能失败');
}
return e;
},
/**
* 解析open方法的参数
* @param args
* @private
*/
_parseOpenArgs: function (args) {
return {
method: args[0],
fullUrl: args[1],
url: utils.urlUtils.getUrlWithoutParam(args[1]),
params: utils.urlUtils.getParamFromUrl(args[1]),
async: args[2]
};
},
/**
* 劫持ajax 请求参数
* @param argsObject
* @param argsArray
* @private
*/
_rebuildOpenArgs: function (argsObject, argsArray) {
argsArray[0] = argsObject.method;
argsArray[1] = utils.urlUtils.margeUrlAndParams(argsObject.url, argsObject.params);
argsArray[2] = argsObject.async;
},
/**
* 获取劫持方法的参数 [原方法,原方法参数,原方法返回值],剔除原方法参数
* @param args
* @return {*|Array.<T>}
* @private
*/
_getHookedArgs: function (args) {
// 将参数中'原方法'剔除
return Array.prototype.slice.call(args, 0).splice(1);
},
/**
* 响应被触发时调用的方法
* @param outerXhr
* @param funcArgs
* @private
*/
_onResponse: function (outerXhr, funcArgs) {
// 因为参数是被劫持的参数为[method(原方法),args(参数)],该方法直接获取参数并转换为数组
var args = this._getHookedArgs(funcArgs);
args[0][0] = this._parseEvent(args[0][0], outerXhr.xhr); // 强制事件指向外部xhr
// 执行所有的名为hookResponse的方法组
var results = this._invokeAimMethods(outerXhr, 'hookResponse', args);
// 遍历结果数组并获取最后返回的有效的值作为响应值
var resultIndex = -1;
utils.ergodicArrayObject(outerXhr, results, function (res, i) {
if (res != null) {
resultIndex = i;
}
});
if (resultIndex != -1) {
outerXhr.xhr.responseText_ = outerXhr.xhr.response_ = results[resultIndex];
}
},
/**
* 手动开始劫持
*/
startHook: function () {
var _this = this;
var normalMethods = {
// 方法中的this指向内部xhr
// 拦截响应
onreadystatechange: function () {
if (this.readyState == 4 && this.status == 200 || this.status == 304) {
_this._onResponse(this, arguments);
}
},
onload: function () {
_this._onResponse(this, arguments);
},
// 拦截请求
open: function () {
var args = _this._getHookedArgs(arguments);
var fullUrl = args[0][1];
_this._xhrDispatcher(this, fullUrl);
var argsObject = _this._parseOpenArgs(args[0]);
_this._invokeAimMethods(this, 'hookRequest', [argsObject]);
_this._rebuildOpenArgs(argsObject, args[0]);
},
send: function () {
var args = _this._getHookedArgs(arguments);
_this._invokeAimMethods(this, 'hookSend', args);
}
};
// 设置总的hookId
this.___hookedId = _global.eHook.hookAjax(normalMethods);
this.isHooked = true;
},
/**
* 注册ajaxUrl拦截
* @param urlPatcher
* @param configOrRequest
* @param response
* @return {number}
*/
register: function (urlPatcher, configOrRequest, response) {
if (!urlPatcher) {
return -1;
}
if (!utils.isExistObject(configOrRequest) && !utils.isFunction(response)) {
return -1;
}
var config = {};
if (utils.isFunction(configOrRequest)) {
config.hookRequest = configOrRequest;
}
if (utils.isFunction(response)) {
config.hookResponse = response;
}
if (utils.isExistObject(configOrRequest)) {
config = configOrRequest;
}
var id = this._getAutoId();
this._urlDispatcherList.push({
// 指定id便于后续取消
id: id,
patcher: urlPatcher,
config: config
});
// 当注册一个register时,自动开始运行劫持
if (!this.isHooked) {
this.startHook();
}
return id;
}
// todo 注销 cancellation: function (registerId){};
};
_global['eHook'] = eHook;
_global['aHook'] = new AHook();
}.bind(window)({
/**
* 对象是否为数组
* @param arr
*/
isArray: function (arr) {
return Array.isArray(arr) || Object.prototype.toString.call(arr) == "[object Array]";
},
/**
* 判断是否为方法
* @param func
* @return {boolean}
*/
isFunction: function (func) {
if (!func) {
return false;
}
return typeof func === 'function';
},
/**
* 判断是否是一个有效的对象
* @param obj
* @return {*|boolean}
*/
isExistObject: function (obj) {
return obj && (typeof obj === 'object');
},
/**
* 遍历数组
* @param context {Object}
* @param arr {Array}
* @param cb {Function} 回调函数
*/
ergodicArrayObject: function (context, arr, cb) {
if (!context) {
context = window;
}
if (!(arr instanceof Array) || !(cb instanceof Function)) {
return;
}
for (var i = 0; i < arr.length; i++) {
var result = cb.call(context, arr[i], i);
if (result && result == -1) {
break;
}
}
},
/**
* 遍历对象属性
* @param context {object} 上下文
* @param obj {object} 遍历对象
* @param cb {function} 回调函数
* @param isReadInnerObject {boolean=} 是否遍历内部对象的属性
*/
ergodicObject: function (context, obj, cb, isReadInnerObject) {
var keys = Object.keys(obj);
this.ergodicArrayObject(this, keys, function (propertyName) {
// 若内部对象需要遍历
var _propertyName = propertyName;
if (isReadInnerObject && obj[propertyName] !== null && typeof obj[propertyName] == 'object') {
this.ergodicObject(this, obj[propertyName], function (value, key) {
return cb.call(context, value, _propertyName + '.' + key);
}, true);
} else {
return cb.call(context, obj[propertyName], propertyName);
}
});
},
/**
* 获取数组对象的一个属性发起动作
* @param context {Object}
* @param arr {Array}
* @param propertyName {String}
* @param cb {Function}
* @param checkProperty {boolean} 是否排除不拥有该属性的对象[default:true]
*/
getPropertyDo: function (context, arr, propertyName, cb, checkProperty) {
if (checkProperty === null) {
checkProperty = true;
}
this.ergodicArrayObject(context, arr, function (ele) {
if (!checkProperty || ele.hasOwnProperty(propertyName)) {
cb.call(context, ele[propertyName], ele);
}
});
},
/**
* 通过数组中每个对象的指定属性生成一个新数组
* @param arr {Array}
* @param propertyName {String}
*/
parseArrayByProperty: function (arr, propertyName) {
var result = [];
if (!this.isArray(arr)) {
return result;
}
this.getPropertyDo(this, arr, propertyName, function (value) {
result.push(value);
}, true);
return result;
},
invokeMethods: function (context, methods, args) {
if (!this.isArray(methods)) {
return;
}
var results = [];
var _this = this;
this.ergodicArrayObject(context, methods, function (method) {
if (!_this.isFunction(method)) {
return;
}
results.push(
method.apply(context, args)
);
});
return results;
},
urlUtils: {
urlMatching: function (url, matchUrl) {
var pattern = eval('/' + matchUrl.replace(/\//g, '\\/') + '/');
return pattern.test(url);
},
getUrlWithoutParam: function (url) {
return url.split('?')[0];
},
getParamFromUrl: function (url) {
var params = [];
var parts = url.split('?');
if (parts.length < 2) {
return params;
}
var paramsStr = parts[1].split('&');
for (var i = 0; i < paramsStr.length; i++) {
var ps = paramsStr[i].split('=');
if (ps.length < 2) {
continue;
}
params.push({
key: ps[0],
value: ps[1]
});
}
return params;
},
margeUrlAndParams: function (url, params) {
if (url.indexOf('?') != -1 || !(params instanceof Array)) {
return url;
}
var paramsStr = [];
for (var i = 0; i < params.length; i++) {
if (params[i].key !== null && params[i].value !== null) {
paramsStr.push(params[i].key + '=' + params[i].value);
}
}
return url + '?' + paramsStr.join('&');
}
}
});