Index: wp-admin/admin-ajax.php
===================================================================
--- wp-admin/admin-ajax.php	(revision 23353)
+++ wp-admin/admin-ajax.php	(working copy)
@@ -56,7 +56,7 @@
 	'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
 	'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment',
 	'query-attachments', 'save-attachment', 'save-attachment-compat', 'send-link-to-editor',
-	'send-attachment-to-editor', 'save-attachment-order',
+	'send-attachment-to-editor', 'save-attachment-order', 'heartbeat',
 );
 
 // Register core Ajax calls.
Index: wp-admin/includes/ajax-actions.php
===================================================================
--- wp-admin/includes/ajax-actions.php	(revision 23353)
+++ wp-admin/includes/ajax-actions.php	(working copy)
@@ -2071,3 +2071,29 @@
 
 	wp_send_json_success( $html );
 }
+
+function wp_ajax_heartbeat() {
+	check_ajax_referer( 'heartbeat-nonce', '_nonce' );
+	$response = array();
+
+	if ( ! empty($_POST['data']) ) {
+		$data = (array) $_POST['data'];
+		// todo: how much to sanitize and preset and what to leave to be accessed from $data or $_POST..?
+		$user = wp_get_current_user();
+		$data['user_id'] = $user->exists() ? $user->ID : 0;
+
+		// todo: separate filters: 'heartbeat_[action]' so we call different callbacks only when there is data for them,
+		// or all callbacks listen to one filter and run when there is something for them in $data?
+		$response = apply_filters( 'heartbeat_received', $response, $data );
+	}
+
+	$response = apply_filters( 'heartbeat_send', $response );
+
+	// Allow the transport to be replaced with long-polling easily
+	do_action( 'heartbeat_tick', $response );
+
+	// always send the current time acording to the server
+	$response['time'] = time();
+
+	wp_send_json($response);
+}
Index: wp-admin/load-scripts.php
===================================================================
--- wp-admin/load-scripts.php	(revision 23353)
+++ wp-admin/load-scripts.php	(working copy)
@@ -103,6 +103,11 @@
 function json_encode() {}
 endif;
 
+/**
+ * @ignore
+ */
+function wp_create_nonce() {}
+
 function get_file($path) {
 
 	if ( function_exists('realpath') )
Index: wp-admin/load-styles.php
===================================================================
--- wp-admin/load-styles.php	(revision 23353)
+++ wp-admin/load-styles.php	(working copy)
@@ -81,6 +81,11 @@
  */
 function wp_guess_url() {}
 
+/**
+ * @ignore
+ */
+function wp_create_nonce() {}
+
 function get_file($path) {
 
 	if ( function_exists('realpath') )
Index: wp-includes/js/autosave.js
===================================================================
--- wp-includes/js/autosave.js	(revision 23353)
+++ wp-includes/js/autosave.js	(working copy)
@@ -341,6 +341,12 @@
 	}
 
 	autosaveOldMessage = jQuery('#autosave').html();
+	
+	// for testing only
+	wp.heartbeat.send('autosave', post_data);
+	
+	
+	/*
 	jQuery.ajax({
 		data: post_data,
 		beforeSend: doAutoSave ? autosave_loading : null,
@@ -348,4 +354,5 @@
 		url: ajaxurl,
 		success: successCallback
 	});
+	*/
 }
