Ticket #21170: 21170.10.diff
File 21170.10.diff, 19.1 KB (added by , 8 years ago) |
---|
-
new file src/wp-includes/js/wp-hooks.js
diff --git src/wp-includes/js/wp-hooks.js src/wp-includes/js/wp-hooks.js new file mode 100644 index 0000000000..78e4348255
- + 1 ( function( wp ) { 2 'use strict'; 3 4 /** 5 * Contains the registered hooks, keyed by hook type. Each hook type is an 6 * array of objects with priority and callback of each registered hook. 7 */ 8 var HOOKS = {}; 9 10 /** 11 * Returns a function which, when invoked, will add a hook. 12 * 13 * @param {string} type Type for which hooks are to be added 14 * @return {Function} Hook added 15 */ 16 function createAddHookByType( type ) { 17 /** 18 * Adds the hook to the appropriate hooks container 19 * 20 * @param {string} hook Name of hook to add 21 * @param {Function} callback Function to call when the hook is run 22 * @param {?number} priority Priority of this hook (default=10) 23 */ 24 return function( hook, callback, priority ) { 25 var hookObject, hooks; 26 if ( typeof hook !== 'string' || typeof callback !== 'function' ) { 27 return; 28 } 29 30 // Assign default priority 31 if ( 'undefined' === typeof priority ) { 32 priority = 10; 33 } else { 34 priority = parseInt( priority, 10 ); 35 } 36 37 // Validate numeric priority 38 if ( isNaN( priority ) ) { 39 return; 40 } 41 42 // Check if adding first of type 43 if ( ! HOOKS[ type ] ) { 44 HOOKS[ type ] = {}; 45 } 46 47 hookObject = { 48 callback: callback, 49 priority: priority 50 }; 51 52 if ( HOOKS[ type ].hasOwnProperty( hook ) ) { 53 // Append and re-sort amongst existing 54 hooks = HOOKS[ type ][ hook ]; 55 hooks.push( hookObject ); 56 hooks = sortHooks( hooks ); 57 } else { 58 // First of its type needs no sort 59 hooks = [ hookObject ]; 60 } 61 62 HOOKS[ type ][ hook ] = hooks; 63 }; 64 } 65 66 /** 67 * Returns a function which, when invoked, will remove a specified hook. 68 * 69 * @param {string} type Type for which hooks are to be removed. 70 * @param {bool} removeAll Whether to always remove all hooked callbacks. 71 * 72 * @return {Function} Hook remover. 73 */ 74 function createRemoveHookByType( type, removeAll ) { 75 /** 76 * Removes the specified hook by resetting its value. 77 * 78 * @param {string} hook Name of hook to remove 79 * @param {Function} callback The specific callback to be removed. If 80 * omitted, clears all callbacks. 81 */ 82 return function( hook, callback ) { 83 var handlers, i; 84 85 // Baily early if no hooks exist by this name 86 if ( ! HOOKS[ type ] || ! HOOKS[ type ].hasOwnProperty( hook ) ) { 87 return; 88 } 89 90 if ( callback && ! removeAll ) { 91 // Try to find specified callback to remove 92 handlers = HOOKS[ type ][ hook ]; 93 for ( i = handlers.length - 1; i >= 0; i-- ) { 94 if ( handlers[ i ].callback === callback ) { 95 handlers.splice( i, 1 ); 96 } 97 } 98 } else { 99 // Reset hooks to empty 100 delete HOOKS[ type ][ hook ]; 101 } 102 }; 103 } 104 105 /** 106 * Returns a function which, when invoked, will execute all registered 107 * hooks of the specified type by calling upon runner with its hook name 108 * and arguments. 109 * 110 * @param {string} type Type for which hooks are to be run, one of 'action' or 'filter'. 111 * @param {Function} runner Function to invoke for each hook callback 112 * @return {Function} Hook runner 113 */ 114 function createRunHookByType( type, runner ) { 115 /** 116 * Runs the specified hook. 117 * 118 * @param {string} hook The hook to run 119 * @param {...*} args Arguments to pass to the action/filter 120 * @return {*} Return value of runner, if applicable 121 * @private 122 */ 123 return function( /* hook, ...args */ ) { 124 var args, hook; 125 126 args = Array.prototype.slice.call( arguments ); 127 hook = args.shift(); 128 129 if ( typeof hook === 'string' ) { 130 return runner( hook, args ); 131 } 132 }; 133 } 134 135 /** 136 * Performs an action if it exists. 137 * 138 * @param {string} action The action to perform. 139 * @param {...*} args Optional args to pass to the action. 140 * @private 141 */ 142 function runDoAction( action, args ) { 143 var handlers, i; 144 if ( HOOKS.actions ) { 145 handlers = HOOKS.actions[ action ]; 146 } 147 148 if ( ! handlers ) { 149 return; 150 } 151 152 HOOKS.actions.current = action; 153 154 for ( i = 0; i < handlers.length; i++ ) { 155 handlers[ i ].callback.apply( null, args ); 156 HOOKS.actions[ action ].runs = HOOKS.actions[ action ].runs ? HOOKS.actions[ action ].runs + 1 : 1; 157 } 158 159 } 160 161 /** 162 * Performs a filter if it exists. 163 * 164 * @param {string} filter The filter to apply. 165 * @param {...*} args Optional args to pass to the filter. 166 * @return {*} The filtered value 167 * @private 168 */ 169 function runApplyFilters( filter, args ) { 170 var handlers, i; 171 if ( HOOKS.filters ) { 172 handlers = HOOKS.filters[ filter ]; 173 } 174 175 if ( ! handlers ) { 176 return args[ 0 ]; 177 } 178 179 HOOKS.filters.current = filter; 180 HOOKS.filters[ filter ].runs = HOOKS.filters[ filter ].runs ? HOOKS.filters[ filter ].runs + 1 : 1; 181 182 for ( i = 0; i < handlers.length; i++ ) { 183 args[ 0 ] = handlers[ i ].callback.apply( null, args ); 184 } 185 delete( HOOKS.filters.current ); 186 187 return args[ 0 ]; 188 } 189 190 /** 191 * Use an insert sort for keeping our hooks organized based on priority. 192 * 193 * @see http://jsperf.com/javascript-sort 194 * 195 * @param {Array} hooks Array of the hooks to sort 196 * @return {Array} The sorted array 197 * @private 198 */ 199 function sortHooks( hooks ) { 200 var i, tmpHook, j, prevHook; 201 for ( i = 1; i < hooks.length; i++ ) { 202 tmpHook = hooks[ i ]; 203 j = i; 204 while ( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) { 205 hooks[ j ] = hooks[ j - 1 ]; 206 --j; 207 } 208 hooks[ j ] = tmpHook; 209 } 210 211 return hooks; 212 } 213 214 215 /** 216 * See what action is currently being executed. 217 * 218 * @param {string} type Type of hooks to check, one of 'action' or 'filter'. 219 * @param {string} action The name of the action to check for. 220 * 221 * @return {[type]} [description] 222 */ 223 function createCurrentHookByType( type ) { 224 return function( action ) { 225 226 // If the action was not passed, check for any current hook. 227 if ( 'undefined' === typeof action ) { 228 return false; 229 } 230 231 // Return the current hook. 232 return HOOKS[ type ] && HOOKS[ type ].current ? 233 HOOKS[ type ].current : 234 false; 235 }; 236 } 237 238 239 240 /** 241 * Checks to see if an action is currently being executed. 242 * 243 * @param {string} type Type of hooks to check, one of 'action' or 'filter'. 244 * @param {string} action The name of the action to check for, if omitted will check for any action being performed. 245 * 246 * @return {[type]} [description] 247 */ 248 function createDoingHookByType( type ) { 249 return function( action ) { 250 251 // If the action was not passed, check for any current hook. 252 if ( 'undefined' === typeof action ) { 253 return 'undefined' !== typeof HOOKS[ type ].current; 254 } 255 256 // Return the current hook. 257 return HOOKS[ type ] && HOOKS[ type ].current ? 258 action === HOOKS[ type ].current : 259 false; 260 }; 261 } 262 263 /** 264 * Retrieve the number of times an action is fired. 265 * 266 * @param {string} type Type for which hooks to check, one of 'action' or 'filter'. 267 * @param {string} action The action to check. 268 * 269 * @return {[type]} [description] 270 */ 271 function createDidHookByType( type ) { 272 return function( action ) { 273 return HOOKS[ type ] && HOOKS[ type ][ action ] && HOOKS[ type ][ action ].runs ? 274 HOOKS[ type ][ action ].runs : 275 0; 276 }; 277 } 278 279 /** 280 * Check to see if an action is registered for a hook. 281 * 282 * @param {string} type Type for which hooks to check, one of 'action' or 'filter'. 283 * @param {string} action The action to check. 284 * 285 * @return {bool} Whether an action has been registered for a hook. 286 */ 287 function createHasHookByType( type ) { 288 return function( action ) { 289 return HOOKS[ type ] && HOOKS[ type ][ action ] ? 290 !! HOOKS[ type ][ action ] : 291 false; 292 }; 293 } 294 295 /** 296 * Remove all the actions registered to a hook. 297 */ 298 function createRemoveAllByType( type ) { 299 return createRemoveHookByType( type, true ); 300 } 301 302 wp.hooks = { 303 304 // Remove functions, 305 removeFilter: createRemoveHookByType( 'filters' ), 306 removeAction: createRemoveHookByType( 'actions' ), 307 308 309 // Do action/apply filter functions. 310 doAction: createRunHookByType( 'actions', runDoAction ), 311 applyFilters: createRunHookByType( 'filters', runApplyFilters ), 312 313 // Add functions. 314 addAction: createAddHookByType( 'actions' ), 315 addFilter: createAddHookByType( 'filters' ), 316 317 // Doing functions. 318 doingAction: createDoingHookByType( 'actions' ), /* True for actions until next action fired. */ 319 doingFilter: createDoingHookByType( 'filters' ), /* True for filters while filter is being applied. */ 320 321 // Did functions. 322 didAction: createDidHookByType( 'actions' ), 323 didFilter: createDidHookByType( 'filters' ), 324 325 // Has functions. 326 hasAction: createHasHookByType( 'actions' ), 327 hasFilter: createHasHookByType( 'filters' ), 328 329 // Remove all functions. 330 removeAllActions: createRemoveAllByType( 'actions' ), 331 removeAllFilters: createRemoveAllByType( 'filters' ), 332 333 // Current filter. 334 currentFilter: createCurrentHookByType( 'filters' ) 335 }; 336 } )( window.wp = window.wp || {} ); -
src/wp-includes/plugin.php
diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php index 86f1c3b319..86f9db8964 100644
function doing_filter( $filter = null ) { 363 363 } 364 364 365 365 /** 366 * Retrieve the name of anaction currently being processed.366 * Retrieve whether action currently being processed. 367 367 * 368 368 * @since 3.9.0 369 369 * -
src/wp-includes/script-loader.php
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php index 0058c5e956..790272188e 100644
function wp_default_scripts( &$scripts ) { 85 85 86 86 $scripts->add( 'wp-a11y', "/wp-includes/js/wp-a11y$suffix.js", array( 'jquery' ), false, 1 ); 87 87 88 $scripts->add( 'wp-hooks', "/wp-includes/js/wp-hooks$suffix.js", array(), false, 1 ); 89 88 90 $scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 ); 89 91 90 92 $scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 ); -
tests/qunit/index.html
diff --git tests/qunit/index.html tests/qunit/index.html index c41fffe63a..183c492d85 100644
76 76 <script src="../../src/wp-includes/js/customize-base.js"></script> 77 77 <script src="../../src/wp-includes/js/customize-models.js"></script> 78 78 <script src="../../src/wp-includes/js/shortcode.js"></script> 79 <script src="../../src/wp-includes/js/wp-hooks.js"></script> 79 80 <script src="../../src/wp-admin/js/customize-controls.js"></script> 80 81 <script src="../../src/wp-includes/js/wp-api.js"></script> 81 82 … … 122 123 <script src="wp-admin/js/customize-base.js"></script> 123 124 <script src="wp-admin/js/customize-header.js"></script> 124 125 <script src="wp-includes/js/shortcode.js"></script> 126 <script src="wp-includes/js/wp-hooks.js"></script> 125 127 <script src="wp-includes/js/wp-api.js"></script> 126 128 <script src="wp-admin/js/customize-controls.js"></script> 127 129 <script src="wp-admin/js/customize-controls-utils.js"></script> -
new file tests/qunit/wp-includes/js/wp-hooks.js
diff --git tests/qunit/wp-includes/js/wp-hooks.js tests/qunit/wp-includes/js/wp-hooks.js new file mode 100644 index 0000000000..57b0d09d52
- + 1 /* global wp */ 2 ( function( QUnit ) { 3 QUnit.module( 'wp-hooks' ); 4 5 function filter_a( str ) { 6 return str + 'a'; 7 } 8 function filter_b( str ) { 9 return str + 'b'; 10 } 11 function filter_c( str ) { 12 return str + 'c'; 13 } 14 function action_a() { 15 window.actionValue += 'a'; 16 } 17 function action_b() { 18 window.actionValue += 'b'; 19 } 20 function action_c() { 21 window.actionValue += 'c'; 22 } 23 function filter_check() { 24 ok( wp.hooks.doingFilter( 'runtest.filter' ), 'The runtest.filter is running.' ); 25 } 26 window.actionValue = ''; 27 28 QUnit.test( 'add and remove a filter', function() { 29 expect( 1 ); 30 wp.hooks.addFilter( 'test.filter', filter_a ); 31 wp.hooks.removeFilter( 'test.filter' ); 32 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' ); 33 } ); 34 35 QUnit.test( 'add a filter and run it', function() { 36 expect( 1 ); 37 wp.hooks.addFilter( 'test.filter', filter_a ); 38 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testa' ); 39 wp.hooks.removeFilter( 'test.filter' ); 40 } ); 41 42 QUnit.test( 'add 2 filters in a row and run them', function() { 43 expect( 1 ); 44 wp.hooks.addFilter( 'test.filter', filter_a ); 45 wp.hooks.addFilter( 'test.filter', filter_b ); 46 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testab' ); 47 wp.hooks.removeFilter( 'test.filter' ); 48 } ); 49 50 QUnit.test( 'add 3 filters with different priorities and run them', function() { 51 expect( 1 ); 52 wp.hooks.addFilter( 'test.filter', filter_a ); 53 wp.hooks.addFilter( 'test.filter', filter_b, 2 ); 54 wp.hooks.addFilter( 'test.filter', filter_c, 8 ); 55 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' ); 56 wp.hooks.removeFilter( 'test.filter' ); 57 } ); 58 59 QUnit.test( 'add and remove an action', function() { 60 expect( 1 ); 61 window.actionValue = ''; 62 wp.hooks.addAction( 'test.action', action_a ); 63 wp.hooks.removeAction( 'test.action' ); 64 wp.hooks.doAction( 'test.action' ); 65 equal( window.actionValue, '' ); 66 } ); 67 68 QUnit.test( 'add an action and run it', function() { 69 expect( 1 ); 70 window.actionValue = ''; 71 wp.hooks.addAction( 'test.action', action_a ); 72 wp.hooks.doAction( 'test.action' ); 73 equal( window.actionValue, 'a' ); 74 wp.hooks.removeAction( 'test.action' ); 75 } ); 76 77 QUnit.test( 'add 2 actions in a row and then run them', function() { 78 expect( 1 ); 79 window.actionValue = ''; 80 wp.hooks.addAction( 'test.action', action_a ); 81 wp.hooks.addAction( 'test.action', action_b ); 82 wp.hooks.doAction( 'test.action' ); 83 equal( window.actionValue, 'ab' ); 84 wp.hooks.removeAction( 'test.action' ); 85 } ); 86 87 QUnit.test( 'add 3 actions with different priorities and run them', function() { 88 expect( 1 ); 89 window.actionValue = ''; 90 wp.hooks.addAction( 'test.action', action_a ); 91 wp.hooks.addAction( 'test.action', action_b, 2 ); 92 wp.hooks.addAction( 'test.action', action_c, 8 ); 93 wp.hooks.doAction( 'test.action' ); 94 equal( window.actionValue, 'bca' ); 95 wp.hooks.removeAction( 'test.action' ); 96 } ); 97 98 QUnit.test( 'pass in two arguments to an action', function() { 99 var arg1 = 10, 100 arg2 = 20; 101 102 expect( 4 ); 103 104 wp.hooks.addAction( 'test.action', function( a, b ) { 105 equal( arg1, a ); 106 equal( arg2, b ); 107 } ); 108 wp.hooks.doAction( 'test.action', arg1, arg2 ); 109 wp.hooks.removeAction( 'test.action' ); 110 111 equal( arg1, 10 ); 112 equal( arg2, 20 ); 113 } ); 114 115 QUnit.test( 'fire action multiple times', function() { 116 var func; 117 expect( 2 ); 118 119 func = function() { 120 ok( true ); 121 }; 122 123 wp.hooks.addAction( 'test.action', func ); 124 wp.hooks.doAction( 'test.action' ); 125 wp.hooks.doAction( 'test.action' ); 126 wp.hooks.removeAction( 'test.action' ); 127 } ); 128 129 QUnit.test( 'remove specific action callback', function() { 130 window.actionValue = ''; 131 wp.hooks.addAction( 'test.action', action_a ); 132 wp.hooks.addAction( 'test.action', action_b, 2 ); 133 wp.hooks.addAction( 'test.action', action_c, 8 ); 134 135 wp.hooks.removeAction( 'test.action', action_b ); 136 wp.hooks.doAction( 'test.action' ); 137 equal( window.actionValue, 'ca' ); 138 wp.hooks.removeAction( 'test.action' ); 139 } ); 140 141 QUnit.test( 'remove all action callbacks', function() { 142 window.actionValue = ''; 143 wp.hooks.addAction( 'test.action', action_a ); 144 wp.hooks.addAction( 'test.action', action_b, 2 ); 145 wp.hooks.addAction( 'test.action', action_c, 8 ); 146 147 wp.hooks.removeAllActions( 'test.action' ); 148 wp.hooks.doAction( 'test.action' ); 149 equal( window.actionValue, '' ); 150 } ); 151 152 QUnit.test( 'remove specific filter callback', function() { 153 wp.hooks.addFilter( 'test.filter', filter_a ); 154 wp.hooks.addFilter( 'test.filter', filter_b, 2 ); 155 wp.hooks.addFilter( 'test.filter', filter_c, 8 ); 156 157 wp.hooks.removeFilter( 'test.filter', filter_b ); 158 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testca' ); 159 wp.hooks.removeFilter( 'test.filter' ); 160 } ); 161 162 QUnit.test( 'remove all filter callbacks', function() { 163 wp.hooks.addFilter( 'test.filter', filter_a ); 164 wp.hooks.addFilter( 'test.filter', filter_b, 2 ); 165 wp.hooks.addFilter( 'test.filter', filter_c, 8 ); 166 167 wp.hooks.removeAllFilters( 'test.filter' ); 168 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' ); 169 } ); 170 171 // Test doingAction, didAction, hasAction. 172 QUnit.test( 'Test doingAction, didAction and hasAction.', function() { 173 174 // Reset state for testing. 175 wp.hooks.removeAction( 'test.action' ); 176 wp.hooks.addAction( 'another.action', function(){} ); 177 wp.hooks.doAction( 'another.action' ); 178 179 // Verify no action is running yet. 180 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' ); 181 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 182 ok( ! wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' ); 183 184 wp.hooks.addAction( 'test.action', action_a ); 185 186 // Verify action added, not running yet. 187 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' ); 188 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 189 ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' ); 190 191 wp.hooks.doAction( 'test.action' ); 192 193 // Verify action added and running. 194 ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 195 equal( wp.hooks.didAction( 'test.action' ), 1, 'The test.action has run once.' ); 196 ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' ); 197 198 wp.hooks.doAction( 'test.action' ); 199 equal( wp.hooks.didAction( 'test.action' ), 2, 'The test.action has run twice.' ); 200 201 wp.hooks.removeAction( 'test.action' ); 202 203 // Verify state is reset appropriately. 204 ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 205 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 206 ok( ! wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' ); 207 208 wp.hooks.doAction( 'another.action' ); 209 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 210 211 // Verify hasAction returns false when no matching action. 212 ok( ! wp.hooks.hasAction( 'notatest.action' ), 'The notatest.action is registered.' ); 213 214 } ); 215 216 QUnit.test( 'Verify doingFilter, didFilter and hasFilter.', function() { 217 expect( 4 ); 218 wp.hooks.addFilter( 'runtest.filter', filter_check ); 219 220 // Verify filter added and running. 221 var test = wp.hooks.applyFilters( 'runtest.filter', true ); 222 equal( wp.hooks.didFilter( 'runtest.filter' ), 1, 'The runtest.filter has run once.' ); 223 ok( wp.hooks.hasFilter( 'runtest.filter' ), 'The runtest.filter is registered.' ); 224 ok( ! wp.hooks.hasFilter( 'notatest.filter' ), 'The notatest.filter is not registered.' ); 225 226 wp.hooks.removeFilter( 'runtest.filter' ); 227 } ); 228 229 } )( window.QUnit );