Hooks

A JavaScript hook/reply ultility

Versión del día 6/6/2018. Echa un vistazo a la versión más reciente.

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greatest.deepsurf.us/scripts/18715/603567/Hooks.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name			Hooks
// @namespace		[email protected]
// @description		A JavaScript hook/reply ultility
// @author			xymopen
// @version			1.1.3
// @grant			none
// @license			BSD 2-Clause
// @homepageURL		https://github.com/xymopen/JS_Misc/blob/master/Hooks.js
// @updateURL		https://raw.githubusercontent.com/xymopen/JS_Misc/master/Hooks.js
// ==/UserScript==

// TODO: JsDoc
// TODO: Unit test

var Hooks = ( function() {
	"use strict";
	
	/**
	 * Hooks functions or properties, getters/setters
	 * and methods of an object you are intrested in
	 */
	var Hooks = {
		/**
		 * hook a function
		 * @function apply
		 * @param {function} target				- a function to be hook
		 * @param {onApply} onApply				- the hook
		 */
		"apply": function apply( target, onApply ) {
			if ( "function" === typeof target && "function" === typeof onApply ) {
				return function() {
					return onApply.call( this, target, this, arguments );
				};
			} else {
				throw new TypeError();
			}
		},
		/**
		 * @callback onApply the hook
		 * @param {function} target				- the function hooked
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} argv				- the arguments pass to the target
		 */

		/**
		 * hook a property of an object
		 * @function property
		 * @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 {onGet} onGet					- the hook call when about to get the property
		 * @param {onSet} 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 );
				return oldValue;
			};

			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.get( arguments );
			} );
		},
		/**
		 * 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
		 */

		/**
		 * 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.set( arguments );
			}, onSet );
		},
		/**
		 * the hook call when about to set the property
		 * @callback onSet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- he 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
		 */

		/**
		 * 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 ], descriptor;
			
			function hookMethod( method, onApply ) {
				return Hooks.apply( method, function( method, thisArg, argv ) {
					return onApply.call( this, target, methodName, method, thisArg, argv );
				} );
			};
			
			if ( "function" === typeof method ) {
				target[ methodName ] = hookMethod( method, onApply );
			} else {
				if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
					throw new Error( "ERR_PROPERTY_EXISTS_NOT_METHOD" );
				} else {
					Hooks.set( target, methodName, function onSet( target, propertyName, oldValue, newValue ) {
						Object.defineProperty( target, methodName, {
							"configurable": true,
							"enumerable": true,
							"value": "function" === typeof newValue ? hookMethod( newValue, onApply ) : newValue,
							"writable": true,
						} );
					} );
				}
			}
		},
		/**
		 * 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} argv				- the arguments pass to the method
		 */
		
		/**
		 * 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 ) {
			return trap( target, propertyName, onGetter, "get", "ERR_NOT_A_GETTER" );
		},
		/**
		 * 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} argv				- 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 ) {
			return trap( target, propertyName, onSetter, "set", "ERR_NOT_A_SETTER" );
		},
		/**
		 * 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} argv				- the arguments pass to the setter, should be a right value
		 */
	};
	
	var Reply = {
		"apply": function apply( param ) {
			var target = param[ 0 ],
				thisArg = param[ 1 ],
				argv = param[ 2 ];
			
			return target.apply( thisArg, argv );
		},
		
		"get": function( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				oldValue = param[ 2 ],
				newValue = param[ 3 ];
			
			return param[ param.length - 1 ];
		},
		
		"getter": function( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				fn = param[ 2 ],
				thisArg = param[ 3 ],
				argv = param[ 4 ];
			
			return fn.apply( thisArg, argv );
		},
		
		"method": function method( param ) {
			var target = param[ 0 ],
				methodName = param[ 1 ],
				method = param[ 2 ],
				thisArg = param[ 3 ],
				argv = param[ 4 ];
			
			return method.apply( thisArg, argv );
		},
	};
	
	Reply.set = Reply.get;
	Reply.setter = Reply.getter;
	
	Hooks.Reply = Reply;

	return Hooks;
} )();