Index: wp-includes/js/heartbeat.js
===================================================================
--- wp-includes/js/heartbeat.js	(revision 0)
+++ wp-includes/js/heartbeat.js	(working copy)
@@ -0,0 +1,160 @@
+/**
+ * Heartbeat API
+ */
+
+ // Ensure the global `wp` object exists.
+window.wp = window.wp || {};
+
+(function($){
+	var Heartbeat = function() {
+		var self = this,
+			running,
+			timeout,
+			nonce,
+			tick = 0,
+			queue = {},
+			interval,
+			lastconnect = 0;
+
+		this.url = typeof ajaxurl != 'undefined' ? ajaxurl : 'wp-admin/admin-ajax.php';
+		this.autostart = true;
+
+		if ( typeof( window.heartbeatSettings != 'undefined' ) ) {
+			settings = $.extend( {}, window.heartbeatSettings );
+			delete window.heartbeatSettings;
+
+			nonce = settings.nonce || '';
+			delete settings.nonce;
+
+			interval = settings.interval || 15000; // default interval
+			delete settings.interval;
+
+			$.extend( this, settings );
+		}
+
+		function time(s) {
+			if ( s )
+				return parseInt( (new Date()).getTime() / 1000 );
+
+			return (new Date()).getTime();
+		}
+
+		function throttle(seconds) {
+			if ( seconds ) {
+				// Limit
+				if ( 5 > seconds || seconds > 60 )
+					return false;
+
+				interval = seconds * 1000;
+			} else if ( seconds === 0 ) {
+				// Allow long polling to be turned on
+				interval = 0;
+			}
+			return interval / 1000;
+		}
+
+		function errorstate() {
+			var since;
+
+			if ( lastconnect ) {
+				since = time() - lastconnect;
+
+				if ( since > 180000 ) {
+					self.connectionLost = true;
+					$(document).trigger( 'heartbeat-connection-lost', { seconds: parseInt(since / 1000) } );
+				} else if ( self.connectionLost ) {
+					self.connectionLost = false;
+					$(document).trigger( 'heartbeat-connection-restored' );
+				}
+			}
+		}
+
+		function connect() {
+			var data = {};
+			tick = time();
+
+			data.data = $.extend( {}, queue );
+			queue = {};
+
+			data.interval = interval / 1000;
+			data._nonce = nonce;
+			data.action = 'heartbeat';
+
+			self.xhr = $.post( self.url, data, function(r){
+				lastconnect = time();
+				self.tick(r);
+			}, 'json' ).always( function(){
+				next();
+			}).fail( function(r){
+				errorstate();
+				self.error(r);
+			});
+		};
+
+		function next() {
+			var delta = time() - tick;
+
+			if ( !running )
+				return;
+
+			if ( delta < interval ) {
+				timeout = window.setTimeout(
+					function(){
+						if ( running )
+							connect();
+					},
+					interval - delta
+				);
+			} else {
+				window.clearTimeout(timeout); // this has already expired?
+				connect();
+			}
+		};
+
+		this.interval = function(seconds) {
+			return throttle(seconds);
+		};
+
+		this.start = function() {
+			// start only once
+			if ( running )
+				return false;
+
+			running = true;
+			connect();
+
+			return true;
+		};
+
+		this.stop = function() {
+			if ( !running )
+				return false;
+
+			if ( self.xhr )
+				self.xhr.abort();
+
+			running = false;
+			return true;
+		}
+
+		this.send = function(action, data) {
+			if ( action )
+				queue[action] = data;
+		}
+
+		if ( this.autostart )
+			$(document).ready( function(){ self.start(); });
+	}
+
+	$.extend( Heartbeat.prototype, {
+		tick: function(r) {
+			$(document).trigger( 'heartbeat-tick', r );
+		},
+		error: function(r) {
+			$(document).trigger( 'heartbeat-error', r );
+		}
+	});
+
+	wp.heartbeat = new Heartbeat();
+
+}(jQuery));
Index: wp-includes/js/heartbeat.js
===================================================================
--- wp-includes/js/heartbeat.js	(revision 0)
+++ wp-includes/js/heartbeat.js	(working copy)

Property changes on: wp-includes/js/heartbeat.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: wp-includes/script-loader.php
===================================================================
--- wp-includes/script-loader.php	(revision 23353)
+++ wp-includes/script-loader.php	(working copy)
@@ -107,6 +107,11 @@
 	) );
 
 	$scripts->add( 'autosave', "/wp-includes/js/autosave$suffix.js", array('schedule', 'wp-ajax-response'), false, 1 );
+	
+	$scripts->add( 'heartbeat', "/wp-includes/js/heartbeat$suffix.js", array(), false, 1 );
+	did_action( 'init' ) && $scripts->localize( 'heartbeat', 'heartbeatSettings',
+		apply_filters( 'heartbeatSettings', array( 'nonce' => wp_create_nonce( 'heartbeat-nonce' ) ) )
+	);
 
 	$scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 );
 
@@ -371,7 +376,7 @@
 
 		$scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array('jquery-ui-sortable'), false, 1 );
 
-		$scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox'), false, 1 );
+		$scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox', 'heartbeat'), false, 1 );
 		did_action( 'init' ) && $scripts->localize( 'post', 'postL10n', array(
 			'ok' => __('OK'),
 			'cancel' => __('Cancel'),
