Index: src/wp-admin/js/word-count.js
===================================================================
--- src/wp-admin/js/word-count.js	(revision 27473)
+++ src/wp-admin/js/word-count.js	(working copy)
@@ -1,4 +1,4 @@
-/* global wordCountL10n */
+/* global wordCountL10n, wp */
 var wpWordCount;
 (function($,undefined) {
 	wpWordCount = {
@@ -38,7 +38,7 @@
 		}
 	};
 
-	$(document).bind( 'wpcountwords', function(e, txt) {
+	wp.hooks.addAction( 'wpcountwords', function(txt) {
 		wpWordCount.wc(txt);
 	});
 }(jQuery));
Index: src/wp-includes/js/wp-hooks.js
===================================================================
--- src/wp-includes/js/wp-hooks.js	(revision 0)
+++ src/wp-includes/js/wp-hooks.js	(revision 0)
@@ -0,0 +1,280 @@
+window.wp = window.wp || {};
+
+(function(namespace) {
+	"use strict";
+
+	/**
+	 * Handles managing all events for whatever you plug it into. Priorities for
+	 * hooks are based on lowest to highest in that, lowest priority hooks are
+	 * fired first.
+	 */
+	var EventManager = function() {
+		/**
+		 * Maintain a reference to the object scope so our public methods never
+		 * get confusing.
+		 */
+		var MethodsAvailable = {
+			removeFilter : removeFilter,
+			applyFilters : applyFilters,
+			addFilter : addFilter,
+			removeAction : removeAction,
+			doAction : doAction,
+			addAction : addAction
+		};
+
+		/**
+		 * Contains the hooks that get registered with this EventManager. The
+		 * array for storage utilizes a "flat" object literal such that looking
+		 * up the hook utilizes the native object literal hash.
+		 */
+		var STORAGE = {
+			actions : {},
+			filters : {}
+		};
+
+		/**
+		 * Adds an action to the event manager.
+		 *
+		 * @param action
+		 *            Must contain namespace.identifier
+		 * @param callback
+		 *            Must be a valid callback function before this action is
+		 *            added
+		 * @param [priority=10]
+		 *            Used to control when the function is executed in relation
+		 *            to other callbacks bound to the same hook
+		 * @param [context]
+		 *            Supply a value to be used for this
+		 */
+		function addAction(action, callback, priority, context) {
+			if (typeof action === 'string' && typeof callback === 'function') {
+				priority = parseInt((priority || 10), 10);
+				_addHook('actions', action, callback, priority, context);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Performs an action if it exists. You can pass as many arguments as
+		 * you want to this function; the only rule is that the first argument
+		 * must always be the action.
+		 */
+		function doAction( /* action, arg1, arg2, ... */) {
+			var args = Array.prototype.slice.call(arguments);
+			var action = args.shift();
+
+			if (typeof action === 'string') {
+				_runHook('actions', action, args);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Removes the specified action if it contains a namespace.identifier &
+		 * exists.
+		 *
+		 * @param action
+		 *            The action to remove
+		 * @param [callback]
+		 *            Callback function to remove
+		 */
+		function removeAction(action, callback) {
+			if (typeof action === 'string') {
+				_removeHook('actions', action, callback);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Adds a filter to the event manager.
+		 *
+		 * @param filter
+		 *            Must contain namespace.identifier
+		 * @param callback
+		 *            Must be a valid callback function before this action is
+		 *            added
+		 * @param [priority=10]
+		 *            Used to control when the function is executed in relation
+		 *            to other callbacks bound to the same hook
+		 * @param [context]
+		 *            Supply a value to be used for this
+		 */
+		function addFilter(filter, callback, priority, context) {
+			if (typeof filter === 'string' && typeof callback === 'function') {
+				priority = parseInt((priority || 10), 10);
+				_addHook('filters', filter, callback, priority);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Performs a filter if it exists. You should only ever pass 1 argument
+		 * to be filtered. The only rule is that the first argument must always
+		 * be the filter.
+		 */
+		function applyFilters( /* filter, filtered arg, arg2, ... */) {
+			var args = Array.prototype.slice.call(arguments);
+			var filter = args.shift();
+
+			if (typeof filter === 'string') {
+				return _runHook('filters', filter, args);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Removes the specified filter if it contains a namespace.identifier &
+		 * exists.
+		 *
+		 * @param filter
+		 *            The action to remove
+		 * @param [callback]
+		 *            Callback function to remove
+		 */
+		function removeFilter(filter, callback) {
+			if (typeof filter === 'string') {
+				_removeHook('filters', filter, callback);
+			}
+
+			return MethodsAvailable;
+		}
+
+		/**
+		 * Removes the specified hook by resetting the value of it.
+		 *
+		 * @param type
+		 *            Type of hook, either 'actions' or 'filters'
+		 * @param hook
+		 *            The hook (namespace.identifier) to remove
+		 * @private
+		 */
+		function _removeHook(type, hook, callback) {
+			var actions, action, index;
+			if (STORAGE[type][hook]) {
+				if (typeof callback === 'undefined') {
+					STORAGE[type][hook] = [];
+				} else {
+					actions = STORAGE[type][hook];
+					for (index = 0; index < actions.length; index++) {
+						action = actions[index];
+						if (action.callback === callback) {
+							STORAGE[type][hook].splice(index, 1);
+						}
+					}
+				}
+			}
+		}
+
+		/**
+		 * Adds the hook to the appropriate storage container
+		 *
+		 * @param type
+		 *            'actions' or 'filters'
+		 * @param hook
+		 *            The hook (namespace.identifier) to add to our event
+		 *            manager
+		 * @param callback
+		 *            The function that will be called when the hook is
+		 *            executed.
+		 * @param priority
+		 *            The priority of this hook. Must be an integer.
+		 * @param [context]
+		 *            A value to be used for this
+		 * @private
+		 */
+		function _addHook(type, hook, callback, priority, context) {
+			var hookObject = {
+				callback : callback,
+				priority : priority,
+				context : context
+			};
+
+			// Utilize 'prop itself' :
+			// http://jsperf.com/hasownproperty-vs-in-vs-undefined/19
+			var hooks = STORAGE[type][hook];
+			if (hooks) {
+				hooks.push(hookObject);
+				hooks = _hookInsertSort(hooks);
+			} else {
+				hooks = [ hookObject ];
+			}
+
+			STORAGE[type][hook] = hooks;
+		}
+
+		/**
+		 * Use an insert sort for keeping our hooks organized based on priority.
+		 * This function is ridiculously faster than bubble sort, etc:
+		 * http://jsperf.com/javascript-sort
+		 *
+		 * @param hooks
+		 *            The custom array containing all of the appropriate hooks
+		 *            to perform an insert sort on.
+		 * @private
+		 */
+		function _hookInsertSort(hooks) {
+			var tmpHook, j, prevHook;
+			for ( var i = 1, len = hooks.length; i < len; i++) {
+				tmpHook = hooks[i];
+				j = i;
+				while ((prevHook = hooks[j - 1])
+						&& prevHook.priority > tmpHook.priority) {
+					hooks[j] = hooks[j - 1];
+					--j;
+				}
+				hooks[j] = tmpHook;
+			}
+
+			return hooks;
+		}
+
+		/**
+		 * Runs the specified hook. If it is an action, the value is not
+		 * modified but if it is a filter, it is.
+		 *
+		 * @param type
+		 *            'actions' or 'filters'
+		 * @param hook
+		 *            The hook ( namespace.identifier ) to be ran.
+		 * @param args
+		 *            Arguments to pass to the action/filter. If it's a filter,
+		 *            args is actually a single parameter.
+		 * @private
+		 */
+		function _runHook(type, hook, args) {
+			var hooks = STORAGE[type][hook];
+			if (typeof hooks === 'undefined') {
+				if (type === 'filters') {
+					return args[0];
+				}
+				return false;
+			}
+
+			for ( var i = 0, len = hooks.length; i < len; i++) {
+				if (type === 'actions') {
+					hooks[i].callback.apply(hooks[i].context, args);
+				} else {
+					args[0] = hooks[i].callback.apply(hooks[i].context, args);
+				}
+			}
+
+			if (type === 'actions') {
+				return true;
+			}
+
+			return args[0];
+		}
+
+		// return all of the publicly available methods
+		return MethodsAvailable;
+
+	};
+	namespace = namespace || {};
+	namespace.hooks = new EventManager();
+
+})(window.wp);
\ No newline at end of file
Index: src/wp-admin/js/post.js
===================================================================
--- src/wp-admin/js/post.js	(revision 27473)
+++ src/wp-admin/js/post.js	(working copy)
@@ -258,7 +258,7 @@
 	);
 };
 
-$(document).on( 'heartbeat-send.refresh-lock', function( e, data ) {
+wp.hooks.addAction( 'heartbeat-send', function( data ) {
 	var lock = $('#active_post_lock').val(),
 		post_id = $('#post_ID').val(),
 		send = {};
@@ -273,7 +273,7 @@
 
 	data['wp-refresh-post-lock'] = send;
 
-}).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
+}).addAction( 'heartbeat-tick', function( data ) {
 	// Post locks: update the lock string or show the dialog if somebody has taken over editing
 	var received, wrap, avatar;
 
@@ -287,11 +287,11 @@
 			if ( wrap.length && ! wrap.is(':visible') ) {
 				if ( wp.autosave ) {
 					// Save the latest changes and disable
-					$(document).one( 'heartbeat-tick', function() {
+					wp.hooks.addAction ( 'heartbeat-tick', function() {
 						wp.autosave.server.suspend();
 						wrap.removeClass('saving').addClass('saved');
 						$(window).off( 'beforeunload.edit-post' );
-					});
+					}, 1);
 
 					wrap.addClass('saving');
 					wp.autosave.server.triggerSave();
@@ -309,7 +309,8 @@
 			$('#active_post_lock').val( received.new_lock );
 		}
 	}
-}).on( 'after-autosave.update-post-slug', function() {
+});
+wp.hooks.addAction ( 'after-autosave', function() {
 	// create slug area only if not already there
 	if ( ! $('#edit-slug-box > *').length ) {
 		$.post( ajaxurl, {
@@ -338,7 +339,7 @@
 		timeout = window.setTimeout( function(){ check = true; }, 300000 );
 	}
 
-	$(document).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) {
+	wp.hooks.doAction( 'heartbeat-send', function( e, data ) {
 		var nonce, post_id;
 
 		if ( check ) {
@@ -349,7 +350,7 @@
 				};
 			}
 		}
-	}).on( 'heartbeat-tick.wp-refresh-nonces', function( e, data ) {
+	}).addAction( 'heartbeat-tick', function( data ) {
 		var nonces = data['wp-refresh-post-nonces'];
 
 		if ( nonces ) {
@@ -364,7 +365,8 @@
 			if ( nonces.heartbeatNonce )
 				window.heartbeatSettings.nonce = nonces.heartbeatNonce;
 		}
-	}).ready( function() {
+	})
+	$(document).ready( function() {
 		schedule();
 	});
 }(jQuery));
@@ -511,15 +513,15 @@
 		});
 	}
 
-	$document.on( 'autosave-disable-buttons.edit-post', function() {
+	wp.hooks.addAction ( 'autosave-disable-buttons', function() {
 		$submitButtons.addClass( 'disabled' );
-	}).on( 'autosave-enable-buttons.edit-post', function() {
+	}).addAction ( 'autosave-enable-buttons', function() {
 		if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
 			$submitButtons.removeClass( 'disabled' );
 		}
-	}).on( 'before-autosave.edit-post', function() {
+	}).addAction ( 'before-autosave', function() {
 		$( '.autosave-message' ).text( postL10n.savingText );
-	}).on( 'after-autosave.edit-post', function( event, data ) {
+	}).addAction( 'after-autosave', function( data ) {
 		$( '.autosave-message' ).text( data.message );
 	});
 
@@ -942,7 +944,7 @@
 
 	// word count
 	if ( typeof(wpWordCount) != 'undefined' ) {
-		$document.triggerHandler('wpcountwords', [ co.val() ]);
+		wp.hooks.doAction('wpcountwords',  co.val() );
 
 		co.keyup( function(e) {
 			var k = e.keyCode || e.charCode;
@@ -951,7 +953,7 @@
 				return true;
 
 			if ( 13 == k || 8 == last || 46 == last )
-				$document.triggerHandler('wpcountwords', [ co.val() ]);
+				wp.hooks.doAction ('wpcountwords',  co.val() );
 
 			last = k;
 			return true;
Index: src/wp-includes/js/tinymce/plugins/wordpress/plugin.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wordpress/plugin.js	(revision 27473)
+++ src/wp-includes/js/tinymce/plugins/wordpress/plugin.js	(working copy)
@@ -394,7 +394,7 @@
 			}
 
 			if ( 13 === key || 8 === last || 46 === last ) {
-				window.jQuery( document ).triggerHandler( 'wpcountwords', [ editor.getContent({ format : 'raw' }) ] );
+				window.wp.hooks.doAction( 'wpcountwords',  editor.getContent({ format : 'raw' }) );
 			}
 
 			last = key;
Index: src/wp-includes/js/wp-auth-check.js
===================================================================
--- src/wp-includes/js/wp-auth-check.js	(revision 27473)
+++ src/wp-includes/js/wp-auth-check.js	(working copy)
@@ -1,4 +1,4 @@
-/* global adminpage */
+/* global adminpage, wp */
 // Interim login dialog
 (function($){
 	var wrap, next;
@@ -88,7 +88,7 @@
 		next = ( new Date() ).getTime() + ( interval * 1000 );
 	}
 
-	$( document ).on( 'heartbeat-tick.wp-auth-check', function( e, data ) {
+	wp.hooks.addAction( 'heartbeat-tick', function( data ) {
 		if ( 'wp-auth-check' in data ) {
 			schedule();
 			if ( ! data['wp-auth-check'] && wrap.hasClass('hidden') ) {
@@ -97,11 +97,12 @@
 				hide();
 			}
 		}
-	}).on( 'heartbeat-send.wp-auth-check', function( e, data ) {
+	}).addAction( 'heartbeat-send', function( data ) {
 		if ( ( new Date() ).getTime() > next ) {
 			data['wp-auth-check'] = true;
 		}
-	}).ready( function() {
+	});
+	$(document).ready( function() {
 		schedule();
 		wrap = $('#wp-auth-check-wrap');
 		wrap.find('.wp-auth-check-close').on( 'click', function() {
Index: src/wp-includes/js/heartbeat.js
===================================================================
--- src/wp-includes/js/heartbeat.js	(revision 27473)
+++ src/wp-includes/js/heartbeat.js	(working copy)
@@ -19,7 +19,7 @@
  * - heartbeat_nopriv_tick
  * @see wp_ajax_nopriv_heartbeat(), wp_ajax_heartbeat()
  *
- * Custom jQuery events:
+ * WP-hooks actions:
  * - heartbeat-send
  * - heartbeat-tick
  * - heartbeat-error
@@ -251,7 +251,7 @@
 
 				if ( trigger && ! hasConnectionError() ) {
 					settings.connectionError = true;
-					$document.trigger( 'heartbeat-connection-lost', [error, status] );
+					wp.hooks.doAction('heartbeat-connection-lost', error, status);
 				}
 			}
 		}
@@ -270,7 +270,7 @@
 			if ( hasConnectionError() ) {
 				settings.errorcount = 0;
 				settings.connectionError = false;
-				$document.trigger( 'heartbeat-connection-restored' );
+				wp.hooks.doAction( 'heartbeat-connection-restored' );
 			}
 		}
 
@@ -296,7 +296,7 @@
 			// Clear the data queue, anything added after this point will be send on the next tick
 			settings.queue = {};
 
-			$document.trigger( 'heartbeat-send', [ heartbeatData ] );
+			wp.hooks.doAction( 'heartbeat-send', heartbeatData );
 
 			ajaxData = {
 				data: heartbeatData,
@@ -328,7 +328,7 @@
 				clearErrorState();
 
 				if ( response.nonces_expired ) {
-					$document.trigger( 'heartbeat-nonces-expired' );
+					wp.hooks.doAction( 'heartbeat-nonces-expired' );
 					return;
 				}
 
@@ -338,7 +338,7 @@
 					delete response.heartbeat_interval;
 				}
 
-				$document.trigger( 'heartbeat-tick', [response, textStatus, jqXHR] );
+				wp.hooks.doAction ( 'heartbeat-tick', response, textStatus, jqXHR );
 
 				// Do this last, can trigger the next XHR if connection time > 5 sec. and newInterval == 'fast'
 				if ( newInterval ) {
@@ -346,7 +346,7 @@
 				}
 			}).fail( function( jqXHR, textStatus, error ) {
 				setErrorState( textStatus || 'unknown', jqXHR.status );
-				$document.trigger( 'heartbeat-error', [jqXHR, textStatus, error] );
+				wp.hooks.doAction ( 'heartbeat-error', jqXHR, textStatus, error );
 			});
 		}
 
@@ -660,7 +660,7 @@
 		 *
 		 * As the data is send asynchronously, this function doesn't return the XHR response.
 		 * To see the response, use the custom jQuery event 'heartbeat-tick' on the document, example:
-		 *		$(document).on( 'heartbeat-tick.myname', function( event, data, textStatus, jqXHR ) {
+		 *		wp.hooks.addAction( 'heartbeat-tick', function( data, textStatus, jqXHR ) {
 		 *			// code
 		 *		});
 		 * If the same 'handle' is used more than once, the data is not overwritten when the third argument is 'true'.
Index: src/wp-includes/js/autosave.js
===================================================================
--- src/wp-includes/js/autosave.js	(revision 27473)
+++ src/wp-includes/js/autosave.js	(working copy)
@@ -1,5 +1,16 @@
-/* global tinymce, wpCookies, autosaveL10n, switchEditors */
+/* global tinymce, wpCookies, autosaveL10n, switchEditors, wp */
 // Back-compat: prevent fatal errors
+
+/**
+ * WP-hook actions
+ * - autosave-disable-buttons
+ * - autosave-enable-buttons
+ * - before-autosave
+ * - after-autosave
+ * - wpcountwords
+ *
+ */
+
 window.autosave = function(){};
 
 ( function( $, window ) {
@@ -77,13 +88,13 @@
 		}
 
 		function disableButtons() {
-			$document.trigger('autosave-disable-buttons');
+			wp.hooks.doAction('autosave-disable-buttons');
 			// Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
 			setTimeout( enableButtons, 5000 );
 		}
 
 		function enableButtons() {
-			$document.trigger( 'autosave-enable-buttons' );
+			wp.hooks.doAction( 'autosave-enable-buttons' );
 		}
 
 		// Autosave in localStorage
@@ -452,7 +463,7 @@
 				lastCompareString = previousCompareString;
 				previousCompareString = '';
 
-				$document.trigger( 'after-autosave', [data] );
+				wp.hooks.doAction( 'after-autosave', data );
 				enableButtons();
 
 				if ( data.success ) {
@@ -514,8 +525,8 @@
 				tempBlockSave();
 				disableButtons();
 
-				$document.trigger( 'wpcountwords', [ postData.content ] )
-					.trigger( 'before-autosave', [ postData ] );
+				wp.hooks.doAction ( 'wpcountwords', postData.content )
+					.doAction( 'before-autosave',  postData  );
 
 				postData._wpnonce = $( '#_wpnonce' ).val() || '';
 
@@ -526,17 +537,17 @@
 				nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
 			}
 
-			$document.on( 'heartbeat-send.autosave', function( event, data ) {
+			wp.hooks.addAction( 'heartbeat-send', function( data ) {
 				var autosaveData = save();
 
 				if ( autosaveData ) {
 					data.wp_autosave = autosaveData;
 				}
-			}).on( 'heartbeat-tick.autosave', function( event, data ) {
+			}).addAction( 'heartbeat-tick', function( data ) {
 				if ( data.wp_autosave ) {
 					response( data.wp_autosave );
 				}
-			}).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
+			}).addAction( 'heartbeat-connection-lost', function( error, status ) {
 				// When connection is lost, keep user from submitting changes.
 				if ( 'timeout' === error || 603 === status ) {
 					var $notice = $('#lost-connection-notice');
@@ -548,10 +559,11 @@
 					$notice.show();
 					disableButtons();
 				}
-			}).on( 'heartbeat-connection-restored.autosave', function() {
+			}).addAction( 'heartbeat-connection-restored', function() {
 				$('#lost-connection-notice').hide();
 				enableButtons();
-			}).ready( function() {
+			});
+			$document.ready( function() {
 				_schedule();
 			});
 
Index: src/wp-admin/js/inline-edit-post.js
===================================================================
--- src/wp-admin/js/inline-edit-post.js	(revision 27473)
+++ src/wp-admin/js/inline-edit-post.js	(working copy)
@@ -1,4 +1,4 @@
-/* global inlineEditL10n, ajaxurl, typenow */
+/* global inlineEditL10n, ajaxurl, typenow, wp */
 
 var inlineEditPost;
 (function($) {
@@ -314,7 +314,7 @@
 $( document ).ready( function(){ inlineEditPost.init(); } );
 
 // Show/hide locks on posts
-$( document ).on( 'heartbeat-tick.wp-check-locked-posts', function( e, data ) {
+wp.hooks.addAction( 'heartbeat-tick', function( data ) {
 	var locked = data['wp-check-locked-posts'] || {};
 
 	$('#the-list tr').each( function(i, el) {
@@ -337,7 +337,7 @@
 			row.removeClass('wp-locked').delay(1000).find('.locked-info span').empty();
 		}
 	});
-}).on( 'heartbeat-send.wp-check-locked-posts', function( e, data ) {
+}).addAction( 'heartbeat-send', function( data ) {
 	var check = [];
 
 	$('#the-list tr').each( function(i, el) {
@@ -349,7 +349,8 @@
 	if ( check.length ) {
 		data['wp-check-locked-posts'] = check;
 	}
-}).ready( function() {
+});
+$(document).ready( function() {
 	// Set the heartbeat interval to 15 sec.
 	if ( typeof wp !== 'undefined' && wp.heartbeat ) {
 		wp.heartbeat.interval( 15 );
