<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[20988] trunk: Theme Customizer: Fix race condition in previewer and use message channels.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg > ul, #logmsg > ol { margin-left: 0; margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/20988">20988</a></dd>
<dt>Author</dt> <dd>ryan</dd>
<dt>Date</dt> <dd>2012-06-04 15:51:46 +0000 (Mon, 04 Jun 2012)</dd>
</dl>

<h3>Log Message</h3>
<pre>Theme Customizer: Fix race condition in previewer and use message channels. Props koopersmith. fixes <a href="http://core.trac.wordpress.org/ticket/20811">#20811</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpadminjscustomizecontrolsdevjs">trunk/wp-admin/js/customize-controls.dev.js</a></li>
<li><a href="#trunkwpincludesclasswpcustomizemanagerphp">trunk/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunkwpincludesjscustomizebasedevjs">trunk/wp-includes/js/customize-base.dev.js</a></li>
<li><a href="#trunkwpincludesjscustomizeloaderdevjs">trunk/wp-includes/js/customize-loader.dev.js</a></li>
<li><a href="#trunkwpincludesjscustomizepreviewdevjs">trunk/wp-includes/js/customize-preview.dev.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkwpadminjscustomizecontrolsdevjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/js/customize-controls.dev.js (20987 => 20988)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/js/customize-controls.dev.js        2012-06-04 15:18:22 UTC (rev 20987)
+++ trunk/wp-admin/js/customize-controls.dev.js        2012-06-04 15:51:46 UTC (rev 20988)
</span><span class="lines">@@ -281,6 +281,116 @@
</span><span class="cx">         // Create the collection of Control objects.
</span><span class="cx">         api.control = new api.Values({ defaultConstructor: api.Control });
</span><span class="cx"> 
</span><ins>+        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 &amp;&amp; location != self.url() ) {
+                                        deferred.rejectWith( self, [ 'redirect', location ] );
+                                        return;
+                                }
+
+                                // Check for a signature in the request.
+                                index = response.lastIndexOf( signature );
+                                if ( -1 === index || index &lt; response.lastIndexOf('&lt;/html&gt;') ) {
+                                        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.
+                                // 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 = $('&lt;iframe /&gt;').appendTo( self.previewer.container );
+
+                                // Bind load event after the iframe has been added to the page;
+                                // otherwise it will fire when injected into the DOM.
+                                self.iframe.one( 'load', function() {
+                                        loaded = true;
+
+                                        if ( ready ) {
+                                                deferred.resolveWith( self );
+                                        } else {
+                                                setTimeout( function() {
+                                                        deferred.rejectWith( self, [ 'ready timeout' ] );
+                                                }, self.sensitivity );
+                                        }
+                                });
+
+                                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++;
+                };
+        }());
+
</ins><span class="cx">         api.Previewer = api.Messenger.extend({
</span><span class="cx">                 refreshBuffer: 250,
</span><span class="cx"> 
</span><span class="lines">@@ -295,8 +405,6 @@
</span><span class="cx"> 
</span><span class="cx">                         $.extend( this, options || {} );
</span><span class="cx"> 
</span><del>-                        this.loaded = $.proxy( this.loaded, this );
-
</del><span class="cx">                         /*
</span><span class="cx">                          * Wrap this.refresh to prevent it from hammering the servers:
</span><span class="cx">                          *
</span><span class="lines">@@ -320,9 +428,7 @@
</span><span class="cx">                                 return function() {
</span><span class="cx">                                         if ( typeof timeout !== 'number' ) {
</span><span class="cx">                                                 if ( self.loading ) {
</span><del>-                                                        self.loading.remove();
-                                                        delete self.loading;
-                                                        self.loader();
</del><ins>+                                                        self.abort();
</ins><span class="cx">                                                 } else {
</span><span class="cx">                                                         return callback();
</span><span class="cx">                                                 }
</span><span class="lines">@@ -336,7 +442,7 @@
</span><span class="cx">                         this.container   = api.ensure( params.container );
</span><span class="cx">                         this.allowedUrls = params.allowedUrls;
</span><span class="cx"> 
</span><del>-                        api.Messenger.prototype.initialize.call( this, params.url );
</del><ins>+                        api.Messenger.prototype.initialize.call( this, params );
</ins><span class="cx"> 
</span><span class="cx">                         // We're dynamically generating the iframe, so the origin is set
</span><span class="cx">                         // to the current window's location, not the url's.
</span><span class="lines">@@ -391,65 +497,49 @@
</span><span class="cx">                         // Update the URL when the iframe sends a URL message.
</span><span class="cx">                         this.bind( 'url', this.url );
</span><span class="cx">                 },
</span><del>-                loader: function() {
-                        if ( this.loading )
-                                return this.loading;
</del><span class="cx"> 
</span><del>-                        this.loading = $('&lt;iframe /&gt;').appendTo( this.container );
</del><ins>+                query: function() {},
</ins><span class="cx"> 
</span><del>-                        return this.loading;
</del><ins>+                abort: function() {
+                        if ( this.loading ) {
+                                this.loading.destroy();
+                                delete this.loading;
+                        }
</ins><span class="cx">                 },
</span><del>-                loaded: function() {
-                        if ( this.iframe )
-                                this.iframe.remove();
</del><span class="cx"> 
</span><del>-                        this.iframe = this.loading;
-                        delete this.loading;
-
-                        this.targetWindow( this.iframe[0].contentWindow );
-                        this.send( 'scroll', this.scroll );
-                },
-                query: function() {},
</del><span class="cx">                 refresh: function() {
</span><span class="cx">                         var self = this;
</span><span class="cx"> 
</span><del>-                        if ( this.request )
-                                this.request.abort();
</del><ins>+                        this.abort();
</ins><span class="cx"> 
</span><del>-                        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;
</del><ins>+                        this.loading = new api.PreviewFrame({
+                                url:       this.url(),
+                                query:     this.query() || {},
+                                previewer: this
+                        });
</ins><span class="cx"> 
</span><del>-                                        // Check if the location response header differs from the current URL.
-                                        // If so, the request was redirected; try loading the requested page.
-                                        if ( location &amp;&amp; location != self.url() ) {
-                                                self.url( location );
-                                                return;
-                                        }
</del><ins>+                        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;
</ins><span class="cx"> 
</span><del>-                                        // Check for a signature in the request.
-                                        index = response.lastIndexOf( signature );
-                                        if ( -1 === index || index &lt; response.lastIndexOf('&lt;/html&gt;') )
-                                                return;
</del><ins>+                                        self.targetWindow( this.targetWindow() );
+                                        self.channel( this.channel() );
+                                });
</ins><span class="cx"> 
</span><del>-                                        // Strip the signature from the request.
-                                        response = response.slice( 0, index ) + response.slice( index + signature.length );
</del><ins>+                                this.send( 'sync', {
+                                        scroll:   self.scroll,
+                                        settings: api.get()
+                                });
+                        });
</ins><span class="cx"> 
</span><del>-                                        self.loader().one( 'load', self.loaded );
-
-                                        iframe.document.open();
-                                        iframe.document.write( response );
-                                        iframe.document.close();
-                                },
-                                xhrFields: {
-                                        withCredentials: true
-                                }
-                        } );
</del><ins>+                        this.loading.fail( function( reason, location ) {
+                                if ( 'redirect' === reason &amp;&amp; location )
+                                        self.url( location );
+                        });
</ins><span class="cx">                 }
</span><span class="cx">         });
</span><span class="cx"> 
</span><span class="lines">@@ -617,7 +707,10 @@
</span><span class="cx">                 });
</span><span class="cx"> 
</span><span class="cx">                 // Create a potential postMessage connection with the parent frame.
</span><del>-                parent = new api.Messenger( api.settings.url.parent );
</del><ins>+                parent = new api.Messenger({
+                        url: api.settings.url.parent,
+                        channel: 'loader'
+                });
</ins><span class="cx"> 
</span><span class="cx">                 // If we receive a 'back' event, we're inside an iframe.
</span><span class="cx">                 // Send any clicks to the 'Return' link to the parent page.
</span></span></pre></div>
<a id="trunkwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/class-wp-customize-manager.php (20987 => 20988)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/class-wp-customize-manager.php        2012-06-04 15:18:22 UTC (rev 20987)
+++ trunk/wp-includes/class-wp-customize-manager.php        2012-06-04 15:51:46 UTC (rev 20988)
</span><span class="lines">@@ -290,7 +290,8 @@
</span><span class="cx">          */
</span><span class="cx">         public function customize_preview_settings() {
</span><span class="cx">                 $settings = array(
</span><del>-                        'values' =&gt; array(),
</del><ins>+                        'values'  =&gt; array(),
+                        'channel' =&gt; esc_js( $_POST['customize_messenger_channel'] ),
</ins><span class="cx">                 );
</span><span class="cx"> 
</span><span class="cx">                 foreach ( $this-&gt;settings as $id =&gt; $setting ) {
</span></span></pre></div>
<a id="trunkwpincludesjscustomizebasedevjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/customize-base.dev.js (20987 => 20988)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/customize-base.dev.js        2012-06-04 15:18:22 UTC (rev 20987)
+++ trunk/wp-includes/js/customize-base.dev.js        2012-06-04 15:51:46 UTC (rev 20988)
</span><span class="lines">@@ -302,13 +302,12 @@
</span><span class="cx">                         return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                get: function() {
-                        var result = {};
</del><ins>+                each: function( callback, context ) {
+                        context = typeof context === 'undefined' ? this : context;
</ins><span class="cx"> 
</span><span class="cx">                         $.each( this._value, function( key, obj ) {
</span><del>-                                result[ key ] = obj.get();
-                        } );
-                        return result;
</del><ins>+                                callback.call( context, obj, key );
+                        });
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 remove: function( id ) {
</span><span class="lines">@@ -481,19 +480,36 @@
</span><span class="cx">                         return this[ key ] = new api.Value( initial, options );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                initialize: function( url, targetWindow, options ) {
</del><ins>+                /**
+                 * 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 ) {
</ins><span class="cx">                         // Target the parent frame by default, but only if a parent frame exists.
</span><span class="cx">                         var defaultTarget = window.parent == window ? null : window.parent;
</span><span class="cx"> 
</span><span class="cx">                         $.extend( this, options || {} );
</span><span class="cx"> 
</span><del>-                        url = this.add( 'url', url );
-                        this.add( 'targetWindow', targetWindow || defaultTarget );
-                        this.add( 'origin', url() ).link( url ).setter( function( to ) {
</del><ins>+                        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 ) {
</ins><span class="cx">                                 return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
</span><span class="cx">                         });
</span><span class="cx"> 
</span><ins>+                        // 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 );
</ins><span class="cx">                         this.receive = $.proxy( this.receive, this );
</span><ins>+                        this.receive.guid = $.guid++;
+
</ins><span class="cx">                         $( window ).on( 'message', this.receive );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="lines">@@ -515,8 +531,15 @@
</span><span class="cx"> 
</span><span class="cx">                         message = JSON.parse( event.data );
</span><span class="cx"> 
</span><del>-                        if ( message &amp;&amp; message.id &amp;&amp; typeof message.data !== 'undefined' )
-                                this.trigger( message.id, message.data );
</del><ins>+                        // Check required message properties.
+                        if ( ! message || ! message.id || typeof message.data === 'undefined' )
+                                return;
+
+                        // Check if channel names match.
+                        if ( ( message.channel || this.channel() ) &amp;&amp; this.channel() !== message.channel )
+                                return;
+
+                        this.trigger( message.id, message.data );
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 send: function( id, data ) {
</span><span class="lines">@@ -527,8 +550,11 @@
</span><span class="cx">                         if ( ! this.url() || ! this.targetWindow() )
</span><span class="cx">                                 return;
</span><span class="cx"> 
</span><del>-                        message = JSON.stringify({ id: id, data: data });
-                        this.targetWindow().postMessage( message, this.origin() );
</del><ins>+                        message = { id: id, data: data };
+                        if ( this.channel() )
+                                message.channel = this.channel();
+
+                        this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
</ins><span class="cx">                 }
</span><span class="cx">         });
</span><span class="cx"> 
</span><span class="lines">@@ -540,7 +566,16 @@
</span><span class="cx">          * ===================================================================== */
</span><span class="cx"> 
</span><span class="cx">         api = $.extend( new api.Values(), api );
</span><ins>+        api.get = function() {
+                var result = {};
</ins><span class="cx"> 
</span><ins>+                this.each( function( obj, key ) {
+                        result[ key ] = obj.get();
+                });
+
+                return result;
+        };
+
</ins><span class="cx">         // Expose the API to the world.
</span><span class="cx">         exports.customize = api;
</span><span class="cx"> })( wp, jQuery );
</span></span></pre></div>
<a id="trunkwpincludesjscustomizeloaderdevjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/customize-loader.dev.js (20987 => 20988)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/customize-loader.dev.js        2012-06-04 15:18:22 UTC (rev 20987)
+++ trunk/wp-includes/js/customize-loader.dev.js        2012-06-04 15:51:46 UTC (rev 20988)
</span><span class="lines">@@ -77,7 +77,11 @@
</span><span class="cx">                         this.iframe.one( 'load', this.loaded );
</span><span class="cx"> 
</span><span class="cx">                         // Create a postMessage connection with the iframe.
</span><del>-                        this.messenger = new api.Messenger( src, this.iframe[0].contentWindow );
</del><ins>+                        this.messenger = new api.Messenger({
+                                url: src,
+                                channel: 'loader',
+                                targetWindow: this.iframe[0].contentWindow
+                        });
</ins><span class="cx"> 
</span><span class="cx">                         // Wait for the connection from the iframe before sending any postMessage events.
</span><span class="cx">                         this.messenger.bind( 'ready', function() {
</span></span></pre></div>
<a id="trunkwpincludesjscustomizepreviewdevjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/customize-preview.dev.js (20987 => 20988)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/customize-preview.dev.js        2012-06-04 15:18:22 UTC (rev 20987)
+++ trunk/wp-includes/js/customize-preview.dev.js        2012-06-04 15:51:46 UTC (rev 20988)
</span><span class="lines">@@ -21,15 +21,11 @@
</span><span class="cx">                 /**
</span><span class="cx">                  * Requires params:
</span><span class="cx">                  *  - url    - the URL of preview frame
</span><del>-                 *
-                 * @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).
</del><span class="cx">                  */
</span><del>-                initialize: function( url, options ) {
</del><ins>+                initialize: function( params, options ) {
</ins><span class="cx">                         var self = this;
</span><span class="cx"> 
</span><del>-                        api.Messenger.prototype.initialize.call( this, url, null, options );
</del><ins>+                        api.Messenger.prototype.initialize.call( this, params, options );
</ins><span class="cx"> 
</span><span class="cx">                         this.body = $( document.body );
</span><span class="cx">                         this.body.on( 'click.preview', 'a', function( event ) {
</span><span class="lines">@@ -39,8 +35,7 @@
</span><span class="cx">                         });
</span><span class="cx"> 
</span><span class="cx">                         // You cannot submit forms.
</span><del>-                        // @todo: Namespace customizer settings so that we can mix the
-                        //        $_POST data with the customize setting $_POST data.
</del><ins>+                        // @todo: Allow form submissions by mixing $_POST data with the customize setting $_POST data.
</ins><span class="cx">                         this.body.on( 'submit.preview', 'form', function( event ) {
</span><span class="cx">                                 event.preventDefault();
</span><span class="cx">                         });
</span><span class="lines">@@ -63,18 +58,40 @@
</span><span class="cx"> 
</span><span class="cx">                 var preview, bg;
</span><span class="cx"> 
</span><del>-                preview = new api.Preview( window.location.href );
</del><ins>+                preview = new api.Preview({
+                        url: window.location.href,
+                        channel: api.settings.channel
+                });
</ins><span class="cx"> 
</span><del>-                $.each( api.settings.values, function( id, value ) {
-                        api.create( id, value );
</del><ins>+                preview.bind( 'settings', function( values ) {
+                        $.each( values, function( id, value ) {
+                                if ( api.has( id ) )
+                                        api( id ).set( value );
+                                else
+                                        api.create( id, value );
+                        });
</ins><span class="cx">                 });
</span><span class="cx"> 
</span><ins>+                preview.trigger( 'settings', api.settings.values );
+
</ins><span class="cx">                 preview.bind( 'setting', function( args ) {
</span><del>-                        var value = api( args.shift() );
-                        if ( value )
</del><ins>+                        var value;
+
+                        args = args.slice();
+
+                        if ( value = api( args.shift() ) )
</ins><span class="cx">                                 value.set.apply( value, args );
</span><span class="cx">                 });
</span><span class="cx"> 
</span><ins>+                preview.bind( 'sync', function( events ) {
+                        $.each( events, function( event, args ) {
+                                preview.trigger( event, args );
+                        });
+                        preview.send( 'synced' );
+                })
+
+                preview.send( 'ready' );
+
</ins><span class="cx">                 /* Custom Backgrounds */
</span><span class="cx">                 bg = $.map(['color', 'image', 'position_x', 'repeat', 'attachment'], function( prop ) {
</span><span class="cx">                         return 'background_' + prop;
</span></span></pre>
</div>
</div>

</body>
</html>