Index: wp-includes/class-wp-customize-manager.php
===================================================================
--- wp-includes/class-wp-customize-manager.php	(revision 20976)
+++ wp-includes/class-wp-customize-manager.php	(working copy)
@@ -290,7 +290,8 @@
 	 */
 	public function customize_preview_settings() {
 		$settings = array(
-			'values' => array(),
+			'values'  => array(),
+			'channel' => esc_js( $_POST['customize_messenger_channel'] ),
 		);
 
 		foreach ( $this->settings as $id => $setting ) {
Index: wp-includes/js/customize-base.dev.js
===================================================================
--- wp-includes/js/customize-base.dev.js	(revision 20976)
+++ wp-includes/js/customize-base.dev.js	(working copy)
@@ -302,13 +302,12 @@
 			return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
 		},
 
-		get: function() {
-			var result = {};
+		each: function( callback, context ) {
+			context = typeof context === 'undefined' ? this : context;
 
 			$.each( this._value, function( key, obj ) {
-				result[ key ] = obj.get();
-			} );
-			return result;
+				callback.call( context, obj, key );
+			});
 		},
 
 		remove: function( id ) {
@@ -481,19 +480,36 @@
 			return this[ key ] = new api.Value( initial, options );
 		},
 
-		initialize: function( url, targetWindow, options ) {
+		/**
+		 * Initialize Messenger.
+		 *
+		 * @param  {object} params        Parameters to configure the messenger.
+		 *         {string} .url          The URL to communicate with.
+		 *         {window} .targetWindow The window instance to communicate with. Default window.parent.
+		 *         {string} .channel      If provided, will send the channel with each message and only accept messages a matching channel.
+		 * @param  {object} options       Extend any instance parameter or method with this object.
+		 */
+		initialize: function( params, options ) {
 			// Target the parent frame by default, but only if a parent frame exists.
 			var defaultTarget = window.parent == window ? null : window.parent;
 
 			$.extend( this, options || {} );
 
-			url = this.add( 'url', url );
-			this.add( 'targetWindow', targetWindow || defaultTarget );
-			this.add( 'origin', url() ).link( url ).setter( function( to ) {
+			this.add( 'channel', params.channel );
+			this.add( 'url', params.url );
+			this.add( 'targetWindow', params.targetWindow || defaultTarget );
+			this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
 				return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
 			});
 
+			// Since we want jQuery to treat the receive function as unique
+			// to this instance, we give the function a new guid.
+			//
+			// This will prevent every Messenger's receive function from being
+			// unbound when calling $.off( 'message', this.receive );
 			this.receive = $.proxy( this.receive, this );
+			this.receive.guid = $.guid++;
+
 			$( window ).on( 'message', this.receive );
 		},
 
@@ -515,8 +531,15 @@
 
 			message = JSON.parse( event.data );
 
-			if ( message && message.id && typeof message.data !== 'undefined' )
-				this.trigger( message.id, message.data );
+			// Check required message properties.
+			if ( ! message || ! message.id || typeof message.data === 'undefined' )
+				return;
+
+			// Check if channel names match.
+			if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
+				return;
+
+			this.trigger( message.id, message.data );
 		},
 
 		send: function( id, data ) {
@@ -527,8 +550,11 @@
 			if ( ! this.url() || ! this.targetWindow() )
 				return;
 
-			message = JSON.stringify({ id: id, data: data });
-			this.targetWindow().postMessage( message, this.origin() );
+			message = { id: id, data: data };
+			if ( this.channel() )
+				message.channel = this.channel();
+
+			this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
 		}
 	});
 
@@ -540,7 +566,16 @@
 	 * ===================================================================== */
 
 	api = $.extend( new api.Values(), api );
+	api.get = function() {
+		var result = {};
 
+		this.each( function( obj, key ) {
+			result[ key ] = obj.get();
+		});
+
+		return result;
+	};
+
 	// Expose the API to the world.
 	exports.customize = api;
 })( wp, jQuery );
Index: wp-includes/js/customize-preview.dev.js
===================================================================
--- wp-includes/js/customize-preview.dev.js	(revision 20976)
+++ wp-includes/js/customize-preview.dev.js	(working copy)
@@ -21,15 +21,11 @@
 		/**
 		 * Requires params:
 		 *  - url    - the URL of preview frame
-		 *
-		 * @todo: Perhaps add a window.onbeforeunload dialog in case the theme
-		 *        somehow attempts to leave the page and we don't catch it
-		 *        (which really shouldn't happen).
 		 */
