- // ==UserScript==
- // @name Hooks
- // @namespace xuyiming.open@outlook.com
- // @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;
- } )();