Hooks

A JavaScript hook/reply ultility

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/18715/661566/Hooks.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name			Hooks
// @namespace		[email protected]
// @description		A JavaScript hook/reply ultility
// @author			xymopen
// @version			1.1.5
// @grant			none
// @license			BSD 2-Clause
// ==/UserScript==

// @updateURL		https://raw.githubusercontent.com/xymopen/JS_Misc/master/Hooks.js

// I was finishing unit test and found there are some bugs about the logic

/**
 * Hooks functions or properties, getters/setters
 * and methods of an object you are intrested in
 */

var Hooks = ( function () {
	"use strict";

	var Functions = {
		/**
		 * wrapper a function
		 * @param {Function} target
		 * @param {(Function, Object?, Arguments) => void} onApply
		 */
		apply: function apply( target, onApply ) {
			if ( "function" === typeof target && "function" === typeof onApply ) {
				var fn = function () {
					return onApply.call( this, target, this, arguments );
				};

				fn.prototype = target.prototype;

				return fn;
			} else {
				throw new TypeError();
			}
		}
	};

	/**
	 * Hooks functions or properties, getters/setters
	 * and methods of an object you are intrested in
	 */
	var Hooks = {
		/**
		 * hook a property of an object
		 * @param {Object} target				- an object having or to have the property to be hooked
		 * @param {String} propertyName			- the name of the property to be hooked
		 * @param {Function} onGet					- the hook call when about to get the property
		 * @param {Function} onSet					- the hook call when about to set the property
		 */
		property: function property( target, propertyName, onGet, onSet ) {
			var descriptor, oldValue;

			if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
				descriptor = Object.getOwnPropertyDescriptor( target, propertyName );

				if ( Object.prototype.hasOwnProperty.call( descriptor, "value" ) ) {
					oldValue = descriptor.value;

					delete descriptor.value;
					delete descriptor.writable;
				} else if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
					oldValue = descriptor.get.call( target );
				} else {
					oldValue = undefined;
				}
			} else {
				descriptor = {
					configurable: true,
					enumerable: true,
				};

				oldValue = undefined;
			}

			descriptor.get = function get() {
				return onGet.call( this, target, propertyName, oldValue );
			};

			descriptor.set = function set( newValue ) {
				oldValue = onSet.call( this, target, propertyName, oldValue, newValue );
			};

			Object.defineProperty( target, propertyName, descriptor );
		},
		/**
		 * the hook call when about to get the property
		 * @callback onGet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- the name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 */

		/**
		 * the hook call when about to set the property
		 * @callback onSet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- the name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 * @param {any} newValue				- the value about to be set to the property
		 */

		/**
		 * alias of #property but fill the #onSet automatically
		 * @function get
		 * @param {Object} target				- an object having or to have the property to be hooked
		 * @param {String} propertyName			- he name of the property to be hooked
		 * @param {onGet} onGet					- the hook call when about to get the property
		 */
		get: function get( target, propertyName, onGet ) {
			return Hooks.property( target, propertyName, onGet, function ( target, propertyName, oldValue, newValue ) {
				return Hooks.Reply.set( arguments );
			} );
		},

		/**
		 * alias of #property but fill the #onGet automatically
		 * @function set
		 * @param {Object} target				- an object having or to have the property to be hooked
		 * @param {String} propertyName			- the name of the property to be hooked
		 * @param {onSet} onSet					- the hook call when about to set the property
		 */
		set: function set( target, propertyName, onSet ) {
			return Hooks.property( target, propertyName, function ( target, propertyName, oldValue ) {
				return Hooks.Reply.get( arguments );
			}, onSet );
		},

		/**
		 * hook a getter property of an object
		 * @function getter
		 * @param {object} target				- an object having the getter property to be hooked
		 * @param {string} propertyName			- the name of the getter property to be hooked
		 * @param {onGetter} onGetter				- the hook replace the getter
		 */
		getter: function getter( target, propertyName, onGetter ) {
			var descriptor;

			if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
				descriptor = Object.getOwnPropertyDescriptor( target, propertyName );

				if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
					descriptor.get = Functions.apply( descriptor.get, function ( getter, thisArg, args ) {
						return onGetter.call( this, target, propertyName, getter, thisArg, args );
					} );

					Object.defineProperty( target, propertyName, descriptor );
				}
			}
		},
		/**
		 * the hook replace the getter
		 * @callback onSetter
		 * @param {object} target				- the object having the getter property hooked
		 * @param {string} propertyName			- he name of the getter property hooked
		 * @param {function} getter				- the getter replaced
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} args				- the arguments pass to the getter, should be #undefined
		 */

		/**
		 * hook a setter property of an object
		 * @function setter
		 * @param {object} target				- an object having the setter property to be hooked
		 * @param {string} propertyName			- the name of the setter property to be hooked
		 * @param {onSetter} onSetter				- the hook replace the setter
		 */
		setter: function setter( target, propertyName, onSetter ) {
			var descriptor;

			if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
				descriptor = Object.getOwnPropertyDescriptor( target, propertyName );

				if ( Object.prototype.hasOwnProperty.call( descriptor, "set" ) ) {
					descriptor.set = Functions.apply( descriptor.set, function ( setter, thisArg, args ) {
						onSetter.call( this, target, propertyName, setter, thisArg, args );
					} );

					Object.defineProperty( target, propertyName, descriptor );
				}
			}
		},
		/**
		 * the hook replace the setter
		 * @callback onSetter
		 * @param {object} target				- the object having the setter property hooked
		 * @param {string} propertyName			- he name of the setter property hooked
		 * @param {function} setter				- the setter replaced
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} args				- the arguments pass to the setter, should be a right value
		 */

		/**
		 * hook a method of an object
		 * @function method
		 * @param {object} target				- an object having or to have the method to be hooked
		 * @param {string} methodName			- the name of the method to be hooked
		 * @param {onApply} onApply				- the hook replace the method
		 */
		method: function method( target, methodName, onApply ) {
			var method = target[ methodName ];

			function hook( method ) {
				return Functions.apply( method, function ( method, thisArg, args ) {
					return onApply.call( this, target, methodName, method, thisArg, args );
				} );
			};

			if ( "function" === typeof method ) {
				target[ methodName ] = hook( method );
			} else if ( !Object.prototype.hasOwnProperty.call( target, methodName ) ) {
				Object.defineProperty( target, methodName, {
					configurable: true,
					enumerable: true,
					set: function ( value ) {
						Object.defineProperty( target, methodName, {
							configurable: true,
							enumerable: true,
							value: typeof value === "function" ? hook( value ) : value,
							writable: true
						} );
					},
					get: function () {
						return undefined;
					}
				} );
			}
		},
		/**
		 * the hook replace the method
		 * @callback onApply
		 * @param {object} target				- the object having the method hooked
		 * @param {string} methodName			- the name of the method hooked
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} args				- the arguments pass to the method
		 */
	};

	Hooks.Reply = {
		get: function ( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				oldValue = param[ 2 ];

			return oldValue;
		},

		set: function ( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				oldValue = param[ 2 ],
				newValue = param[ 3 ];

			return newValue;
		},

		getter: function ( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				getter = param[ 2 ],
				thisArg = param[ 3 ],
				args = param[ 4 ];

			return getter.apply( thisArg, args );
		},

		setter: function ( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				setter = param[ 2 ],
				thisArg = param[ 3 ],
				args = param[ 4 ];

			setter.apply( thisArg, args );
		},

		method: function method( param ) {
			var target = param[ 0 ],
				methodName = param[ 1 ],
				method = param[ 2 ],
				thisArg = param[ 3 ],
				args = param[ 4 ];

			return method.apply( thisArg, args );
		},
	};

	return Hooks;
} )();