-		initialize: function( url, options ) {
+		initialize: function( params, options ) {
 			var self = this;
 
-			api.Messenger.prototype.initialize.call( this, url, null, options );
+			api.Messenger.prototype.initialize.call( this, params, options );
 
 			this.body = $( document.body );
 			this.body.on( 'click.preview', 'a', function( event ) {
@@ -39,8 +35,7 @@
 			});
 
 			// You cannot submit forms.
-			// @todo: Namespace customizer settings so that we can mix the
-			//        $_POST data with the customize setting $_POST data.
+			// @todo: Allow form submissions by mixing $_POST data with the customize setting $_POST data.
 			this.body.on( 'submit.preview', 'form', function( event ) {
 				event.preventDefault();
 			});
@@ -63,18 +58,40 @@
 
 		var preview, bg;
 
-		preview = new api.Preview( window.location.href );
+		preview = new api.Preview({
+			url: window.location.href,
+			channel: api.settings.channel
+		});
 
-		$.each( api.settings.values, function( id, value ) {
-			api.create( id, value );
+		preview.bind( 'settings', function( values ) {
+			$.each( values, function( id, value ) {
+				if ( api.has( id ) )
+					api( id ).set( value );
+				else
+					api.create( id, value );
+			});
 		});
 
+		preview.trigger( 'settings', api.settings.values );
+
 		preview.bind( 'setting', function( args ) {
-			var value = api( args.shift() );
-			if ( value )
+			var value;
+
+			args = args.slice();
+
+			if ( value = api( args.shift() ) )
 				value.set.apply( value, args );
 		});
 
+		preview.bind( 'sync', function( events ) {
+			$.each( events, function( event, args ) {
+				preview.trigger( event, args );
+			});
+			preview.send( 'synced' );
+		})
+
+		preview.send( 'ready' );
+
 		/* Custom Backgrounds */
 		bg = $.map(['color', 'image', 'position_x', 'repeat', 'attachment'], function( prop ) {
 			return 'background_' + prop;
Index: wp-includes/js/customize-loader.dev.js
===================================================================
--- wp-includes/js/customize-loader.dev.js	(revision 20976)
+++ wp-includes/js/customize-loader.dev.js	(working copy)
@@ -77,7 +77,11 @@
 			this.iframe.one( 'load', this.loaded );
 
 			// Create a postMessage connection with the iframe.
-			this.messenger = new api.Messenger( src, this.iframe[0].contentWindow );
+			this.messenger = new api.Messenger({
+				url: src,
+				channel: 'loader',
+				targetWindow: this.iframe[0].contentWindow
+			});
 
 			// Wait for the connection from the iframe before sending any postMessage events.
 			this.messenger.bind( 'ready', function() {
Index: wp-admin/js/customize-controls.dev.js
===================================================================
--- wp-admin/js/customize-controls.dev.js	(revision 20976)
+++ wp-admin/js/customize-controls.dev.js	(working copy)
@@ -281,6 +281,109 @@
 	// Create the collection of Control objects.
 	api.control = new api.Values({ defaultConstructor: api.Control });
 
+	api.PreviewFrame = api.Messenger.extend({
+		sensitivity: 2000,
+
+		initialize: function( params, options ) {
+			var loaded   = false,
+				ready    = false,
+				deferred = $.Deferred(),
+				self     = this;
+
+			// This is the promise object.
+			deferred.promise( this );
+
+			this.previewer = params.previewer;
+
+			$.extend( params, { channel: api.PreviewFrame.uuid() });
+
+			api.Messenger.prototype.initialize.call( this, params, options );
+
+			this.bind( 'ready', function() {
+				ready = true;
+
+				if ( loaded )
+					deferred.resolveWith( self );
+			});
+
+			params.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() });
+
+			this.request = $.ajax( this.url(), {
+				type: 'POST',
+				data: params.query,
+				xhrFields: {
+					withCredentials: true
+				}
+			} );
+
+			this.request.fail( function() {
+				deferred.rejectWith( self, [ 'request failure' ] );
+			});
+
+			this.request.done( function( response ) {
+				var location = self.request.getResponseHeader('Location'),
+					signature = 'WP_CUSTOMIZER_SIGNATURE',
+					index;
+
+				// Check if the location response header differs from the current URL.
+				// If so, the request was redirected; try loading the requested page.
+				if ( location && location != self.url() ) {
+					deferred.rejectWith( self, [ 'redirect', location ] );
+					return;
+				}
+
+				// Check for a signature in the request.
+				index = response.lastIndexOf( signature );
+				if ( -1 === index || index < response.lastIndexOf('</html>') ) {
+					deferred.rejectWith( self, [ 'unsigned' ] );
+					return;
+				}
+
+				// Strip the signature from the request.
+				response = response.slice( 0, index ) + response.slice( index + signature.length );
+
+				// Create the iframe and inject the html content.
+				self.iframe = $('<iframe />').one( 'load', function() {
+					loaded = true;
+
+					if ( ready ) {
+						deferred.resolveWith( self );
+					} else {
+						setTimeout( function() {
+							deferred.rejectWith( self, [ 'ready timeout' ] );
+						}, self.sensitivity );
+					}
+				});
+
+				self.iframe.appendTo( self.previewer.container );
+				self.targetWindow( self.iframe[0].contentWindow );
+
+				self.targetWindow().document.open();
+				self.targetWindow().document.write( response );
+				self.targetWindow().document.close();
+			});
+		},
+
+		destroy: function() {
+			api.Messenger.prototype.destroy.call( this );
+			this.request.abort();
+
+			if ( this.iframe )
+				this.iframe.remove();
+
+			delete this.request;
+			delete this.iframe;
+			delete this.targetWindow;
+		}
+	});
+
+	(function(){
+		var uuid = 0;
+		api.PreviewFrame.uuid = function() {
+			return 'preview-' + uuid++;
+		};
+	}());
+
 	api.Previewer = api.Messenger.extend({
 		refreshBuffer: 250,
 
@@ -295,8 +398,6 @@
 
 			$.extend( this, options || {} );
 
-			this.loaded = $.proxy( this.loaded, this );
-
 			/*
 			 * Wrap this.refresh to prevent it from hammering the servers:
 			 *
@@ -320,9 +421,7 @@
 				return function() {
 					if ( typeof timeout !== 'number' ) {
 						if ( self.loading ) {
-							self.loading.remove();
-							delete self.loading;
-							self.loader();
+							self.abort();
 						} else {
 							return callback();
 						}
@@ -336,7 +435,7 @@
 			this.container   = api.ensure( params.container );
 			this.allowedUrls = params.allowedUrls;
 
-			api.Messenger.prototype.initialize.call( this, params.url );
+			api.Messenger.prototype.initialize.call( this, params );
 
 			// We're dynamically generating the iframe, so the origin is set
 			// to the current window's location, not the url's.
@@ -391,65 +490,49 @@
 			// Update the URL when the iframe sends a URL message.
 			this.bind( 'url', this.url );
 		},
-		loader: function() {
-			if ( this.loading )
-				return this.loading;
 
-			this.loading = $('<iframe />').appendTo( this.container );
+		query: function() {},
 
-			return this.loading;
+		abort: function() {
+			if ( this.loading ) {
+				this.loading.destroy();
+				delete this.loading;
+			}
 		},
-		loaded: function() {
-			if ( this.iframe )
-				this.iframe.remove();
 
-			this.iframe = this.loading;
-			delete this.loading;
-
-			this.targetWindow( this.iframe[0].contentWindow );
-			this.send( 'scroll', this.scroll );
-		},
-		query: function() {},
 		refresh: function() {
 			var self = this;
 
-			if ( this.request )
-				this.request.abort();
+			this.abort();
 
-			this.request = $.ajax( this.url(), {
-				type: 'POST',
-				data: this.query() || {},
-				success: function( response ) {
-					var iframe = self.loader()[0].contentWindow,
-						location = self.request.getResponseHeader('Location'),
-						signature = 'WP_CUSTOMIZER_SIGNATURE',
-						index;
+			this.loading = new api.PreviewFrame({
+				url:       this.url(),
+				query:     this.query() || {},
+				previewer: this
+			});
 
-					// Check if the location response header differs from the current URL.
-					// If so, the request was redirected; try loading the requested page.
-					if ( location && location != self.url() ) {
-						self.url( location );
-						return;
-					}
+			this.loading.done( function() {
+				// 'this' is the loading frame
+				this.bind( 'synced', function() {
+					if ( self.iframe )
+						self.iframe.destroy();
+					self.iframe = this;
+					delete self.loading;
 
-					// Check for a signature in the request.
-					index = response.lastIndexOf( signature );
-					if ( -1 === index || index < response.lastIndexOf('</html>') )
-						return;
+					self.targetWindow( this.targetWindow() );
+					self.channel( this.channel() );
+				});
 
-					// Strip the signature from the request.
-					response = response.slice( 0, index ) + response.slice( index + signature.length );
+				this.send( 'sync', {
+					scroll:   self.scroll,
+					settings: api.get()
+				});
+			});
 
-					self.loader().one( 'load', self.loaded );
-
-					iframe.document.open();
-					iframe.document.write( response );
-					iframe.document.close();
-				},
-				xhrFields: {
-					withCredentials: true
-				}
-			} );
+			this.loading.fail( function( reason, location ) {
+				if ( 'redirect' === reason && location )
+					self.url( location );
+			});
 		}
 	});
 
@@ -617,7 +700,10 @@
 		});
 
 		// Create a potential postMessage connection with the parent frame.
-		parent = new api.Messenger( api.settings.url.parent );
+		parent = new api.Messenger({
+			url: api.settings.url.parent,
+			channel: 'loader'
+		});
 
 		// If we receive a 'back' event, we're inside an iframe.
 		// Send any clicks to the 'Return' link to the parent page.
