<!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>[36586] trunk: Customize: Add selective refresh framework with implementation for widgets and re-implementation for nav menus.</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 { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { 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" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/36586">36586</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/36586","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>westonruter</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-02-19 18:40:06 +0000 (Fri, 19 Feb 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Customize: Add selective refresh framework with implementation for widgets and re-implementation for nav menus.

See https://make.wordpress.org/core/2016/02/16/selective-refresh-in-the-customizer/.

Props westonruter, valendesigns, DrewAPicture, ocean90.
Fixes <a href="https://core.trac.wordpress.org/ticket/27355">#27355</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminjscustomizecontrolsjs">trunk/src/wp-admin/js/customize-controls.js</a></li>
<li><a href="#trunksrcwpadminjscustomizenavmenusjs">trunk/src/wp-admin/js/customize-nav-menus.js</a></li>
<li><a href="#trunksrcwpadminjscustomizewidgetsjs">trunk/src/wp-admin/js/customize-widgets.js</a></li>
<li><a href="#trunksrcwpcontentthemestwentythirteenjsthemecustomizerjs">trunk/src/wp-content/themes/twentythirteen/js/theme-customizer.js</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizenavmenusphp">trunk/src/wp-includes/class-wp-customize-nav-menus.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizewidgetsphp">trunk/src/wp-includes/class-wp-customize-widgets.php</a></li>
<li><a href="#trunksrcwpincludescsscustomizepreviewcss">trunk/src/wp-includes/css/customize-preview.css</a></li>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizenavmenuitemsettingphp">trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php</a></li>
<li><a href="#trunksrcwpincludesjscustomizepreviewnavmenusjs">trunk/src/wp-includes/js/customize-preview-nav-menus.js</a></li>
<li><a href="#trunksrcwpincludesjscustomizepreviewwidgetsjs">trunk/src/wp-includes/js/customize-preview-widgets.js</a></li>
<li><a href="#trunksrcwpincludesscriptloaderphp">trunk/src/wp-includes/script-loader.php</a></li>
<li><a href="#trunktestsphpunittestscustomizemanagerphp">trunk/tests/phpunit/tests/customize/manager.php</a></li>
<li><a href="#trunktestsphpunittestscustomizenavmenuitemsettingphp">trunk/tests/phpunit/tests/customize/nav-menu-item-setting.php</a></li>
<li><a href="#trunktestsphpunittestscustomizenavmenusphp">trunk/tests/phpunit/tests/customize/nav-menus.php</a></li>
<li><a href="#trunktestsphpunittestscustomizewidgetsphp">trunk/tests/phpunit/tests/customize/widgets.php</a></li>
<li><a href="#trunktestsqunitfixturescustomizemenusjs">trunk/tests/qunit/fixtures/customize-menus.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizepartialphp">trunk/src/wp-includes/customize/class-wp-customize-partial.php</a></li>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizeselectiverefreshphp">trunk/src/wp-includes/customize/class-wp-customize-selective-refresh.php</a></li>
<li><a href="#trunksrcwpincludesjscustomizeselectiverefreshjs">trunk/src/wp-includes/js/customize-selective-refresh.js</a></li>
<li><a href="#trunktestsphpunittestscustomizepartialphp">trunk/tests/phpunit/tests/customize/partial.php</a></li>
<li><a href="#trunktestsphpunittestscustomizeselectiverefreshajaxphp">trunk/tests/phpunit/tests/customize/selective-refresh-ajax.php</a></li>
<li><a href="#trunktestsphpunittestscustomizeselectiverefreshphp">trunk/tests/phpunit/tests/customize/selective-refresh.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminjscustomizecontrolsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-controls.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-controls.js       2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-admin/js/customize-controls.js 2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3786,6 +3786,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        });
</span><span class="cx" style="display: block; padding: 0 10px">                });
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Focus on the control that is associated with the given setting.
+               api.previewer.bind( 'focus-control-for-setting', function( settingId ) {
+                       var matchedControl;
+                       api.control.each( function( control ) {
+                               var settingIds = _.pluck( control.settings, 'id' );
+                               if ( -1 !== _.indexOf( settingIds, settingId ) ) {
+                                       matchedControl = control;
+                               }
+                       } );
+
+                       if ( matchedControl ) {
+                               matchedControl.focus();
+                       }
+               } );
+
+               // Refresh the preview when it requests.
+               api.previewer.bind( 'refresh', function() {
+                       api.previewer.refresh();
+               });
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 api.trigger( 'ready' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Make sure left column gets focus
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizenavmenusjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-nav-menus.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-nav-menus.js      2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-admin/js/customize-nav-menus.js        2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -19,7 +19,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        api.Menus.data = {
</span><span class="cx" style="display: block; padding: 0 10px">                itemTypes: [],
</span><span class="cx" style="display: block; padding: 0 10px">                l10n: {},
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                menuItemTransport: 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         settingTransport: 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                 phpIntMax: 0,
</span><span class="cx" style="display: block; padding: 0 10px">                defaultSettingValues: {
</span><span class="cx" style="display: block; padding: 0 10px">                        nav_menu: {},
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2310,7 +2310,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        customizeId = 'nav_menu_item[' + String( placeholderId ) + ']';
</span><span class="cx" style="display: block; padding: 0 10px">                        settingArgs = {
</span><span class="cx" style="display: block; padding: 0 10px">                                type: 'nav_menu_item',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                transport: 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         transport: api.Menus.data.settingTransport,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 previewer: api.previewer
</span><span class="cx" style="display: block; padding: 0 10px">                        };
</span><span class="cx" style="display: block; padding: 0 10px">                        setting = api.create( customizeId, customizeId, {}, settingArgs );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2399,7 +2399,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        // Register the menu control setting.
</span><span class="cx" style="display: block; padding: 0 10px">                        api.create( customizeId, customizeId, {}, {
</span><span class="cx" style="display: block; padding: 0 10px">                                type: 'nav_menu',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                transport: 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         transport: api.Menus.data.settingTransport,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 previewer: api.previewer
</span><span class="cx" style="display: block; padding: 0 10px">                        } );
</span><span class="cx" style="display: block; padding: 0 10px">                        api( customizeId ).set( $.extend(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2486,10 +2486,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                } );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                api.previewer.bind( 'refresh', function() {
-                       api.previewer.refresh();
-               });
-
</del><span class="cx" style="display: block; padding: 0 10px">                 // Open and focus menu control.
</span><span class="cx" style="display: block; padding: 0 10px">                api.previewer.bind( 'focus-nav-menu-item-control', api.Menus.focusMenuItemControl );
</span><span class="cx" style="display: block; padding: 0 10px">        } );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2535,7 +2531,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']';
</span><span class="cx" style="display: block; padding: 0 10px">                                newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
</span><span class="cx" style="display: block; padding: 0 10px">                                        type: 'nav_menu',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        transport: 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 transport: api.Menus.data.settingTransport,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         previewer: api.previewer
</span><span class="cx" style="display: block; padding: 0 10px">                                } );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2683,7 +2679,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                newCustomizeId = 'nav_menu_item[' + String( update.post_id ) + ']';
</span><span class="cx" style="display: block; padding: 0 10px">                                newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
</span><span class="cx" style="display: block; padding: 0 10px">                                        type: 'nav_menu_item',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        transport: 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 transport: api.Menus.data.settingTransport,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         previewer: api.previewer
</span><span class="cx" style="display: block; padding: 0 10px">                                } );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizewidgetsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-widgets.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-widgets.js        2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-admin/js/customize-widgets.js  2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -34,7 +34,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                multi_number: null,
</span><span class="cx" style="display: block; padding: 0 10px">                name: null,
</span><span class="cx" style="display: block; padding: 0 10px">                id_base: null,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                transport: 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                 params: [],
</span><span class="cx" style="display: block; padding: 0 10px">                width: null,
</span><span class="cx" style="display: block; padding: 0 10px">                height: null,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1982,7 +1982,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        isExistingWidget = api.has( settingId );
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! isExistingWidget ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                settingArgs = {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        transport: 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                                         previewer: this.setting.previewer
</span><span class="cx" style="display: block; padding: 0 10px">                                };
</span><span class="cx" style="display: block; padding: 0 10px">                                setting = api.create( settingId, settingId, '', settingArgs );
</span></span></pre></div>
<a id="trunksrcwpcontentthemestwentythirteenjsthemecustomizerjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-content/themes/twentythirteen/js/theme-customizer.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-content/themes/twentythirteen/js/theme-customizer.js 2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-content/themes/twentythirteen/js/theme-customizer.js   2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -38,4 +38,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                } );
</span><span class="cx" style="display: block; padding: 0 10px">        } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       if ( wp.customize.selectiveRefresh ) {
+               wp.customize.selectiveRefresh.bind( 'sidebar-updated', function( sidebarPartial ) {
+                       var widgetArea;
+                       if ( 'sidebar-1' === sidebarPartial.sidebarId && $.isFunction( $.fn.masonry ) ) {
+                               widgetArea = $( '#secondary .widget-area' );
+                               widgetArea.masonry( 'destroy' );
+                               widgetArea.masonry();
+                       }
+               } );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> } )( jQuery );
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-manager.php      2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/class-wp-customize-manager.php        2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -67,6 +67,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $nav_menus;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Methods and properties dealing with selective refresh in the Customizer preview.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var WP_Customize_Selective_Refresh
+        */
+       public $selective_refresh;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Registered instances of WP_Customize_Setting.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,7 +109,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @access protected
</span><span class="cx" style="display: block; padding: 0 10px">         * @var array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected $components = array( 'widgets', 'nav_menus' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected $components = array( 'widgets', 'nav_menus', 'selective_refresh' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Registered instances of WP_Customize_Section.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -249,15 +258,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $components = apply_filters( 'customize_loaded_components', $this->components, $this );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( in_array( 'widgets', $components ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( in_array( 'widgets', $components, true ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->widgets = new WP_Customize_Widgets( $this );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( in_array( 'nav_menus', $components ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               if ( in_array( 'nav_menus', $components, true ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->nav_menus = new WP_Customize_Nav_Menus( $this );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( in_array( 'selective_refresh', $components, true ) ) {
+                       require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
+                       $this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'setup_theme', array( $this, 'setup_theme' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1711,6 +1726,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'autofocus' => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'documentTitleTmpl' => $this->get_document_title_template(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'previewableDevices' => $this->get_previewable_devices(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'selectiveRefreshEnabled' => isset( $this->selective_refresh ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Prepare Customize Section objects to pass to JavaScript.
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizenavmenusphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-nav-menus.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-nav-menus.php    2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/class-wp-customize-nav-menus.php      2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -61,6 +61,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               // Selective Refresh partials.
+               add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -375,7 +378,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'reorderLabelOn'    => esc_attr__( 'Reorder menu items' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'reorderLabelOff'   => esc_attr__( 'Close reorder mode' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'menuItemTransport'    => 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'settingTransport'     => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'phpIntMax'            => PHP_INT_MAX,
</span><span class="cx" style="display: block; padding: 0 10px">                        'defaultSettingValues' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'nav_menu'      => $temp_nav_menu_setting->default,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -426,11 +429,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function filter_dynamic_setting_args( $setting_args, $setting_id ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $setting_args = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'type' => WP_Customize_Nav_Menu_Setting::TYPE,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'type'      => WP_Customize_Nav_Menu_Setting::TYPE,
+                               'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                         );
</span><span class="cx" style="display: block; padding: 0 10px">                } elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $setting_args = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'type'      => WP_Customize_Nav_Menu_Item_Setting::TYPE,
+                               'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                         );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                return $setting_args;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -515,7 +520,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $setting = $this->manager->get_setting( $setting_id );
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( $setting ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $setting->transport = 'postMessage';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $setting->transport = isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh';
</ins><span class="cx" style="display: block; padding: 0 10px">                                 remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
</span><span class="cx" style="display: block; padding: 0 10px">                                add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        } else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -523,7 +528,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'sanitize_callback' => array( $this, 'intval_base10' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'theme_supports'    => 'menus',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'              => 'theme_mod',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'transport'         => 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'transport'         => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'default'           => 0,
</span><span class="cx" style="display: block; padding: 0 10px">                                ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -549,7 +554,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id, array(
+                               'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
+                       ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Add the menu contents.
</span><span class="cx" style="display: block; padding: 0 10px">                        $menu_items = (array) wp_get_nav_menu_items( $menu_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -562,7 +569,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $value = (array) $item;
</span><span class="cx" style="display: block; padding: 0 10px">                                $value['nav_menu_term_id'] = $menu_id;
</span><span class="cx" style="display: block; padding: 0 10px">                                $this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id, array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'value' => $value,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'value'     => $value,
+                                       'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                // Create a control for each menu item.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -586,7 +594,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->manager->add_setting( 'new_menu_name', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type'      => 'new_menu',
</span><span class="cx" style="display: block; padding: 0 10px">                        'default'   => '',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'transport' => 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->manager->add_control( 'new_menu_name', array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -802,29 +810,39 @@
</span><span class="cx" style="display: block; padding: 0 10px">        <?php
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        //
</ins><span class="cx" style="display: block; padding: 0 10px">         // Start functionality specific to partial-refresh of menu changes in Customizer preview.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        const RENDER_AJAX_ACTION = 'customize_render_menu_partial';
-       const RENDER_NONCE_POST_KEY = 'render-menu-nonce';
-       const RENDER_QUERY_VAR = 'wp_customize_menu_render';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ //
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * The number of wp_nav_menu() calls which have happened in the preview.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Filters arguments for dynamic nav_menu selective refresh partials.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @since 4.3.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">          * @access public
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var int
-        */
-       public $preview_nav_menu_instance_number = 0;
-
-       /**
-        * Nav menu args used for each instance.
</del><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @since 4.3.0
-        * @access public
-        * @var array
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param array|false $partial_args Partial args.
+        * @param string      $partial_id   Partial ID.
+        * @return array Partial args
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public $preview_nav_menu_instance_args = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( preg_match( '/^nav_menu_instance\[[0-9a-f]{32}\]$/', $partial_id ) ) {
+                       if ( false === $partial_args ) {
+                               $partial_args = array();
+                       }
+                       $partial_args = array_merge(
+                               $partial_args,
+                               array(
+                                       'type'                => 'nav_menu_instance',
+                                       'render_callback'     => array( $this, 'render_nav_menu_partial' ),
+                                       'container_inclusive' => true,
+                               )
+                       );
+               }
+
+               return $partial_args;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Add hooks for the Customizer preview.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -832,13 +850,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function customize_preview_init() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_action( 'template_redirect', array( $this, 'render_menu' ) );
</del><span class="cx" style="display: block; padding: 0 10px">                 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               if ( ! isset( $_REQUEST[ self::RENDER_QUERY_VAR ] ) ) {
-                       add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
-                       add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
+               add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -846,53 +860,69 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.3.0
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         *
</del><span class="cx" style="display: block; padding: 0 10px">          * @see wp_nav_menu()
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @see WP_Customize_Widgets_Partial_Refresh::filter_dynamic_sidebar_params()
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array $args An array containing wp_nav_menu() arguments.
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array Arguments.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function filter_wp_nav_menu_args( $args ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->preview_nav_menu_instance_number += 1;
-               $args['instance_number'] = $this->preview_nav_menu_instance_number;
-
-               $can_partial_refresh = (
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /*
+                * The following conditions determine whether or not this instance of
+                * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be
+                * selective refreshed if...
+                */
+               $can_selective_refresh = (
+                       // ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated),
</ins><span class="cx" style="display: block; padding: 0 10px">                         ! empty( $args['echo'] )
</span><span class="cx" style="display: block; padding: 0 10px">                        &&
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // ...and if the fallback_cb can be serialized to JSON, since it will be included in the placement context data,
</ins><span class="cx" style="display: block; padding: 0 10px">                         ( empty( $args['fallback_cb'] ) || is_string( $args['fallback_cb'] ) )
</span><span class="cx" style="display: block; padding: 0 10px">                        &&
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // ...and if the walker can also be serialized to JSON, since it will be included in the placement context data as well,
</ins><span class="cx" style="display: block; padding: 0 10px">                         ( empty( $args['walker'] ) || is_string( $args['walker'] ) )
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        &&
-                       (
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // ...and if it has a theme location assigned or an assigned menu to display,
+                       && (
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ! empty( $args['theme_location'] )
</span><span class="cx" style="display: block; padding: 0 10px">                                ||
</span><span class="cx" style="display: block; padding: 0 10px">                                ( ! empty( $args['menu'] ) && ( is_numeric( $args['menu'] ) || is_object( $args['menu'] ) ) )
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        &&
+                       // ...and if the nav menu would be rendered with a wrapper container element (upon which to attach data-* attributes).
+                       (
+                               ! empty( $args['container'] )
+                               ||
+                               ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) )
+                       )
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $args['can_partial_refresh'] = $can_partial_refresh;
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $hashed_args = $args;
-
-               if ( ! $can_partial_refresh ) {
-                       $hashed_args['fallback_cb'] = '';
-                       $hashed_args['walker'] = '';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $can_selective_refresh ) {
+                       return $args;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Replace object menu arg with a term_id menu arg, as this exports better to JS and is easier to compare hashes.
-               if ( ! empty( $hashed_args['menu'] ) && is_object( $hashed_args['menu'] ) ) {
-                       $hashed_args['menu'] = $hashed_args['menu']->term_id;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $exported_args = $args;
+
+               /*
+                * Replace object menu arg with a term_id menu arg, as this exports better
+                * to JS and is easier to compare hashes.
+                */
+               if ( ! empty( $exported_args['menu'] ) && is_object( $exported_args['menu'] ) ) {
+                       $exported_args['menu'] = $exported_args['menu']->term_id;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                ksort( $hashed_args );
-               $hashed_args['args_hash'] = $this->hash_nav_menu_args( $hashed_args );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         ksort( $exported_args );
+               $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->preview_nav_menu_instance_args[ $this->preview_nav_menu_instance_number ] = $hashed_args;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $args['customize_preview_nav_menus_args'] = $exported_args;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $args;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Prepare wp_nav_menu() calls for partial refresh. Wraps output in container for refreshing.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Prepares wp_nav_menu() calls for partial refresh.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Injects attributes into container element.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 4.3.0
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -903,29 +933,29 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return null
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function filter_wp_nav_menu( $nav_menu_content, $args ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! empty( $args->can_partial_refresh ) && ! empty( $args->instance_number ) ) {
-                       $nav_menu_content = preg_replace(
-                               '/(?<=class=")/',
-                               sprintf( 'partial-refreshable-nav-menu partial-refreshable-nav-menu-%1$d ', $args->instance_number ),
-                               $nav_menu_content,
-                               1 // Only update the class on the first element found, the menu container.
-                       );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! empty( $args->customize_preview_nav_menus_args ) ) {
+                       $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) );
+                       $attributes .= ' data-customize-partial-type="nav_menu_instance"';
+                       $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $args->customize_preview_nav_menus_args ) ) );
+                       $nav_menu_content = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $nav_menu_content, 1 );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">                return $nav_menu_content;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Hash (hmac) the arguments with the nonce and secret auth key to ensure they
-        * are not tampered with when submitted in the Ajax request.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Hashes (hmac) the nav menu arguments to ensure they are not tampered with when
+        * submitted in the Ajax request.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Note that the array is expected to be pre-sorted.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 4.3.0
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array $args The arguments to hash.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @return string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @return string Hashed nav menu arguments.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function hash_nav_menu_args( $args ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return wp_hash( wp_create_nonce( self::RENDER_AJAX_ACTION ) . serialize( $args ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return wp_hash( serialize( $args ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -935,32 +965,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function customize_preview_enqueue_deps() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                wp_enqueue_script( 'customize-preview-nav-menus' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( isset( $this->manager->selective_refresh ) ) {
+                       $script = wp_scripts()->registered['customize-preview-nav-menus'];
+                       $script->deps[] = 'customize-selective-refresh';
+               }
+
+               wp_enqueue_script( 'customize-preview-nav-menus' ); // Note that we have overridden this.
</ins><span class="cx" style="display: block; padding: 0 10px">                 wp_enqueue_style( 'customize-preview' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               add_action( 'wp_print_footer_scripts', array( $this, 'export_preview_data' ) );
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Export data from PHP to JS.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Exports data from PHP to JS.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @deprecated 4.5.0 Obsolete
</ins><span class="cx" style="display: block; padding: 0 10px">          * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function export_preview_data() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               // Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
-               $exports = array(
-                       'renderQueryVar'        => self::RENDER_QUERY_VAR,
-                       'renderNonceValue'      => wp_create_nonce( self::RENDER_AJAX_ACTION ),
-                       'renderNoncePostKey'    => self::RENDER_NONCE_POST_KEY,
-                       'navMenuInstanceArgs'   => $this->preview_nav_menu_instance_args,
-                       'l10n'                  => array(
-                               'editNavMenuItemTooltip' => __( 'Shift-click to edit this menu item.' ),
-                       ),
-               );
-
-               printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         _deprecated_function( __METHOD__, '4.5.0' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -970,49 +992,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @see wp_nav_menu()
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @param WP_Customize_Partial $partial       Partial.
+        * @param array                $nav_menu_args Nav menu args supplied as container context.
+        * @return string|false
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function render_menu() {
-               if ( empty( $_POST[ self::RENDER_QUERY_VAR ] ) ) {
-                       return;
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function render_nav_menu_partial( $partial, $nav_menu_args ) {
+               unset( $partial );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->manager->remove_preview_signature();
-
-               if ( empty( $_POST[ self::RENDER_NONCE_POST_KEY ] ) ) {
-                       wp_send_json_error( 'missing_nonce_param' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! isset( $nav_menu_args['args_hmac'] ) ) {
+                       // Error: missing_args_hmac.
+                       return false;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! is_customize_preview() ) {
-                       wp_send_json_error( 'expected_customize_preview' );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $nav_menu_args_hmac = $nav_menu_args['args_hmac'];
+               unset( $nav_menu_args['args_hmac'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! check_ajax_referer( self::RENDER_AJAX_ACTION, self::RENDER_NONCE_POST_KEY, false ) ) {
-                       wp_send_json_error( 'nonce_check_fail' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         ksort( $nav_menu_args );
+               if ( ! hash_equals( $this->hash_nav_menu_args( $nav_menu_args ), $nav_menu_args_hmac ) ) {
+                       // Error: args_hmac_mismatch.
+                       return false;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! current_user_can( 'edit_theme_options' ) ) {
-                       wp_send_json_error( 'unauthorized' );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         ob_start();
+               wp_nav_menu( $nav_menu_args );
+               $content = ob_get_clean();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! isset( $_POST['wp_nav_menu_args'] ) ) {
-                       wp_send_json_error( 'missing_param' );
-               }
-
-               if ( ! isset( $_POST['wp_nav_menu_args_hash'] ) ) {
-                       wp_send_json_error( 'missing_param' );
-               }
-
-               $wp_nav_menu_args = json_decode( wp_unslash( $_POST['wp_nav_menu_args'] ), true );
-               if ( ! is_array( $wp_nav_menu_args ) ) {
-                       wp_send_json_error( 'wp_nav_menu_args_not_array' );
-               }
-
-               $wp_nav_menu_args_hash = sanitize_text_field( wp_unslash( $_POST['wp_nav_menu_args_hash'] ) );
-               if ( ! hash_equals( $this->hash_nav_menu_args( $wp_nav_menu_args ), $wp_nav_menu_args_hash ) ) {
-                       wp_send_json_error( 'wp_nav_menu_args_hash_mismatch' );
-               }
-
-               $wp_nav_menu_args['echo'] = false;
-               wp_send_json_success( wp_nav_menu( $wp_nav_menu_args ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $content;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizewidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-widgets.php      2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/class-wp-customize-widgets.php        2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,6 +100,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'dynamic_sidebar',                         array( $this, 'tally_rendered_widgets' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'is_active_sidebar',                       array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'dynamic_sidebar_has_widgets',             array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               // Selective Refresh.
+               add_filter( 'customize_dynamic_partial_args',          array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
+               add_action( 'customize_preview_init',                  array( $this, 'selective_refresh_init' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -682,6 +686,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'widgetReorderNav' => $widget_reorder_nav_tpl,
</span><span class="cx" style="display: block; padding: 0 10px">                                'moveWidgetArea'   => $move_widget_area_tpl,
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'selectiveRefresh'     => isset( $this->manager->selective_refresh ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -762,7 +767,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type'       => 'option',
</span><span class="cx" style="display: block; padding: 0 10px">                        'capability' => 'edit_theme_options',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'transport'  => 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'transport'  => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'default'    => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -884,7 +889,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
</span><span class="cx" style="display: block; padding: 0 10px">                                'is_disabled'  => $is_disabled,
</span><span class="cx" style="display: block; padding: 0 10px">                                'id_base'      => $id_base,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'transport'    => 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'transport'    => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
</span><span class="cx" style="display: block; padding: 0 10px">                                'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
</span><span class="cx" style="display: block; padding: 0 10px">                                'is_wide'      => $this->is_wide_widget( $widget['id'] ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1061,8 +1066,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'registeredSidebars' => array_values( $wp_registered_sidebars ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'registeredWidgets'  => $wp_registered_widgets,
</span><span class="cx" style="display: block; padding: 0 10px">                        'l10n'               => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'widgetTooltip'  => __( 'Shift-click to edit this widget.' ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'selectiveRefresh'   => isset( $this->manager->selective_refresh ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        unset( $registered_widget['callback'] ); // may not be JSON-serializeable
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1459,11 +1465,327 @@
</span><span class="cx" style="display: block; padding: 0 10px">                wp_send_json_success( compact( 'form', 'instance' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /***************************************************************************
-        * Option Update Capturing
-        ***************************************************************************/
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+        * Selective Refresh Methods
+        */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Filter args for dynamic widget partials.
+        *
+        * @since 4.5.0
+        *
+        * @param array|false $partial_args Partial args.
+        * @param string      $partial_id  Partial ID.
+        * @return array Partial args
+        */
+       public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
+
+               if ( preg_match( '/^widget\[.+\]$/', $partial_id ) ) {
+                       if ( false === $partial_args ) {
+                               $partial_args = array();
+                       }
+                       $partial_args = array_merge(
+                               $partial_args,
+                               array(
+                                       'type' => 'widget',
+                                       'render_callback' => array( $this, 'render_widget_partial' ),
+                                       'container_inclusive' => true,
+                               )
+                       );
+               }
+
+               return $partial_args;
+       }
+
+       /**
+        * Add hooks for selective refresh.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function selective_refresh_init() {
+               if ( ! isset( $this->manager->selective_refresh ) ) {
+                       return;
+               }
+
+               add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
+               add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
+               add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
+               add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
+               add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
+       }
+
+       /**
+        * Enqueue scripts for the Customizer preview.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function customize_preview_enqueue_deps() {
+               if ( isset( $this->manager->selective_refresh ) ) {
+                       $script = wp_scripts()->registered['customize-preview-widgets'];
+                       $script->deps[] = 'customize-selective-refresh';
+               }
+
+               wp_enqueue_script( 'customize-preview-widgets' );
+               wp_enqueue_style( 'customize-preview' );
+       }
+
+       /**
+        * Inject selective refresh data attributes into widget container elements.
+        *
+        * @param array $params {
+        *     Dynamic sidebar params.
+        *
+        *     @type array $args        Sidebar args.
+        *     @type array $widget_args Widget args.
+        * }
+        * @see WP_Customize_Nav_Menus_Partial_Refresh::filter_wp_nav_menu_args()
+        *
+        * @return array Params.
+        */
+       public function filter_dynamic_sidebar_params( $params ) {
+               $sidebar_args = array_merge(
+                       array(
+                               'before_widget' => '',
+                               'after_widget' => '',
+                       ),
+                       $params[0]
+               );
+
+               // Skip widgets not in a registered sidebar or ones which lack a proper wrapper element to attach the data-* attributes to.
+               $matches = array();
+               $is_valid = (
+                       isset( $sidebar_args['id'] )
+                       &&
+                       is_registered_sidebar( $sidebar_args['id'] )
+                       &&
+                       ( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
+                       &&
+                       preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
+               );
+               if ( ! $is_valid ) {
+                       return $params;
+               }
+               $this->before_widget_tags_seen[ $matches['tag_name'] ] = true;
+
+               $context = array(
+                       'sidebar_id' => $sidebar_args['id'],
+               );
+               if ( isset( $this->context_sidebar_instance_number ) ) {
+                       $context['sidebar_instance_number'] = $this->context_sidebar_instance_number;
+               } else if ( isset( $sidebar_args['id'] ) && isset( $this->sidebar_instance_count[ $sidebar_args['id'] ] ) ) {
+                       $context['sidebar_instance_number'] = $this->sidebar_instance_count[ $sidebar_args['id'] ];
+               }
+
+               $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'widget[' . $sidebar_args['widget_id'] . ']' ) );
+               $attributes .= ' data-customize-partial-type="widget"';
+               $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $context ) ) );
+               $attributes .= sprintf( ' data-customize-widget-id="%s"', esc_attr( $sidebar_args['widget_id'] ) );
+               $sidebar_args['before_widget'] = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $sidebar_args['before_widget'] );
+
+               $params[0] = $sidebar_args;
+               return $params;
+       }
+
+       /**
+        * List of the tag names seen for before_widget strings.
+        *
+        * This is used in the filter_wp_kses_allowed_html filter to ensure that the
+        * data-* attributes can be whitelisted.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var array
+        */
+       protected $before_widget_tags_seen = array();
+
+       /**
+        * Ensure that the HTML data-* attributes for selective refresh are allowed by kses.
+        *
+        * This is needed in case the $before_widget is run through wp_kses() when printed.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param array $allowed_html Allowed HTML.
+        * @return array Allowed HTML.
+        */
+       public function filter_wp_kses_allowed_data_attributes( $allowed_html ) {
+               foreach ( array_keys( $this->before_widget_tags_seen ) as $tag_name ) {
+                       if ( ! isset( $allowed_html[ $tag_name ] ) ) {
+                               $allowed_html[ $tag_name ] = array();
+                       }
+                       $allowed_html[ $tag_name ] = array_merge(
+                               $allowed_html[ $tag_name ],
+                               array_fill_keys( array(
+                                       'data-customize-partial-id',
+                                       'data-customize-partial-type',
+                                       'data-customize-partial-placement-context',
+                                       'data-customize-partial-widget-id',
+                                       'data-customize-partial-options',
+                               ), true )
+                       );
+               }
+               return $allowed_html;
+       }
+
+       /**
+        * Keep track of the number of times that dynamic_sidebar() was called for a given sidebar index.
+        *
+        * This helps facilitate the uncommon scenario where a single sidebar is rendered multiple times on a template.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var array
+        */
+       protected $sidebar_instance_count = array();
+
+       /**
+        * The current request's sidebar_instance_number context.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var int
+        */
+       protected $context_sidebar_instance_number;
+
+       /**
+        * Current sidebar ID being rendered.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var array
+        */
+       protected $current_dynamic_sidebar_id_stack = array();
+
+       /**
+        * Start keeping track of the current sidebar being rendered.
+        *
+        * Insert marker before widgets are rendered in a dynamic sidebar.
+        *
+        * @since 4.5.0
+        *
+        * @param int|string $index Index, name, or ID of the dynamic sidebar.
+        */
+       public function start_dynamic_sidebar( $index ) {
+               array_unshift( $this->current_dynamic_sidebar_id_stack, $index );
+               if ( ! isset( $this->sidebar_instance_count[ $index ] ) ) {
+                       $this->sidebar_instance_count[ $index ] = 0;
+               }
+               $this->sidebar_instance_count[ $index ] += 1;
+               if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
+                       printf( "\n<!--dynamic_sidebar_before:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
+               }
+       }
+
+       /**
+        * Finish keeping track of the current sidebar being rendered.
+        *
+        * Insert marker after widgets are rendered in a dynamic sidebar.
+        *
+        * @since 4.5.0
+        *
+        * @param int|string $index Index, name, or ID of the dynamic sidebar.
+        */
+       public function end_dynamic_sidebar( $index ) {
+               if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
+                       printf( "\n<!--dynamic_sidebar_after:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
+               }
+       }
+
+       /**
+        * Current sidebar being rendered.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var string
+        */
+       protected $rendering_widget_id;
+
+       /**
+        * Current widget being rendered.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var string
+        */
+       protected $rendering_sidebar_id;
+
+       /**
+        * Filter sidebars_widgets to ensure the currently-rendered widget is the only widget in the current sidebar.
+        *
+        * @since 4.5.0
+        * @access private
+        *
+        * @param array $sidebars_widgets Sidebars widgets.
+        * @return array Sidebars widgets.
+        */
+       public function filter_sidebars_widgets_for_rendering_widget( $sidebars_widgets ) {
+               $sidebars_widgets[ $this->rendering_sidebar_id ] = array( $this->rendering_widget_id );
+               return $sidebars_widgets;
+       }
+
+       /**
+        * Render a specific widget using the supplied sidebar arguments.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @see dynamic_sidebar()
+        *
+        * @param WP_Customize_Partial $partial      Partial.
+        * @param array                $context {
+        *     Sidebar args supplied as container context.
+        *
+        *     @type string $sidebar_id                ID for sidebar for widget to render into.
+        *     @type int    [$sidebar_instance_number] Disambiguating instance number.
+        * }
+        * @return string|false
+        */
+       public function render_widget_partial( $partial, $context ) {
+               $id_data   = $partial->id_data();
+               $widget_id = array_shift( $id_data['keys'] );
+
+               if ( ! is_array( $context )
+                       || empty( $context['sidebar_id'] )
+                       || ! is_registered_sidebar( $context['sidebar_id'] )
+               ) {
+                       return false;
+               }
+
+               $this->rendering_sidebar_id = $context['sidebar_id'];
+
+               if ( isset( $context['sidebar_instance_number'] ) ) {
+                       $this->context_sidebar_instance_number = intval( $context['sidebar_instance_number'] );
+               }
+
+               // Filter sidebars_widgets so that only the queried widget is in the sidebar.
+               $this->rendering_widget_id = $widget_id;
+
+               $filter_callback = array( $this, 'filter_sidebars_widgets_for_rendering_widget' );
+               add_filter( 'sidebars_widgets', $filter_callback, 1000 );
+
+               // Render the widget.
+               ob_start();
+               dynamic_sidebar( $this->rendering_sidebar_id = $context['sidebar_id'] );
+               $container = ob_get_clean();
+
+               // Reset variables for next partial render.
+               remove_filter( 'sidebars_widgets', $filter_callback, 1000 );
+
+               $this->context_sidebar_instance_number = null;
+               $this->rendering_sidebar_id = null;
+               $this->rendering_widget_id = null;
+
+               return $container;
+       }
+
+       //
+       // Option Update Capturing
+       //
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * List of captured widget option updates.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1611,7 +1933,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( array_keys( $this->_captured_options ) as $option_name ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
</span></span></pre></div>
<a id="trunksrcwpincludescsscustomizepreviewcss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/css/customize-preview.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/css/customize-preview.css   2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/css/customize-preview.css     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,3 +4,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">        transition: opacity 0.25s;
</span><span class="cx" style="display: block; padding: 0 10px">        cursor: progress;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/* Override highlight when refreshing */
+.customize-partial-refreshing.widget-customizer-highlighted-widget {
+       -webkit-box-shadow: none;
+       box-shadow: none;
+}
+
+.customize-render-content-error {
+       outline: solid 1px red;
+}
+.customize-render-content-error-message {
+       display: block;
+       padding: 1em;
+       background-color: #FFCCCC;
+}
</ins></span></pre></div>
<a id="trunksrcwpincludescustomizeclasswpcustomizenavmenuitemsettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php      2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php        2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -67,10 +67,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Default transport.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 4.5.0 Default changed to 'refresh'
</ins><span class="cx" style="display: block; padding: 0 10px">          * @access public
</span><span class="cx" style="display: block; padding: 0 10px">         * @var string
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public $transport = 'postMessage';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public $transport = 'refresh';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * The post ID represented by this setting instance. This is the db_id.
</span></span></pre></div>
<a id="trunksrcwpincludescustomizeclasswpcustomizepartialphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/customize/class-wp-customize-partial.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/customize/class-wp-customize-partial.php                            (rev 0)
+++ trunk/src/wp-includes/customize/class-wp-customize-partial.php      2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,288 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WordPress Customize Partial class
+ *
+ * @package WordPress
+ * @subpackage Customize
+ * @since 4.5.0
+ */
+
+/**
+ * Customize Partial class.
+ *
+ * Representation of a rendered region in the previewed page that gets
+ * selectively refreshed when an associated setting is changed.
+ * This class is analogous of WP_Customize_Control.
+ *
+ * @since 4.5.0
+ */
+class WP_Customize_Partial {
+
+       /**
+        * Component.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var WP_Customize_Selective_Refresh
+        */
+       public $component;
+
+       /**
+        * Unique identifier for the partial.
+        *
+        * If the partial is used to display a single setting, this would generally
+        * be the same as the associated setting's ID.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var string
+        */
+       public $id;
+
+       /**
+        * Parsed ID.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var array {
+        *     @type string $base ID base.
+        *     @type array  $keys Keys for multidimensional.
+        * }
+        */
+       protected $id_data = array();
+
+       /**
+        * Type of this partial.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var string
+        */
+       public $type = 'default';
+
+       /**
+        * The jQuery selector to find the container element for the partial.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var string
+        */
+       public $selector;
+
+       /**
+        * All settings tied to the partial.
+        *
+        * @access public
+        * @since 4.5.0
+        * @var WP_Customize_Setting[]
+        */
+       public $settings;
+
+       /**
+        * The ID for the setting that this partial is primarily responsible for rendering.
+        *
+        * If not supplied, it will default to the ID of the first setting.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var string
+        */
+       public $primary_setting;
+
+       /**
+        * Render callback.
+        *
+        * @since 4.5.0
+        * @access public
+        * @see WP_Customize_Partial::render()
+        * @var callable Callback is called with one argument, the instance of
+        *                 WP_Customize_Partial. The callback can either echo the
+        *                 partial or return the partial as a string, or return false if error.
+        */
+       public $render_callback;
+
+       /**
+        * Whether the container element is included in the partial, or if only the contents are rendered.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var bool
+        */
+       public $container_inclusive = false;
+
+       /**
+        * Whether to refresh the entire preview in case a partial cannot be refreshed.
+        *
+        * A partial render is considered a failure if the render_callback returns false.
+        *
+        * @since 4.5.0
+        * @access public
+        * @var bool
+        */
+       public $fallback_refresh = true;
+
+       /**
+        * Constructor.
+        *
+        * Supplied `$args` override class property defaults.
+        *
+        * If `$args['settings']` is not defined, use the $id as the setting ID.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param WP_Customize_Selective_Refresh $component Customize Partial Refresh plugin instance.
+        * @param string                         $id        Control ID.
+        * @param array                          $args      {
+        *     Optional. Arguments to override class property defaults.
+        *
+        *     @type array|string $settings All settings IDs tied to the partial. If undefined, `$id` will be used.
+        * }
+        */
+       public function __construct( WP_Customize_Selective_Refresh $component, $id, $args = array() ) {
+               $keys = array_keys( get_object_vars( $this ) );
+               foreach ( $keys as $key ) {
+                       if ( isset( $args[ $key ] ) ) {
+                               $this->$key = $args[ $key ];
+                       }
+               }
+
+               $this->component       = $component;
+               $this->id              = $id;
+               $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
+               $this->id_data['base'] = array_shift( $this->id_data['keys'] );
+
+               if ( empty( $this->render_callback ) ) {
+                       $this->render_callback = array( $this, 'render_callback' );
+               }
+
+               // Process settings.
+               if ( empty( $this->settings ) ) {
+                       $this->settings = array( $id );
+               } else if ( is_string( $this->settings ) ) {
+                       $this->settings = array( $this->settings );
+               }
+
+               if ( empty( $this->primary_setting ) ) {
+                       $this->primary_setting = current( $this->settings );
+               }
+       }
+
+       /**
+        * Retrieves parsed ID data for multidimensional setting.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @return array {
+        *     ID data for multidimensional partial.
+        *
+        *     @type string $base ID base.
+        *     @type array  $keys Keys for multidimensional array.
+        * }
+        */
+       final public function id_data() {
+               return $this->id_data;
+       }
+
+       /**
+        * Renders the template partial involving the associated settings.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param array $container_context Optional. Array of context data associated with the target container (placement).
+        *                                 Default empty array.
+        * @return string|array|false The rendered partial as a string, raw data array (for client-side JS template),
+        *                            or false if no render applied.
+        */
+       final public function render( $container_context = array() ) {
+               $partial  = $this;
+               $rendered = false;
+
+               if ( ! empty( $this->render_callback ) ) {
+                       ob_start();
+                       $return_render = call_user_func( $this->render_callback, $this, $container_context );
+                       $ob_render = ob_get_clean();
+
+                       if ( null !== $return_render && '' !== $ob_render ) {
+                               _doing_it_wrong( __FUNCTION__, __( 'Partial render must echo the content or return the content string (or array), but not both.' ), '4.5.0' );
+                       }
+
+                       /*
+                        * Note that the string return takes precedence because the $ob_render may just\
+                        * include PHP warnings or notices.
+                        */
+                       $rendered = null !== $return_render ? $return_render : $ob_render;
+               }
+
+               /**
+                * Filters partial rendering.
+                *
+                * @since 4.5.0
+                *
+                * @param string|array|false   $rendered          The partial value. Default false.
+                * @param WP_Customize_Partial $partial           WP_Customize_Setting instance.
+                * @param array                $container_context Optional array of context data associated with
+                *                                                the target container.
+                */
+               $rendered = apply_filters( 'customize_partial_render', $rendered, $partial, $container_context );
+
+               /**
+                * Filters partial rendering for a specific partial.
+                *
+                * The dynamic portion of the hook name, `$partial->ID` refers to the partial ID.
+                *
+                * @since 4.5.0
+                *
+                * @param string|array|false   $rendered          The partial value. Default false.
+                * @param WP_Customize_Partial $partial           WP_Customize_Setting instance.
+                * @param array                $container_context Optional array of context data associated with
+                *                                                the target container.
+                */
+               $rendered = apply_filters( "customize_partial_render_{$partial->id}", $rendered, $partial, $container_context );
+
+               return $rendered;
+       }
+
+       /**
+        * Default callback used when invoking WP_Customize_Control::render().
+        *
+        * Note that this method may echo the partial *or* return the partial as
+        * a string or array, but not both. Output buffering is performed when this
+        * is called. Subclasses can override this with their specific logic, or they
+        * may provide an 'render_callback' argument to the constructor.
+        *
+        * This method may return an HTML string for straight DOM injection, or it
+        * may return an array for supporting Partial JS subclasses to render by
+        * applying to client-side templating.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @return string|array|false
+        */
+       public function render_callback() {
+               return false;
+       }
+
+       /**
+        * Retrieves the data to export to the client via JSON.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @return array Array of parameters passed to the JavaScript.
+        */
+       public function json() {
+               $exports = array(
+                       'settings'           => $this->settings,
+                       'primarySetting'     => $this->primary_setting,
+                       'selector'           => $this->selector,
+                       'type'               => $this->type,
+                       'fallbackRefresh'    => $this->fallback_refresh,
+                       'containerInclusive' => $this->container_inclusive,
+               );
+               return $exports;
+       }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludescustomizeclasswpcustomizeselectiverefreshphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/customize/class-wp-customize-selective-refresh.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/customize/class-wp-customize-selective-refresh.php                          (rev 0)
+++ trunk/src/wp-includes/customize/class-wp-customize-selective-refresh.php    2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,437 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WordPress Customize Selective Refresh class
+ *
+ * @package WordPress
+ * @subpackage Customize
+ * @since 4.5.0
+ */
+
+/**
+ * WordPress Customize Selective Refresh class.
+ *
+ * @since 4.5.0
+ */
+class WP_Customize_Selective_Refresh {
+
+       /**
+        * Query var used in requests to render partials.
+        *
+        * @since 4.5.0
+        */
+       const RENDER_QUERY_VAR = 'wp_customize_render_partials';
+
+       /**
+        * Customize manager.
+        *
+        * @var WP_Customize_Manager
+        */
+       public $manager;
+
+       /**
+        * Registered instances of WP_Customize_Partial.
+        *
+        * @since 4.5.0
+        * @access protected
+        * @var WP_Customize_Partial[]
+        */
+       protected $partials = array();
+
+       /**
+        * Log of errors triggered when partials are rendered.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var array
+        */
+       protected $triggered_errors = array();
+
+       /**
+        * Keep track of the current partial being rendered.
+        *
+        * @since 4.5.0
+        * @access private
+        * @var string
+        */
+       protected $current_partial_id;
+
+       /**
+        * Plugin bootstrap for Partial Refresh functionality.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param WP_Customize_Manager $manager Manager instance.
+        */
+       public function __construct( WP_Customize_Manager $manager ) {
+               $this->manager = $manager;
+               require_once( ABSPATH . WPINC . '/customize/class-wp-customize-partial.php' );
+
+               add_action( 'customize_preview_init', array( $this, 'init_preview' ) );
+       }
+
+       /**
+        * Retrieves the registered partials.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @return array Partials.
+        */
+       public function partials() {
+               return $this->partials;
+       }
+
+       /**
+        * Adds a partial.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param WP_Customize_Partial|string $id   Customize Partial object, or Panel ID.
+        * @param array                       $args Optional. Partial arguments. Default empty array.
+        * @return WP_Customize_Partial             The instance of the panel that was added.
+        */
+       public function add_partial( $id, $args = array() ) {
+               if ( $id instanceof WP_Customize_Partial ) {
+                       $partial = $id;
+               } else {
+                       $class = 'WP_Customize_Partial';
+
+                       /** This filter (will be) documented in wp-includes/class-wp-customize-manager.php */
+                       $args = apply_filters( 'customize_dynamic_partial_args', $args, $id );
+
+                       /** This filter (will be) documented in wp-includes/class-wp-customize-manager.php */
+                       $class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args );
+
+                       $partial = new $class( $this, $id, $args );
+               }
+
+               $this->partials[ $partial->id ] = $partial;
+               return $partial;
+       }
+
+       /**
+        * Retrieves a partial.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param string $id Customize Partial ID.
+        * @return WP_Customize_Partial|null The partial, if set. Otherwise null.
+        */
+       public function get_partial( $id ) {
+               if ( isset( $this->partials[ $id ] ) ) {
+                       return $this->partials[ $id ];
+               } else {
+                       return null;
+               }
+       }
+
+       /**
+        * Removes a partial.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @param string $id Customize Partial ID.
+        */
+       public function remove_partial( $id ) {
+               unset( $this->partials[ $id ] );
+       }
+
+       /**
+        * Initializes the Customizer preview.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function init_preview() {
+               add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) );
+               add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
+       }
+
+       /**
+        * Enqueues preview scripts.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function enqueue_preview_scripts() {
+               wp_enqueue_script( 'customize-selective-refresh' );
+               add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 );
+       }
+
+       /**
+        * Exports data in preview after it has finished rendering so that partials can be added at runtime.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function export_preview_data() {
+               $partials = array();
+
+               foreach ( $this->partials() as $partial ) {
+                       $partials[ $partial->id ] = $partial->json();
+               }
+
+               $exports = array(
+                       'partials'       => $partials,
+                       'renderQueryVar' => self::RENDER_QUERY_VAR,
+                       'l10n'           => array(
+                               'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
+                               /* translators: %s: message from JS error */
+                               'errorMessageTpl'  => __( 'Script error: %s' ),
+                               /* translators: %s: document.write() */
+                               'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ),
+                       ),
+               );
+
+               // Export data to JS.
+               echo sprintf( '<script>var _customizePartialRefreshExports = %s;</script>', wp_json_encode( $exports ) );
+       }
+
+       /**
+        * Registers dynamically-created partials.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @see WP_Customize_Manager::add_dynamic_settings()
+        *
+        * @param array $partial_ids The partial ID to add.
+        * @return array Added WP_Customize_Partial instances.
+        */
+       public function add_dynamic_partials( $partial_ids ) {
+               $new_partials = array();
+
+               foreach ( $partial_ids as $partial_id ) {
+
+                       // Skip partials already created.
+                       $partial = $this->get_partial( $partial_id );
+                       if ( $partial ) {
+                               continue;
+                       }
+
+                       $partial_args = false;
+                       $partial_class = 'WP_Customize_Partial';
+
+                       /**
+                        * Filters a dynamic partial's constructor arguments.
+                        *
+                        * For a dynamic partial to be registered, this filter must be employed
+                        * to override the default false value with an array of args to pass to
+                        * the WP_Customize_Partial constructor.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
+                        * @param string      $partial_id   ID for dynamic partial.
+                        */
+                       $partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id );
+                       if ( false === $partial_args ) {
+                               continue;
+                       }
+
+                       /**
+                        * Filters the class used to construct partials.
+                        *
+                        * Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @param string $partial_class WP_Customize_Partial or a subclass.
+                        * @param string $partial_id    ID for dynamic partial.
+                        * @param array  $partial_args  The arguments to the WP_Customize_Partial constructor.
+                        */
+                       $partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args );
+
+                       $partial = new $partial_class( $this, $partial_id, $partial_args );
+
+                       $this->add_partial( $partial );
+                       $new_partials[] = $partial;
+               }
+               return $new_partials;
+       }
+
+       /**
+        * Checks whether the request is for rendering partials.
+        *
+        * Note that this will not consider whether the request is authorized or valid,
+        * just that essentially the route is a match.
+        *
+        * @since 4.5.0
+        * @access public
+        *
+        * @return bool Whether the request is for rendering partials.
+        */
+       public function is_render_partials_request() {
+               return ! empty( $_POST[ self::RENDER_QUERY_VAR ] );
+       }
+
+       /**
+        * Handles PHP errors triggered during rendering the partials.
+        *
+        * These errors will be relayed back to the client in the Ajax response.
+        *
+        * @since 4.5.0
+        * @access private
+        *
+        * @param int    $errno   Error number.
+        * @param string $errstr  Error string.
+        * @param string $errfile Error file.
+        * @param string $errline Error line.
+        * @return true Always true.
+        */
+       public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) {
+               $this->triggered_errors[] = array(
+                       'partial'      => $this->current_partial_id,
+                       'error_number' => $errno,
+                       'error_string' => $errstr,
+                       'error_file'   => $errfile,
+                       'error_line'   => $errline,
+               );
+               return true;
+       }
+
+       /**
+        * Handles the Ajax request to return the rendered partials for the requested placements.
+        *
+        * @since 4.5.0
+        * @access public
+        */
+       public function handle_render_partials_request() {
+               if ( ! $this->is_render_partials_request() ) {
+                       return;
+               }
+
+               $this->manager->remove_preview_signature();
+
+               /*
+                * Note that is_customize_preview() returning true will entail that the
+                * user passed the 'customize' capability check and the nonce check, since
+                * WP_Customize_Manager::setup_theme() is where the previewing flag is set.
+                */
+               if ( ! is_customize_preview() ) {
+                       status_header( 403 );
+                       wp_send_json_error( 'expected_customize_preview' );
+               } else if ( ! isset( $_POST['partials'] ) ) {
+                       status_header( 400 );
+                       wp_send_json_error( 'missing_partials' );
+               }
+
+               $partials = json_decode( wp_unslash( $_POST['partials'] ), true );
+
+               if ( ! is_array( $partials ) ) {
+                       wp_send_json_error( 'malformed_partials' );
+               }
+
+               $this->add_dynamic_partials( array_keys( $partials ) );
+
+               /**
+                * Fires immediately before partials are rendered.
+                *
+                * Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts
+                * and styles which may get enqueued in the response.
+                *
+                * @since 4.5.0
+                *
+                * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
+                * @param array                          $partials Placements' context data for the partials rendered in the request.
+                *                                                 The array is keyed by partial ID, with each item being an array of
+                *                                                 the placements' context data.
+                */
+               do_action( 'customize_render_partials_before', $this, $partials );
+
+               set_error_handler( array( $this, 'handle_error' ), error_reporting() );
+
+               $contents = array();
+
+               foreach ( $partials as $partial_id => $container_contexts ) {
+                       $this->current_partial_id = $partial_id;
+
+                       if ( ! is_array( $container_contexts ) ) {
+                               wp_send_json_error( 'malformed_container_contexts' );
+                       }
+
+                       $partial = $this->get_partial( $partial_id );
+
+                       if ( ! $partial ) {
+                               $contents[ $partial_id ] = null;
+                               continue;
+                       }
+
+                       $contents[ $partial_id ] = array();
+
+                       // @todo The array should include not only the contents, but also whether the container is included?
+                       if ( empty( $container_contexts ) ) {
+                               // Since there are no container contexts, render just once.
+                               $contents[ $partial_id ][] = $partial->render( null );
+                       } else {
+                               foreach ( $container_contexts as $container_context ) {
+                                       $contents[ $partial_id ][] = $partial->render( $container_context );
+                               }
+                       }
+               }
+               $this->current_partial_id = null;
+
+               restore_error_handler();
+
+               /**
+                * Fires immediately after partials are rendered.
+                *
+                * Plugins may do things like call wp_footer() to scrape scripts output and return them
+                * via the {@see 'customize_render_partials_response'} filter.
+                *
+                * @since 4.5.0
+                *
+                * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
+                * @param array                          $partials Placements' context data for the partials rendered in the request.
+                *                                                 The array is keyed by partial ID, with each item being an array of
+                *                                                 the placements' context data.
+                */
+               do_action( 'customize_render_partials_after', $this, $partials );
+
+               $response = array(
+                       'contents' => $contents,
+               );
+
+               if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
+                       $response['errors'] = $this->triggered_errors;
+               }
+
+               /**
+                * Filters the response from rendering the partials.
+                *
+                * Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies
+                * for the partials being rendered. The response data will be available to the client via
+                * the `render-partials-response` JS event, so the client can then inject the scripts and
+                * styles into the DOM if they have not already been enqueued there.
+                *
+                * If plugins do this, they'll need to take care for any scripts that do `document.write()`
+                * and make sure that these are not injected, or else to override the function to no-op,
+                * or else the page will be destroyed.
+                *
+                * Plugins should be aware that `$scripts` and `$styles` may eventually be included by
+                * default in the response.
+                *
+                * @since 4.5.0
+                *
+                * @param array $response {
+                *     Response.
+                *
+                *     @type array $contents Associative array mapping a partial ID its corresponding array of contents
+                *                           for the containers requested.
+                *     @type array $errors   List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY`
+                *                           is enabled.
+                * }
+                * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
+                * @param array                          $partials Placements' context data for the partials rendered in the request.
+                *                                                 The array is keyed by partial ID, with each item being an array of
+                *                                                 the placements' context data.
+                */
+               $response = apply_filters( 'customize_render_partials_response', $response, $this, $partials );
+
+               wp_send_json_success( $response );
+       }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesjscustomizepreviewnavmenusjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/js/customize-preview-nav-menus.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/js/customize-preview-nav-menus.js   2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/js/customize-preview-nav-menus.js     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,315 +1,217 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/* global JSON, _wpCustomizePreviewNavMenusExports */
-
-( function( $, _, wp ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( $, _, wp, api ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         'use strict';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! wp || ! wp.customize ) { return; }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ var self = {};
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        var api = wp.customize,
-               currentRefreshDebounced = {},
-               refreshDebounceDelay = 200,
-               settings = {},
-               defaultSettings = {
-                       renderQueryVar: null,
-                       renderNonceValue: null,
-                       renderNoncePostKey: null,
-                       requestUri: '/',
-                       navMenuInstanceArgs: {},
-                       l10n: {}
-               };
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Initialize nav menus preview.
+        */
+       self.init = function() {
+               var self = this;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        api.MenusCustomizerPreview = {
-               /**
-                * Bootstrap functionality.
-                */
-               init : function() {
-                       var self = this, initializedSettings = {};
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( api.selectiveRefresh ) {
+                       self.watchNavMenuLocationChanges();
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        settings = _.extend( {}, defaultSettings );
-                       if ( 'undefined' !== typeof _wpCustomizePreviewNavMenusExports ) {
-                               _.extend( settings, _wpCustomizePreviewNavMenusExports );
-                       }
-
-                       api.each( function( setting, id ) {
-                               setting.id = id;
-                               initializedSettings[ setting.id ] = true;
-                               self.bindListener( setting );
-                       } );
-
-                       api.preview.bind( 'setting', function( args ) {
-                               var id, value, setting;
-                               args = args.slice();
-                               id = args.shift();
-                               value = args.shift();
-
-                               setting = api( id );
-                               if ( ! setting ) {
-                                       // Currently customize-preview.js is not creating settings for dynamically-created settings in the pane, so we have to do it.
-                                       setting = api.create( id, value ); // @todo This should be in core
-                               }
-                               if ( ! setting.id ) {
-                                       // Currently customize-preview.js doesn't set the id property for each setting, like customize-controls.js does.
-                                       setting.id = id;
-                               }
-
-                               if ( ! initializedSettings[ setting.id ] ) {
-                                       initializedSettings[ setting.id ] = true;
-                                       if ( self.bindListener( setting ) ) {
-                                               setting.callbacks.fireWith( setting, [ setting(), null ] );
-                                       }
-                               }
-                       } );
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         api.preview.bind( 'active', function() {
</ins><span class="cx" style="display: block; padding: 0 10px">                         self.highlightControls();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         } );
+       };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                *
-                * @param {wp.customize.Value} setting
-                * @returns {boolean} Whether the setting was bound.
-                */
-               bindListener : function( setting ) {
-                       var matches, themeLocation;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( api.selectiveRefresh ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        matches = setting.id.match( /^nav_menu\[(-?\d+)]$/ );
-                       if ( matches ) {
-                               setting.navMenuId = parseInt( matches[1], 10 );
-                               setting.bind( this.onChangeNavMenuSetting );
-                               return true;
-                       }
-
-                       matches = setting.id.match( /^nav_menu_item\[(-?\d+)]$/ );
-                       if ( matches ) {
-                               setting.navMenuItemId = parseInt( matches[1], 10 );
-                               setting.bind( this.onChangeNavMenuItemSetting );
-                               return true;
-                       }
-
-                       matches = setting.id.match( /^nav_menu_locations\[(.+?)]/ );
-                       if ( matches ) {
-                               themeLocation = matches[1];
-                               setting.bind( _.bind( function() {
-                                       this.refreshMenuLocation( themeLocation );
-                               }, this ) );
-                               return true;
-                       }
-
-                       return false;
-               },
-
</del><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * Handle changing of a nav_menu setting.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * Partial representing an invocation of wp_nav_menu().
</ins><span class="cx" style="display: block; padding: 0 10px">                  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * @this {wp.customize.Setting}
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * @class
+                * @augments wp.customize.selectiveRefresh.Partial
+                * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">                  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                onChangeNavMenuSetting : function() {
-                       var setting = this;
-                       if ( ! setting.navMenuId ) {
-                               throw new Error( 'Expected navMenuId property to be set.' );
-                       }
-                       api.MenusCustomizerPreview.refreshMenu( setting.navMenuId );
-               },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.NavMenuInstancePartial = api.selectiveRefresh.Partial.extend({
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                * Handle changing of a nav_menu_item setting.
-                *
-                * @this {wp.customize.Setting}
-                * @param {object} to
-                * @param {object} from
-                */
-               onChangeNavMenuItemSetting : function( to, from ) {
-                       if ( from && from.nav_menu_term_id && ( ! to || from.nav_menu_term_id !== to.nav_menu_term_id ) ) {
-                               api.MenusCustomizerPreview.refreshMenu( from.nav_menu_term_id );
-                       }
-                       if ( to && to.nav_menu_term_id ) {
-                               api.MenusCustomizerPreview.refreshMenu( to.nav_menu_term_id );
-                       }
-               },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /**
+                        * Constructor.
+                        *
+                        * @since 4.5.0
+                        * @param {string} id - Partial ID.
+                        * @param {Object} options
+                        * @param {Object} options.params
+                        * @param {Object} options.params.navMenuArgs
+                        * @param {string} options.params.navMenuArgs.args_hmac
+                        * @param {string} [options.params.navMenuArgs.theme_location]
+                        * @param {number} [options.params.navMenuArgs.menu]
+                        * @param {object} [options.constructingContainerContext]
+                        */
+                       initialize: function( id, options ) {
+                               var partial = this, matches, argsHmac;
+                               matches = id.match( /^nav_menu_instance\[([0-9a-f]{32})]$/ );
+                               if ( ! matches ) {
+                                       throw new Error( 'Illegal id for nav_menu_instance partial. The key corresponds with the args HMAC.' );
+                               }
+                               argsHmac = matches[1];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                * Update a given menu rendered in the preview.
-                *
-                * @param {int} menuId
-                */
-               refreshMenu : function( menuId ) {
-                       var assignedLocations = [];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         options = options || {};
+                               options.params = _.extend(
+                                       {
+                                               selector: '[data-customize-partial-id="' + id + '"]',
+                                               navMenuArgs: options.constructingContainerContext || {},
+                                               containerInclusive: true
+                                       },
+                                       options.params || {}
+                               );
+                               api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        api.each(function( setting, id ) {
-                               var matches = id.match( /^nav_menu_locations\[(.+?)]/ );
-                               if ( matches && menuId === setting() ) {
-                                       assignedLocations.push( matches[1] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! _.isObject( partial.params.navMenuArgs ) ) {
+                                       throw new Error( 'Missing navMenuArgs' );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        });
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( partial.params.navMenuArgs.args_hmac !== argsHmac ) {
+                                       throw new Error( 'args_hmac mismatch with id' );
+                               }
+                       },
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        _.each( settings.navMenuInstanceArgs, function( navMenuArgs, instanceNumber ) {
-                               if ( menuId === navMenuArgs.menu || -1 !== _.indexOf( assignedLocations, navMenuArgs.theme_location ) ) {
-                                       this.refreshMenuInstanceDebounced( instanceNumber );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /**
+                        * Return whether the setting is related to this partial.
+                        *
+                        * @since 4.5.0
+                        * @param {wp.customize.Value|string} setting  - Object or ID.
+                        * @param {number|object|false|null}  newValue - New value, or null if the setting was just removed.
+                        * @param {number|object|false|null}  oldValue - Old value, or null if the setting was just added.
+                        * @returns {boolean}
+                        */
+                       isRelatedSetting: function( setting, newValue, oldValue ) {
+                               var partial = this, navMenuLocationSetting, navMenuId, isNavMenuItemSetting;
+                               if ( _.isString( setting ) ) {
+                                       setting = api( setting );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        }, this );
-               },
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                * Refresh the menu(s) associated with a given nav menu location.
-                *
-                * @param {string} location
-                */
-               refreshMenuLocation : function( location ) {
-                       var foundInstance = false;
-                       _.each( settings.navMenuInstanceArgs, function( navMenuArgs, instanceNumber ) {
-                               if ( location === navMenuArgs.theme_location ) {
-                                       this.refreshMenuInstanceDebounced( instanceNumber );
-                                       foundInstance = true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /*
+                                * Prevent nav_menu_item changes only containing type_label differences triggering a refresh.
+                                * These settings in the preview do not include type_label property, and so if one of these
+                                * nav_menu_item settings is dirty, after a refresh the nav menu instance would do a selective
+                                * refresh immediately because the setting from the pane would have the type_label whereas
+                                * the setting in the preview would not, thus triggering a change event. The following
+                                * condition short-circuits this unnecessary selective refresh and also prevents an infinite
+                                * loop in the case where a nav_menu_instance partial had done a fallback refresh.
+                                * @todo Nav menu item settings should not include a type_label property to begin with.
+                                */
+                               isNavMenuItemSetting = /^nav_menu_item\[/.test( setting.id );
+                               if ( isNavMenuItemSetting && _.isObject( newValue ) && _.isObject( oldValue ) ) {
+                                       delete newValue.type_label;
+                                       delete oldValue.type_label;
+                                       if ( _.isEqual( oldValue, newValue ) ) {
+                                               return false;
+                                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        }, this );
-                       if ( ! foundInstance ) {
-                               api.preview.send( 'refresh' );
-                       }
-               },
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                * Update a specific instance of a given menu on the page.
-                *
-                * @param {int} instanceNumber
-                */
-               refreshMenuInstance : function( instanceNumber ) {
-                       var data, menuId, customized, container, request, wpNavMenuArgs, instance, containerInstanceClassName;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( partial.params.navMenuArgs.theme_location ) {
+                                       if ( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' === setting.id ) {
+                                               return true;
+                                       }
+                                       navMenuLocationSetting = api( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' );
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! settings.navMenuInstanceArgs[ instanceNumber ] ) {
-                               throw new Error( 'unknown_instance_number' );
-                       }
-                       instance = settings.navMenuInstanceArgs[ instanceNumber ];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         navMenuId = partial.params.navMenuArgs.menu;
+                               if ( ! navMenuId && navMenuLocationSetting ) {
+                                       navMenuId = navMenuLocationSetting();
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        containerInstanceClassName = 'partial-refreshable-nav-menu-' + String( instanceNumber );
-                       container = $( '.' + containerInstanceClassName );
-
-                       if ( _.isNumber( instance.menu ) ) {
-                               menuId = instance.menu;
-                       } else if ( instance.theme_location && api.has( 'nav_menu_locations[' + instance.theme_location + ']' ) ) {
-                               menuId = api( 'nav_menu_locations[' + instance.theme_location + ']' ).get();
-                       }
-
-                       if ( ! menuId || ! instance.can_partial_refresh || 0 === container.length ) {
-                               api.preview.send( 'refresh' );
-                               return;
-                       }
-                       menuId = parseInt( menuId, 10 );
-
-                       data = {
-                               nonce: wp.customize.settings.nonce.preview,
-                               wp_customize: 'on'
-                       };
-                       if ( ! wp.customize.settings.theme.active ) {
-                               data.theme = wp.customize.settings.theme.stylesheet;
-                       }
-                       data[ settings.renderQueryVar ] = '1';
-
-                       // Gather settings to send in partial refresh request.
-                       customized = {};
-                       api.each( function( setting, id ) {
-                               var value = setting.get(), shouldSend = false;
-                               // @todo Core should propagate the dirty state into the Preview as well so we can use that here.
-
-                               // Send setting if it is a nav_menu_locations[] setting.
-                               shouldSend = shouldSend || /^nav_menu_locations\[/.test( id );
-
-                               // Send setting if it is the setting for this menu.
-                               shouldSend = shouldSend || id === 'nav_menu[' + String( menuId ) + ']';
-
-                               // Send setting if it is one that is associated with this menu, or it is deleted.
-                               shouldSend = shouldSend || ( /^nav_menu_item\[/.test( id ) && ( false === value || menuId === value.nav_menu_term_id ) );
-
-                               if ( shouldSend ) {
-                                       customized[ id ] = value;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! navMenuId ) {
+                                       return false;
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } );
-                       data.customized = JSON.stringify( customized );
-                       data[ settings.renderNoncePostKey ] = settings.renderNonceValue;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         return (
+                                       ( 'nav_menu[' + navMenuId + ']' === setting.id ) ||
+                                       ( isNavMenuItemSetting && (
+                                               ( newValue && newValue.nav_menu_term_id === navMenuId ) ||
+                                               ( oldValue && oldValue.nav_menu_term_id === navMenuId )
+                                       ) )
+                               );
+                       },
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        wpNavMenuArgs = $.extend( {}, instance );
-                       data.wp_nav_menu_args_hash = wpNavMenuArgs.args_hash;
-                       delete wpNavMenuArgs.args_hash;
-                       data.wp_nav_menu_args = JSON.stringify( wpNavMenuArgs );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /**
+                        * Render content.
+                        *
+                        * @inheritdoc
+                        * @param {wp.customize.selectiveRefresh.Placement} placement
+                        */
+                       renderContent: function( placement ) {
+                               var partial = this, previousContainer = placement.container;
+                               if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        container.addClass( 'customize-partial-refreshing' );
-
-                       request = wp.ajax.send( null, {
-                               data: data,
-                               url: api.settings.url.self
-                       } );
-                       request.done( function( data ) {
-                               // If the menu is now not visible, refresh since the page layout may have changed.
-                               if ( false === data ) {
-                                       api.preview.send( 'refresh' );
-                                       return;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 // Trigger deprecated event.
+                                       $( document ).trigger( 'customize-preview-menu-refreshed', [ {
+                                               instanceNumber: null, // @deprecated
+                                               wpNavArgs: placement.context, // @deprecated
+                                               wpNavMenuArgs: placement.context,
+                                               oldContainer: previousContainer,
+                                               newContainer: placement.container
+                                       } ] );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        }
+               });
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                var eventParam, previousContainer = container;
-                               container = $( data );
-                               container.addClass( containerInstanceClassName );
-                               container.addClass( 'partial-refreshable-nav-menu customize-partial-refreshing' );
-                               previousContainer.replaceWith( container );
-                               eventParam = {
-                                       instanceNumber: instanceNumber,
-                                       wpNavArgs: wpNavMenuArgs, // @deprecated
-                                       wpNavMenuArgs: wpNavMenuArgs,
-                                       oldContainer: previousContainer,
-                                       newContainer: container
-                               };
-                               container.removeClass( 'customize-partial-refreshing' );
-                               $( document ).trigger( 'customize-preview-menu-refreshed', [ eventParam ] );
-                       } );
-                       request.fail( function() {
-                               api.preview.send( 'refresh' );
-                       } );
-               },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         api.selectiveRefresh.partialConstructor.nav_menu_instance = self.NavMenuInstancePartial;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                refreshMenuInstanceDebounced : function( instanceNumber ) {
-                       if ( currentRefreshDebounced[ instanceNumber ] ) {
-                               clearTimeout( currentRefreshDebounced[ instanceNumber ] );
-                       }
-                       currentRefreshDebounced[ instanceNumber ] = setTimeout(
-                               _.bind( function() {
-                                       this.refreshMenuInstance( instanceNumber );
-                               }, this ),
-                               refreshDebounceDelay
-                       );
-               },
-
</del><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * Connect nav menu items with their corresponding controls in the pane.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * Watch for changes to nav_menu_locations[] settings.
+                *
+                * Refresh partials associated with the given nav_menu_locations[] setting,
+                * or request an entire preview refresh if there are no containers in the
+                * document for a partial associated with the theme location.
+                *
+                * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">                  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                highlightControls: function() {
-                       var selector = '.menu-item',
-                               addTooltips;
-
-                       // Open expand the menu item control when shift+clicking the menu item
-                       $( document ).on( 'click', selector, function( e ) {
-                               var navMenuItemParts;
-                               if ( ! e.shiftKey ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.watchNavMenuLocationChanges = function() {
+                       api.bind( 'change', function( setting ) {
+                               var themeLocation, themeLocationPartialFound = false, matches = setting.id.match( /^nav_menu_locations\[(.+)]$/ );
+                               if ( ! matches ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         return;
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                themeLocation = matches[1];
+                               api.selectiveRefresh.partial.each( function( partial ) {
+                                       if ( partial.extended( self.NavMenuInstancePartial ) && partial.params.navMenuArgs.theme_location === themeLocation ) {
+                                               partial.refresh();
+                                               themeLocationPartialFound = true;
+                                       }
+                               } );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                navMenuItemParts = $( this ).attr( 'class' ).match( /(?:^|\s)menu-item-(\d+)(?:\s|$)/ );
-                               if ( navMenuItemParts ) {
-                                       e.preventDefault();
-                                       e.stopPropagation(); // Make sure a sub-nav menu item will get focused instead of parent items.
-                                       api.preview.send( 'focus-nav-menu-item-control', parseInt( navMenuItemParts[1], 10 ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! themeLocationPartialFound ) {
+                                       api.selectiveRefresh.requestFullRefresh();
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        });
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 } );
+               };
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        addTooltips = function( e, params ) {
-                               params.newContainer.find( selector ).attr( 'title', settings.l10n.editNavMenuItemTooltip );
-                       };
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Connect nav menu items with their corresponding controls in the pane.
+        *
+        * Setup shift-click on nav menu items which are more granular than the nav menu partial itself.
+        * Also this applies even if a nav menu is not partial-refreshable.
+        *
+        * @since 4.5.0
+        */
+       self.highlightControls = function() {
+               var selector = '.menu-item';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        addTooltips( null, { newContainer: $( document.body ) } );
-                       $( document ).on( 'customize-preview-menu-refreshed', addTooltips );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Focus on the menu item control when shift+clicking the menu item.
+               $( document ).on( 'click', selector, function( e ) {
+                       var navMenuItemParts;
+                       if ( ! e.shiftKey ) {
+                               return;
+                       }
+
+                       navMenuItemParts = $( this ).attr( 'class' ).match( /(?:^|\s)menu-item-(\d+)(?:\s|$)/ );
+                       if ( navMenuItemParts ) {
+                               e.preventDefault();
+                               e.stopPropagation(); // Make sure a sub-nav menu item will get focused instead of parent items.
+                               api.preview.send( 'focus-nav-menu-item-control', parseInt( navMenuItemParts[1], 10 ) );
+                       }
+               });
</ins><span class="cx" style="display: block; padding: 0 10px">         };
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        api.bind( 'preview-ready', function() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                api.preview.bind( 'active', function() {
-                       api.MenusCustomizerPreview.init();
-               } );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.init();
</ins><span class="cx" style="display: block; padding: 0 10px">         } );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-}( jQuery, _, wp ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return self;
+
+}( jQuery, _, wp, wp.customize ) );
</ins></span></pre></div>
<a id="trunksrcwpincludesjscustomizepreviewwidgetsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/js/customize-preview-widgets.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/js/customize-preview-widgets.js     2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/js/customize-preview-widgets.js       2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,119 +1,648 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-(function( wp, $ ){
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global _wpWidgetCustomizerPreviewSettings */
+wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! wp || ! wp.customize ) { return; }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ var self;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        var api = wp.customize;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ self = {
+               renderedSidebars: {},
+               renderedWidgets: {},
+               registeredSidebars: [],
+               registeredWidgets: {},
+               widgetSelectors: [],
+               preview: null,
+               l10n: {
+                       widgetTooltip: ''
+               }
+       };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * wp.customize.WidgetCustomizerPreview
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Init widgets preview.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        api.WidgetCustomizerPreview = {
-               renderedSidebars: {}, // @todo Make rendered a property of the Backbone model
-               renderedWidgets: {}, // @todo Make rendered a property of the Backbone model
-               registeredSidebars: [], // @todo Make a Backbone collection
-               registeredWidgets: {}, // @todo Make array, Backbone collection
-               widgetSelectors: [],
-               preview: null,
-               l10n: {},
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ self.init = function() {
+               var self = this;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                init: function () {
-                       var self = this;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.preview = api.preview;
+               if ( api.selectiveRefresh ) {
+                       self.addPartials();
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        this.preview = api.preview;
-                       this.buildWidgetSelectors();
-                       this.highlightControls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.buildWidgetSelectors();
+               self.highlightControls();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        this.preview.bind( 'highlight-widget', self.highlightWidget );
-               },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.preview.bind( 'highlight-widget', self.highlightWidget );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                api.preview.bind( 'active', function() {
+                       self.highlightControls();
+               } );
+       };
+
+       if ( api.selectiveRefresh ) {
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * Calculate the selector for the sidebar's widgets based on the registered sidebar's info
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * Partial representing a widget instance.
+                *
+                * @class
+                * @augments wp.customize.selectiveRefresh.Partial
+                * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">                  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                buildWidgetSelectors: function () {
-                       var self = this;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.WidgetPartial = api.selectiveRefresh.Partial.extend({
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $.each( this.registeredSidebars, function ( i, sidebar ) {
-                               var widgetTpl = [
-                                               sidebar.before_widget.replace('%1$s', '').replace('%2$s', ''),
-                                               sidebar.before_title,
-                                               sidebar.after_title,
-                                               sidebar.after_widget
-                                       ].join(''),
-                                       emptyWidget,
-                                       widgetSelector,
-                                       widgetClasses;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /**
+                        * Constructor.
+                        *
+                        * @since 4.5.0
+                        * @param {string} id - Partial ID.
+                        * @param {Object} options
+                        * @param {Object} options.params
+                        */
+                       initialize: function( id, options ) {
+                               var partial = this, matches;
+                               matches = id.match( /^widget\[(.+)]$/ );
+                               if ( ! matches ) {
+                                       throw new Error( 'Illegal id for widget partial.' );
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                emptyWidget = $(widgetTpl);
-                               widgetSelector = emptyWidget.prop('tagName');
-                               widgetClasses = emptyWidget.prop('className');
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         partial.widgetId = matches[1];
+                               options = options || {};
+                               options.params = _.extend(
+                                       {
+                                               /* Note that a selector of ('#' + partial.widgetId) is faster, but jQuery will only return the one result. */
+                                               selector: '[id="' + partial.widgetId + '"]', // Alternatively, '[data-customize-widget-id="' + partial.widgetId + '"]'
+                                               settings: [ self.getWidgetSettingId( partial.widgetId ) ],
+                                               containerInclusive: true
+                                       },
+                                       options.params || {}
+                               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty.
-                               if ( ! widgetClasses ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
+                       },
+
+                       /**
+                        * Send widget-updated message to parent so spinner will get removed from widget control.
+                        *
+                        * @inheritdoc
+                        * @param {wp.customize.selectiveRefresh.Placement} placement
+                        */
+                       renderContent: function( placement ) {
+                               var partial = this;
+                               if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
+                                       api.preview.send( 'widget-updated', partial.widgetId );
+                                       api.selectiveRefresh.trigger( 'widget-updated', partial );
+                               }
+                       }
+               });
+
+               /**
+                * Partial representing a widget area.
+                *
+                * @class
+                * @augments wp.customize.selectiveRefresh.Partial
+                * @since 4.5.0
+                */
+               self.SidebarPartial = api.selectiveRefresh.Partial.extend({
+
+                       /**
+                        * Constructor.
+                        *
+                        * @since 4.5.0
+                        * @param {string} id - Partial ID.
+                        * @param {Object} options
+                        * @param {Object} options.params
+                        */
+                       initialize: function( id, options ) {
+                               var partial = this, matches;
+                               matches = id.match( /^sidebar\[(.+)]$/ );
+                               if ( ! matches ) {
+                                       throw new Error( 'Illegal id for sidebar partial.' );
+                               }
+                               partial.sidebarId = matches[1];
+
+                               options = options || {};
+                               options.params = _.extend(
+                                       {
+                                               settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
+                                       },
+                                       options.params || {}
+                               );
+
+                               api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
+
+                               if ( ! partial.params.sidebarArgs ) {
+                                       throw new Error( 'The sidebarArgs param was not provided.' );
+                               }
+                               if ( partial.params.settings.length > 1 ) {
+                                       throw new Error( 'Expected SidebarPartial to only have one associated setting' );
+                               }
+                       },
+
+                       /**
+                        * Set up the partial.
+                        *
+                        * @since 4.5.0
+                        */
+                       ready: function() {
+                               var sidebarPartial = this;
+
+                               // Watch for changes to the sidebar_widgets setting.
+                               _.each( sidebarPartial.settings(), function( settingId ) {
+                                       api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
+                               } );
+
+                               // Trigger an event for this sidebar being updated whenever a widget inside is rendered.
+                               api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
+                                       var isAssignedWidgetPartial = (
+                                               placement.partial.extended( self.WidgetPartial ) &&
+                                               ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
+                                       );
+                                       if ( isAssignedWidgetPartial ) {
+                                               api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
+                                       }
+                               } );
+
+                               // Make sure that a widget partial has a container in the DOM prior to a refresh.
+                               api.bind( 'change', function( widgetSetting ) {
+                                       var widgetId, parsedId;
+                                       parsedId = self.parseWidgetSettingId( widgetSetting.id );
+                                       if ( ! parsedId ) {
+                                               return;
+                                       }
+                                       widgetId = parsedId.idBase;
+                                       if ( parsedId.number ) {
+                                               widgetId += '-' + String( parsedId.number );
+                                       }
+                                       if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
+                                               sidebarPartial.ensureWidgetPlacementContainers( widgetId );
+                                       }
+                               } );
+                       },
+
+                       /**
+                        * Get the before/after boundary nodes for all instances of this sidebar (usually one).
+                        *
+                        * Note that TreeWalker is not implemented in IE8.
+                        *
+                        * @since 4.5.0
+                        * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
+                        */
+                       findDynamicSidebarBoundaryNodes: function() {
+                               var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
+                               regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
+                               recursiveCommentTraversal = function( childNodes ) {
+                                       _.each( childNodes, function( node ) {
+                                               var matches;
+                                               if ( 8 === node.nodeType ) {
+                                                       matches = node.nodeValue.match( regExp );
+                                                       if ( ! matches || matches[2] !== partial.sidebarId ) {
+                                                               return;
+                                                       }
+                                                       if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
+                                                               boundaryNodes[ matches[3] ] = {
+                                                                       before: null,
+                                                                       after: null,
+                                                                       instanceNumber: parseInt( matches[3], 10 )
+                                                               };
+                                                       }
+                                                       if ( 'dynamic_sidebar_before' === matches[1] ) {
+                                                               boundaryNodes[ matches[3] ].before = node;
+                                                       } else {
+                                                               boundaryNodes[ matches[3] ].after = node;
+                                                       }
+                                               } else if ( 1 === node.nodeType ) {
+                                                       recursiveCommentTraversal( node.childNodes );
+                                               }
+                                       } );
+                               };
+
+                               recursiveCommentTraversal( document.body.childNodes );
+                               return _.values( boundaryNodes );
+                       },
+
+                       /**
+                        * Get the placements for this partial.
+                        *
+                        * @since 4.5.0
+                        * @returns {Array}
+                        */
+                       placements: function() {
+                               var partial = this;
+                               return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
+                                       return new api.selectiveRefresh.Placement( {
+                                               partial: partial,
+                                               container: null,
+                                               startNode: boundaryNodes.before,
+                                               endNode: boundaryNodes.after,
+                                               context: {
+                                                       instanceNumber: boundaryNodes.instanceNumber
+                                               }
+                                       } );
+                               } );
+                       },
+
+                       /**
+                        * Get the list of widget IDs associated with this widget area.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @returns {Array}
+                        */
+                       getWidgetIds: function() {
+                               var sidebarPartial = this, settingId, widgetIds;
+                               settingId = sidebarPartial.settings()[0];
+                               if ( ! settingId ) {
+                                       throw new Error( 'Missing associated setting.' );
+                               }
+                               if ( ! api.has( settingId ) ) {
+                                       throw new Error( 'Setting does not exist.' );
+                               }
+                               widgetIds = api( settingId ).get();
+                               if ( ! _.isArray( widgetIds ) ) {
+                                       throw new Error( 'Expected setting to be array of widget IDs' );
+                               }
+                               return widgetIds.slice( 0 );
+                       },
+
+                       /**
+                        * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
+                        */
+                       reflowWidgets: function() {
+                               var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
+                               widgetIds = sidebarPartial.getWidgetIds();
+                               sidebarPlacements = sidebarPartial.placements();
+
+                               widgetPartials = {};
+                               _.each( widgetIds, function( widgetId ) {
+                                       var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
+                                       if ( widgetPartial ) {
+                                               widgetPartials[ widgetId ] = widgetPartial;
+                                       }
+                               } );
+
+                               _.each( sidebarPlacements, function( sidebarPlacement ) {
+                                       var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
+
+                                       // Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
+                                       _.each( widgetPartials, function( widgetPartial ) {
+                                               _.each( widgetPartial.placements(), function( widgetPlacement ) {
+
+                                                       if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
+                                                               thisPosition = widgetPlacement.container.index();
+                                                               sidebarWidgets.push( {
+                                                                       partial: widgetPartial,
+                                                                       placement: widgetPlacement,
+                                                                       position: thisPosition
+                                                               } );
+                                                               if ( thisPosition < lastPosition ) {
+                                                                       needsSort = true;
+                                                               }
+                                                               lastPosition = thisPosition;
+                                                       }
+                                               } );
+                                       } );
+
+                                       if ( needsSort ) {
+                                               _.each( sidebarWidgets, function( sidebarWidget ) {
+                                                       sidebarPlacement.endNode.parentNode.insertBefore(
+                                                               sidebarWidget.placement.container[0],
+                                                               sidebarPlacement.endNode
+                                                       );
+
+                                                       // @todo Rename partial-placement-moved?
+                                                       api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
+                                               } );
+
+                                               sortedSidebarContainers.push( sidebarPlacement );
+                                       }
+                               } );
+
+                               if ( sortedSidebarContainers.length > 0 ) {
+                                       api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
+                               }
+
+                               return sortedSidebarContainers;
+                       },
+
+                       /**
+                        * Make sure there is a widget instance container in this sidebar for the given widget ID.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @param {string} widgetId
+                        * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
+                        */
+                       ensureWidgetPlacementContainers: function( widgetId ) {
+                               var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
+                               widgetPartial = api.selectiveRefresh.partial( partialId );
+                               if ( ! widgetPartial ) {
+                                       widgetPartial = new self.WidgetPartial( partialId, {
+                                               params: {}
+                                       } );
+                                       api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
+                               }
+
+                               // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
+                               _.each( sidebarPartial.placements(), function( sidebarPlacement ) {
+                                       var foundWidgetPlacement, widgetContainerElement;
+
+                                       foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
+                                               return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
+                                       } );
+                                       if ( foundWidgetPlacement ) {
+                                               return;
+                                       }
+
+                                       widgetContainerElement = $(
+                                               sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) +
+                                               sidebarPartial.params.sidebarArgs.after_widget
+                                       );
+
+                                       widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
+                                       widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
+                                       widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
+
+                                       /*
+                                        * Make sure the widget container element has the customize-container context data.
+                                        * The sidebar_instance_number is used to disambiguate multiple instances of the
+                                        * same sidebar are rendered onto the template, and so the same widget is embedded
+                                        * multiple times.
+                                        */
+                                       widgetContainerElement.data( 'customize-partial-placement-context', {
+                                               'sidebar_id': sidebarPartial.sidebarId,
+                                               'sidebar_instance_number': sidebarPlacement.context.instanceNumber
+                                       } );
+
+                                       sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
+                                       wasInserted = true;
+                               } );
+
+                               if ( wasInserted ) {
+                                       sidebarPartial.reflowWidgets();
+                               }
+
+                               return widgetPartial;
+                       },
+
+                       /**
+                        * Handle change to the sidebars_widgets[] setting.
+                        *
+                        * @since 4.5.0
+                        *
+                        * @param {Array} newWidgetIds New widget ids.
+                        * @param {Array} oldWidgetIds Old widget ids.
+                        */
+                       handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
+                               var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
+
+                               needsRefresh = (
+                                       ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
+                                       ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
+                               );
+                               if ( needsRefresh ) {
+                                       sidebarPartial.fallback();
</ins><span class="cx" style="display: block; padding: 0 10px">                                         return;
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                widgetClasses = widgetClasses.replace(/^\s+|\s+$/g, '');
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Handle removal of widgets.
+                               widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
+                               _.each( widgetsRemoved, function( removedWidgetId ) {
+                                       var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
+                                       if ( widgetPartial ) {
+                                               _.each( widgetPartial.placements(), function( placement ) {
+                                                       var isRemoved = (
+                                                               placement.context.sidebar_id === sidebarPartial.sidebarId ||
+                                                               ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
+                                                       );
+                                                       if ( isRemoved ) {
+                                                               placement.container.remove();
+                                                       }
+                                               } );
+                                       }
+                               } );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( widgetClasses ) {
-                                       widgetSelector += '.' + widgetClasses.split(/\s+/).join('.');
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Handle insertion of widgets.
+                               widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
+                               _.each( widgetsAdded, function( addedWidgetId ) {
+                                       var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
+                                       addedWidgetPartials.push( widgetPartial );
+                               } );
+
+                               _.each( addedWidgetPartials, function( widgetPartial ) {
+                                       widgetPartial.refresh();
+                               } );
+
+                               api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
+                       },
+
+                       /**
+                        * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
+                        *
+                        * @since 4.5.0
+                        */
+                       refresh: function() {
+                               var partial = this, deferred = $.Deferred();
+
+                               deferred.fail( function() {
+                                       partial.fallback();
+                               } );
+
+                               if ( 0 === partial.placements().length ) {
+                                       deferred.reject();
+                               } else {
+                                       _.each( partial.reflowWidgets(), function( sidebarPlacement ) {
+                                               api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
+                                       } );
+                                       deferred.resolve();
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                self.widgetSelectors.push(widgetSelector);
-                       });
-               },
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                return deferred.promise();
+                       }
+               });
+
+               api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
+               api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * Highlight the widget on widget updates or widget control mouse overs.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * Add partials for the registered widget areas (sidebars).
</ins><span class="cx" style="display: block; padding: 0 10px">                  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * @param  {string} widgetId ID of the widget.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * @since 4.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">                  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                highlightWidget: function( widgetId ) {
-                       var $body = $( document.body ),
-                               $widget = $( '#' + widgetId );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self.addPartials = function() {
+                       _.each( self.registeredSidebars, function( registeredSidebar ) {
+                               var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
+                               partial = api.selectiveRefresh.partial( partialId );
+                               if ( ! partial ) {
+                                       partial = new self.SidebarPartial( partialId, {
+                                               params: {
+                                                       sidebarArgs: registeredSidebar
+                                               }
+                                       } );
+                                       api.selectiveRefresh.partial.add( partial.id, partial );
+                               }
+                       } );
+               };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $widget.addClass( 'widget-customizer-highlighted-widget' );
-                       setTimeout( function () {
-                               $widget.removeClass( 'widget-customizer-highlighted-widget' );
-                       }, 500 );
-               },
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Calculate the selector for the sidebar's widgets based on the registered sidebar's info.
+        *
+        * @since 3.9.0
+        */
+       self.buildWidgetSelectors = function() {
+               var self = this;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /**
-                * Show a title and highlight widgets on hover. On shift+clicking
-                * focus the widget control.
-                */
-               highlightControls: function() {
-                       var self = this,
-                               selector = this.widgetSelectors.join(',');
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $.each( self.registeredSidebars, function( i, sidebar ) {
+                       var widgetTpl = [
+                                       sidebar.before_widget.replace( '%1$s', '' ).replace( '%2$s', '' ),
+                                       sidebar.before_title,
+                                       sidebar.after_title,
+                                       sidebar.after_widget
+                               ].join( '' ),
+                               emptyWidget,
+                               widgetSelector,
+                               widgetClasses;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $(selector).attr( 'title', this.l10n.widgetTooltip );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 emptyWidget = $( widgetTpl );
+                       widgetSelector = emptyWidget.prop( 'tagName' );
+                       widgetClasses = emptyWidget.prop( 'className' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $(document).on( 'mouseenter', selector, function () {
-                               self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) );
-                       });
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty.
+                       if ( ! widgetClasses ) {
+                               return;
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Open expand the widget control when shift+clicking the widget element
-                       $(document).on( 'click', selector, function ( e ) {
-                               if ( ! e.shiftKey ) {
-                                       return;
-                               }
-                               e.preventDefault();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) );
-                       });
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( widgetClasses ) {
+                               widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' );
+                       }
+                       self.widgetSelectors.push( widgetSelector );
+               });
+       };
+
+       /**
+        * Highlight the widget on widget updates or widget control mouse overs.
+        *
+        * @since 3.9.0
+        * @param  {string} widgetId ID of the widget.
+        */
+       self.highlightWidget = function( widgetId ) {
+               var $body = $( document.body ),
+                       $widget = $( '#' + widgetId );
+
+               $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' );
+
+               $widget.addClass( 'widget-customizer-highlighted-widget' );
+               setTimeout( function() {
+                       $widget.removeClass( 'widget-customizer-highlighted-widget' );
+               }, 500 );
+       };
+
+       /**
+        * Show a title and highlight widgets on hover. On shift+clicking
+        * focus the widget control.
+        *
+        * @since 3.9.0
+        */
+       self.highlightControls = function() {
+               var self = this,
+                       selector = this.widgetSelectors.join( ',' );
+
+               $( selector ).attr( 'title', this.l10n.widgetTooltip );
+
+               $( document ).on( 'mouseenter', selector, function() {
+                       self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) );
+               });
+
+               // Open expand the widget control when shift+clicking the widget element
+               $( document ).on( 'click', selector, function( e ) {
+                       if ( ! e.shiftKey ) {
+                               return;
+                       }
+                       e.preventDefault();
+
+                       self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) );
+               });
+       };
+
+       /**
+        * Parse a widget ID.
+        *
+        * @since 4.5.0
+        *
+        * @param {string} widgetId Widget ID.
+        * @returns {{idBase: string, number: number|null}}
+        */
+       self.parseWidgetId = function( widgetId ) {
+               var matches, parsed = {
+                       idBase: '',
+                       number: null
+               };
+
+               matches = widgetId.match( /^(.+)-(\d+)$/ );
+               if ( matches ) {
+                       parsed.idBase = matches[1];
+                       parsed.number = parseInt( matches[2], 10 );
+               } else {
+                       parsed.idBase = widgetId; // Likely an old single widget.
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               return parsed;
</ins><span class="cx" style="display: block; padding: 0 10px">         };
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $(function () {
-               var settings = window._wpWidgetCustomizerPreviewSettings;
-               if ( ! settings ) {
-                       return;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Parse a widget setting ID.
+        *
+        * @since 4.5.0
+        *
+        * @param {string} settingId Widget setting ID.
+        * @returns {{idBase: string, number: number|null}|null}
+        */
+       self.parseWidgetSettingId = function( settingId ) {
+               var matches, parsed = {
+                       idBase: '',
+                       number: null
+               };
+
+               matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ );
+               if ( ! matches ) {
+                       return null;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                parsed.idBase = matches[1];
+               if ( matches[2] ) {
+                       parsed.number = parseInt( matches[2], 10 );
+               }
+               return parsed;
+       };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $.extend( api.WidgetCustomizerPreview, settings );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Convert a widget ID into a Customizer setting ID.
+        *
+        * @since 4.5.0
+        *
+        * @param {string} widgetId Widget ID.
+        * @returns {string} settingId Setting ID.
+        */
+       self.getWidgetSettingId = function( widgetId ) {
+               var parsed = this.parseWidgetId( widgetId ), settingId;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                api.WidgetCustomizerPreview.init();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         settingId = 'widget_' + parsed.idBase;
+               if ( parsed.number ) {
+                       settingId += '[' + String( parsed.number ) + ']';
+               }
+
+               return settingId;
+       };
+
+       api.bind( 'preview-ready', function() {
+               $.extend( self, _wpWidgetCustomizerPreviewSettings );
+               self.init();
</ins><span class="cx" style="display: block; padding: 0 10px">         });
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-})( window.wp, jQuery );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return self;
+})( jQuery, _, wp, wp.customize );
</ins></span></pre></div>
<a id="trunksrcwpincludesjscustomizeselectiverefreshjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/js/customize-selective-refresh.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/js/customize-selective-refresh.js                           (rev 0)
+++ trunk/src/wp-includes/js/customize-selective-refresh.js     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,873 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global jQuery, JSON, _customizePartialRefreshExports, console */
+
+wp.customize.selectiveRefresh = ( function( $, api ) {
+       'use strict';
+       var self, Partial, Placement;
+
+       self = {
+               ready: $.Deferred(),
+               data: {
+                       partials: {},
+                       renderQueryVar: '',
+                       l10n: {
+                               shiftClickToEdit: ''
+                       },
+                       refreshBuffer: 250
+               },
+               currentRequest: null
+       };
+
+       _.extend( self, api.Events );
+
+       /**
+        * A Customizer Partial.
+        *
+        * A partial provides a rendering of one or more settings according to a template.
+        *
+        * @see PHP class WP_Customize_Partial.
+        *
+        * @class
+        * @augments wp.customize.Class
+        * @since 4.5.0
+        *
+        * @param {string} id                              Unique identifier for the control instance.
+        * @param {object} options                         Options hash for the control instance.
+        * @param {object} options.params
+        * @param {string} options.params.type             Type of partial (e.g. nav_menu, widget, etc)
+        * @param {string} options.params.selector         jQuery selector to find the container element in the page.
+        * @param {array}  options.params.settings         The IDs for the settings the partial relates to.
+        * @param {string} options.params.primarySetting   The ID for the primary setting the partial renders.
+        * @param {bool}   options.params.fallbackRefresh  Whether to refresh the entire preview in case of a partial refresh failure.
+        */
+       Partial = self.Partial = api.Class.extend({
+
+               id: null,
+
+                /**
+                * Constructor.
+                *
+                * @since 4.5.0
+                *
+                * @param {string} id - Partial ID.
+                * @param {Object} options
+                * @param {Object} options.params
+                */
+               initialize: function( id, options ) {
+                       var partial = this;
+                       options = options || {};
+                       partial.id = id;
+
+                       partial.params = _.extend(
+                               {
+                                       selector: null,
+                                       settings: [],
+                                       primarySetting: null,
+                                       containerInclusive: false,
+                                       fallbackRefresh: true // Note this needs to be false in a frontend editing context.
+                               },
+                               options.params || {}
+                       );
+
+                       partial.deferred = {};
+                       partial.deferred.ready = $.Deferred();
+
+                       partial.deferred.ready.done( function() {
+                               partial.ready();
+                       } );
+               },
+
+               /**
+                * Set up the partial.
+                *
+                * @since 4.5.0
+                */
+               ready: function() {
+                       var partial = this;
+                       _.each( _.pluck( partial.placements(), 'container' ), function( container ) {
+                               $( container ).attr( 'title', self.data.l10n.shiftClickToEdit );
+                       } );
+                       $( document ).on( 'click', partial.params.selector, function( e ) {
+                               if ( ! e.shiftKey ) {
+                                       return;
+                               }
+                               e.preventDefault();
+                               _.each( partial.placements(), function( placement ) {
+                                       if ( $( placement.container ).is( e.currentTarget ) ) {
+                                               partial.showControl();
+                                       }
+                               } );
+                       } );
+               },
+
+               /**
+                * Find all placements for this partial int he document.
+                *
+                * @since 4.5.0
+                *
+                * @return {Array.<Placement>}
+                */
+               placements: function() {
+                       var partial = this, selector;
+
+                       selector = partial.params.selector;
+                       if ( selector ) {
+                               selector += ', ';
+                       }
+                       selector += '[data-customize-partial-id="' + partial.id + '"]'; // @todo Consider injecting customize-partial-id-${id} classnames instead.
+
+                       return $( selector ).map( function() {
+                               var container = $( this ), context;
+
+                               context = container.data( 'customize-partial-placement-context' );
+                               if ( _.isString( context ) && '{' === context.substr( 0, 1 ) ) {
+                                       throw new Error( 'context JSON parse error' );
+                               }
+
+                               return new Placement( {
+                                       partial: partial,
+                                       container: container,
+                                       context: context
+                               } );
+                       } ).get();
+               },
+
+               /**
+                * Get list of setting IDs related to this partial.
+                *
+                * @since 4.5.0
+                *
+                * @return {String[]}
+                */
+               settings: function() {
+                       var partial = this;
+                       if ( partial.params.settings && 0 !== partial.params.settings.length ) {
+                               return partial.params.settings;
+                       } else if ( partial.params.primarySetting ) {
+                               return [ partial.params.primarySetting ];
+                       } else {
+                               return [ partial.id ];
+                       }
+               },
+
+               /**
+                * Return whether the setting is related to the partial.
+                *
+                * @since 4.5.0
+                *
+                * @param {wp.customize.Value|string} setting  ID or object for setting.
+                * @return {boolean} Whether the setting is related to the partial.
+                */
+               isRelatedSetting: function( setting /*... newValue, oldValue */ ) {
+                       var partial = this;
+                       if ( _.isString( setting ) ) {
+                               setting = api( setting );
+                       }
+                       if ( ! setting ) {
+                               return false;
+                       }
+                       return -1 !== _.indexOf( partial.settings(), setting.id );
+               },
+
+               /**
+                * Show the control to modify this partial's setting(s).
+                *
+                * This may be overridden for inline editing.
+                *
+                * @since 4.5.0
+                */
+               showControl: function() {
+                       var partial = this, settingId = partial.params.primarySetting;
+                       if ( ! settingId ) {
+                               settingId = _.first( partial.settings() );
+                       }
+                       api.preview.send( 'focus-control-for-setting', settingId );
+               },
+
+               /**
+                * Prepare container for selective refresh.
+                *
+                * @since 4.5.0
+                *
+                * @param {Placement} placement
+                */
+               preparePlacement: function( placement ) {
+                       $( placement.container ).addClass( 'customize-partial-refreshing' );
+               },
+
+               /**
+                * Reference to the pending promise returned from self.requestPartial().
+                *
+                * @since 4.5.0
+                * @private
+                */
+               _pendingRefreshPromise: null,
+
+               /**
+                * Request the new partial and render it into the placements.
+                *
+                * @since 4.5.0
+                *
+                * @this {wp.customize.selectiveRefresh.Partial}
+                * @return {jQuery.Promise}
+                */
+               refresh: function() {
+                       var partial = this, refreshPromise;
+
+                       refreshPromise = self.requestPartial( partial );
+
+                       if ( ! partial._pendingRefreshPromise ) {
+                               _.each( partial.placements(), function( placement ) {
+                                       partial.preparePlacement( placement );
+                               } );
+
+                               refreshPromise.done( function( placements ) {
+                                       _.each( placements, function( placement ) {
+                                               partial.renderContent( placement );
+                                       } );
+                               } );
+
+                               refreshPromise.fail( function( data, placements ) {
+                                       partial.fallback( data, placements );
+                               } );
+
+                               // Allow new request when this one finishes.
+                               partial._pendingRefreshPromise = refreshPromise;
+                               refreshPromise.always( function() {
+                                       partial._pendingRefreshPromise = null;
+                               } );
+                       }
+
+                       return refreshPromise;
+               },
+
+               /**
+                * Apply the addedContent in the placement to the document.
+                *
+                * Note the placement object will have its container and removedNodes
+                * properties updated.
+                *
+                * @since 4.5.0
+                *
+                * @param {Placement}             placement
+                * @param {Element|jQuery}        [placement.container]  - This param will be empty if there was no element matching the selector.
+                * @param {string|object|boolean} placement.addedContent - Rendered HTML content, a data object for JS templates to render, or false if no render.
+                * @param {object}                [placement.context]    - Optional context information about the container.
+                * @returns {boolean} Whether the rendering was successful and the fallback was not invoked.
+                */
+               renderContent: function( placement ) {
+                       var partial = this, content, newContainerElement, errorMessageElement;
+                       if ( ! placement.container ) {
+                               partial.fallback( new Error( 'no_container' ), [ placement ] );
+                               return false;
+                       }
+                       placement.container = $( placement.container );
+                       if ( false === placement.addedContent ) {
+                               partial.fallback( new Error( 'missing_render' ), [ placement ] );
+                               return false;
+                       }
+
+                       // Currently a subclass needs to override renderContent to handle partials returning data object.
+                       if ( ! _.isString( placement.addedContent ) ) {
+                               partial.fallback( new Error( 'non_string_content' ), [ placement ] );
+                               return false;
+                       }
+
+                       /* jshint ignore:start */
+                       self.orginalDocumentWrite = document.write;
+                       document.write = function() {
+                               throw new Error( self.data.l10n.badDocumentWrite );
+                       };
+                       /* jshint ignore:end */
+                       try {
+                               content = placement.addedContent;
+                               if ( wp.emoji && wp.emoji.parse && ! $.contains( document.head, placement.container[0] ) ) {
+                                       content = wp.emoji.parse( content );
+                               }
+
+                               if ( partial.params.containerInclusive ) {
+
+                                       // Note that content may be an empty string, and in this case jQuery will just remove the oldContainer
+                                       newContainerElement = $( content );
+
+                                       // Merge the new context on top of the old context.
+                                       placement.context = _.extend(
+                                               placement.context,
+                                               newContainerElement.data( 'customize-partial-placement-context' ) || {}
+                                       );
+                                       newContainerElement.data( 'customize-partial-placement-context', placement.context );
+
+                                       placement.removedNodes = placement.container;
+                                       placement.container = newContainerElement;
+                                       placement.removedNodes.replaceWith( placement.container );
+                                       placement.container.attr( 'title', self.data.l10n.shiftClickToEdit );
+                               } else {
+                                       placement.removedNodes = document.createDocumentFragment();
+                                       while ( placement.container[0].firstChild ) {
+                                               placement.removedNodes.appendChild( placement.container[0].firstChild );
+                                       }
+
+                                       placement.container.html( content );
+                               }
+
+                               placement.container.removeClass( 'customize-render-content-error' );
+                       } catch ( error ) {
+                               if ( 'undefined' !== typeof console && console.error ) {
+                                       console.error( partial.id, error );
+                               }
+                               placement.container.addClass( 'customize-render-content-error' );
+                               errorMessageElement = placement.container.find( '.customize-render-content-error-message:first' );
+                               if ( ! errorMessageElement.length ) {
+                                       errorMessageElement = $( '<span class="customize-render-content-error-message"><span>' );
+                                       placement.container.append( errorMessageElement );
+                               }
+                               errorMessageElement.text( self.data.l10n.errorMessageTpl.replace( '%s', error.message ) );
+                       }
+                       /* jshint ignore:start */
+                       document.write = self.orginalDocumentWrite;
+                       self.orginalDocumentWrite = null;
+                       /* jshint ignore:end */
+
+                       placement.container.removeClass( 'customize-partial-refreshing' );
+
+                       // Prevent placement container from being being re-triggered as being rendered among nested partials.
+                       placement.container.data( 'customize-partial-content-rendered', true );
+
+                       /**
+                        * Announce when a partial's placement has been rendered so that dynamic elements can be re-built.
+                        */
+                       self.trigger( 'partial-content-rendered', placement );
+                       return true;
+               },
+
+               /**
+                * Handle fail to render partial.
+                *
+                * The first argument is either the failing jqXHR or an Error object, and the second argument is the array of containers.
+                *
+                * @since 4.5.0
+                */
+               fallback: function() {
+                       var partial = this;
+                       if ( partial.params.fallbackRefresh ) {
+                               self.requestFullRefresh();
+                       }
+               }
+       } );
+
+       /**
+        * A Placement for a Partial.
+        *
+        * A partial placement is the actual physical representation of a partial for a given context.
+        * It also may have information in relation to how a placement may have just changed.
+        * The placement is conceptually similar to a DOM Range or MutationRecord.
+        *
+        * @class
+        * @augments wp.customize.Class
+        * @since 4.5.0
+        */
+       self.Placement = Placement = api.Class.extend({
+
+               /**
+                * The partial with which the container is associated.
+                *
+                * @param {wp.customize.selectiveRefresh.Partial}
+                */
+               partial: null,
+
+               /**
+                * DOM element which contains the placement's contents.
+                *
+                * This will be null if the startNode and endNode do not point to the same
+                * DOM element, such as in the case of a sidebar partial.
+                * This container element itself will be replaced for partials that
+                * have containerInclusive param defined as true.
+                */
+               container: null,
+
+               /**
+                * DOM node for the initial boundary of the placement.
+                *
+                * This will normally be the same as endNode since most placements appear as elements.
+                * This is primarily useful for widget sidebars which do not have intrinsic containers, but
+                * for which an HTML comment is output before to mark the starting position.
+                */
+               startNode: null,
+
+               /**
+                * DOM node for the terminal boundary of the placement.
+                *
+                * This will normally be the same as startNode since most placements appear as elements.
+                * This is primarily useful for widget sidebars which do not have intrinsic containers, but
+                * for which an HTML comment is output before to mark the ending position.
+                */
+               endNode: null,
+
+               /**
+                * Context data.
+                *
+                * This provides information about the placement which is included in the request
+                * in order to render the partial properly.
+                *
+                * @param {object}
+                */
+               context: null,
+
+               /**
+                * The content for the partial when refreshed.
+                *
+                * @param {string}
+                */
+               addedContent: null,
+
+               /**
+                * DOM node(s) removed when the partial is refreshed.
+                *
+                * If the partial is containerInclusive, then the removedNodes will be
+                * the single Element that was the partial's former placement. If the
+                * partial is not containerInclusive, then the removedNodes will be a
+                * documentFragment containing the nodes removed.
+                *
+                * @param {Element|DocumentFragment}
+                */
+               removedNodes: null,
+
+               /**
+                * Constructor.
+                *
+                * @since 4.5.0
+                *
+                * @param {object}                   args
+                * @param {Partial}                  args.partial
+                * @param {jQuery|Element}           [args.container]
+                * @param {Node}                     [args.startNode]
+                * @param {Node}                     [args.endNode]
+                * @param {object}                   [args.context]
+                * @param {string}                   [args.addedContent]
+                * @param {jQuery|DocumentFragment}  [args.removedNodes]
+                */
+               initialize: function( args ) {
+                       var placement = this;
+
+                       args = _.extend( {}, args || {} );
+                       if ( ! args.partial || ! args.partial.extended( Partial ) ) {
+                               throw new Error( 'Missing partial' );
+                       }
+                       args.context = args.context || {};
+                       if ( args.container ) {
+                               args.container = $( args.container );
+                       }
+
+                       _.extend( placement, args );
+               }
+
+       });
+
+       /**
+        * Mapping of type names to Partial constructor subclasses.
+        *
+        * @since 4.5.0
+        *
+        * @type {Object.<string, wp.customize.selectiveRefresh.Partial>}
+        */
+       self.partialConstructor = {};
+
+       self.partial = new api.Values({ defaultConstructor: Partial });
+
+       /**
+        * Get the POST vars for a Customizer preview request.
+        *
+        * @since 4.5.0
+        * @see wp.customize.previewer.query()
+        *
+        * @return {object}
+        */
+       self.getCustomizeQuery = function() {
+               var dirtyCustomized = {};
+               api.each( function( value, key ) {
+                       if ( value._dirty ) {
+                               dirtyCustomized[ key ] = value();
+                       }
+               } );
+
+               return {
+                       wp_customize: 'on',
+                       nonce: api.settings.nonce.preview,
+                       theme: api.settings.theme.stylesheet,
+                       customized: JSON.stringify( dirtyCustomized )
+               };
+       };
+
+       /**
+        * Currently-requested partials and their associated deferreds.
+        *
+        * @since 4.5.0
+        * @type {Object<string, { deferred: jQuery.Promise, partial: wp.customize.selectiveRefresh.Partial }>}
+        */
+       self._pendingPartialRequests = {};
+
+       /**
+        * Timeout ID for the current requesr, or null if no request is current.
+        *
+        * @since 4.5.0
+        * @type {number|null}
+        * @private
+        */
+       self._debouncedTimeoutId = null;
+
+       /**
+        * Current jqXHR for the request to the partials.
+        *
+        * @since 4.5.0
+        * @type {jQuery.jqXHR|null}
+        * @private
+        */
+       self._currentRequest = null;
+
+       /**
+        * Request full page refresh.
+        *
+        * When selective refresh is embedded in the context of frontend editing, this request
+        * must fail or else changes will be lost, unless transactions are implemented.
+        *
+        * @since 4.5.0
+        */
+       self.requestFullRefresh = function() {
+               api.preview.send( 'refresh' );
+       };
+
+       /**
+        * Request a re-rendering of a partial.
+        *
+        * @since 4.5.0
+        *
+        * @param {wp.customize.selectiveRefresh.Partial} partial
+        * @return {jQuery.Promise}
+        */
+       self.requestPartial = function( partial ) {
+               var partialRequest;
+
+               if ( self._debouncedTimeoutId ) {
+                       clearTimeout( self._debouncedTimeoutId );
+                       self._debouncedTimeoutId = null;
+               }
+               if ( self._currentRequest ) {
+                       self._currentRequest.abort();
+                       self._currentRequest = null;
+               }
+
+               partialRequest = self._pendingPartialRequests[ partial.id ];
+               if ( ! partialRequest || 'pending' !== partialRequest.deferred.state() ) {
+                       partialRequest = {
+                               deferred: $.Deferred(),
+                               partial: partial
+                       };
+                       self._pendingPartialRequests[ partial.id ] = partialRequest;
+               }
+
+               // Prevent leaking partial into debounced timeout callback.
+               partial = null;
+
+               self._debouncedTimeoutId = setTimeout(
+                       function() {
+                               var data, partialPlacementContexts, partialsPlacements, request;
+
+                               self._debouncedTimeoutId = null;
+                               data = self.getCustomizeQuery();
+
+                               /*
+                                * It is key that the containers be fetched exactly at the point of the request being
+                                * made, because the containers need to be mapped to responses by array indices.
+                                */
+                               partialsPlacements = {};
+
+                               partialPlacementContexts = {};
+
+                               _.each( self._pendingPartialRequests, function( pending, partialId ) {
+                                       partialsPlacements[ partialId ] = pending.partial.placements();
+                                       if ( ! self.partial.has( partialId ) ) {
+                                               pending.deferred.rejectWith( pending.partial, [ new Error( 'partial_removed' ), partialsPlacements[ partialId ] ] );
+                                       } else {
+                                               /*
+                                                * Note that this may in fact be an empty array. In that case, it is the responsibility
+                                                * of the Partial subclass instance to know where to inject the response, or else to
+                                                * just issue a refresh (default behavior). The data being returned with each container
+                                                * is the context information that may be needed to render certain partials, such as
+                                                * the contained sidebar for rendering widgets or what the nav menu args are for a menu.
+                                                */
+                                               partialPlacementContexts[ partialId ] = _.map( partialsPlacements[ partialId ], function( placement ) {
+                                                       return placement.context || {};
+                                               } );
+                                       }
+                               } );
+
+                               data.partials = JSON.stringify( partialPlacementContexts );
+                               data[ self.data.renderQueryVar ] = '1';
+
+                               request = self._currentRequest = wp.ajax.send( null, {
+                                       data: data,
+                                       url: api.settings.url.self
+                               } );
+
+                               request.done( function( data ) {
+
+                                       /**
+                                        * Announce the data returned from a request to render partials.
+                                        *
+                                        * The data is filtered on the server via customize_render_partials_response
+                                        * so plugins can inject data from the server to be utilized
+                                        * on the client via this event. Plugins may use this filter
+                                        * to communicate script and style dependencies that need to get
+                                        * injected into the page to support the rendered partials.
+                                        * This is similar to the 'saved' event.
+                                        */
+                                       self.trigger( 'render-partials-response', data );
+
+                                       // Relay errors (warnings) captured during rendering and relay to console.
+                                       if ( data.errors && 'undefined' !== typeof console && console.warn ) {
+                                               _.each( data.errors, function( error ) {
+                                                       console.warn( error );
+                                               } );
+                                       }
+
+                                       /*
+                                        * Note that data is an array of items that correspond to the array of
+                                        * containers that were submitted in the request. So we zip up the
+                                        * array of containers with the array of contents for those containers,
+                                        * and send them into .
+                                        */
+                                       _.each( self._pendingPartialRequests, function( pending, partialId ) {
+                                               var placementsContents;
+                                               if ( ! _.isArray( data.contents[ partialId ] ) ) {
+                                                       pending.deferred.rejectWith( pending.partial, [ new Error( 'unrecognized_partial' ), partialsPlacements[ partialId ] ] );
+                                               } else {
+                                                       placementsContents = _.map( data.contents[ partialId ], function( content, i ) {
+                                                               var partialPlacement = partialsPlacements[ partialId ][ i ];
+                                                               if ( partialPlacement ) {
+                                                                       partialPlacement.addedContent = content;
+                                                               } else {
+                                                                       partialPlacement = new Placement( {
+                                                                               partial: pending.partial,
+                                                                               addedContent: content
+                                                                       } );
+                                                               }
+                                                               return partialPlacement;
+                                                       } );
+                                                       pending.deferred.resolveWith( pending.partial, [ placementsContents ] );
+                                               }
+                                       } );
+                                       self._pendingPartialRequests = {};
+                               } );
+
+                               request.fail( function( data, statusText ) {
+
+                                       /*
+                                        * Ignore failures caused by partial.currentRequest.abort()
+                                        * The pending deferreds will remain in self._pendingPartialRequests
+                                        * for re-use with the next request.
+                                        */
+                                       if ( 'abort' === statusText ) {
+                                               return;
+                                       }
+
+                                       _.each( self._pendingPartialRequests, function( pending, partialId ) {
+                                               pending.deferred.rejectWith( pending.partial, [ data, partialsPlacements[ partialId ] ] );
+                                       } );
+                                       self._pendingPartialRequests = {};
+                               } );
+                       },
+                       self.data.refreshBuffer
+               );
+
+               return partialRequest.deferred.promise();
+       };
+
+       /**
+        * Add partials for any nav menu container elements in the document.
+        *
+        * This method may be called multiple times. Containers that already have been
+        * seen will be skipped.
+        *
+        * @since 4.5.0
+        *
+        * @param {jQuery|HTMLElement} [rootElement]
+        * @param {object}             [options]
+        * @param {boolean=true}       [options.triggerRendered]
+        */
+       self.addPartials = function( rootElement, options ) {
+               var containerElements;
+               if ( ! rootElement ) {
+                       rootElement = document.documentElement;
+               }
+               rootElement = $( rootElement );
+               options = _.extend(
+                       {
+                               triggerRendered: true
+                       },
+                       options || {}
+               );
+
+               containerElements = rootElement.find( '[data-customize-partial-id]' );
+               if ( rootElement.is( '[data-customize-partial-id]' ) ) {
+                       containerElements = containerElements.add( rootElement );
+               }
+               containerElements.each( function() {
+                       var containerElement = $( this ), partial, id, Constructor, partialOptions, containerContext;
+                       id = containerElement.data( 'customize-partial-id' );
+                       if ( ! id ) {
+                               return;
+                       }
+                       containerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
+
+                       partial = self.partial( id );
+                       if ( ! partial ) {
+                               partialOptions = containerElement.data( 'customize-partial-options' ) || {};
+                               partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
+                               Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
+                               partial = new Constructor( id, partialOptions );
+                               self.partial.add( partial.id, partial );
+                       }
+
+                       /*
+                        * Only trigger renders on (nested) partials that have been not been
+                        * handled yet. An example where this would apply is a nav menu
+                        * embedded inside of a custom menu widget. When the widget's title
+                        * is updated, the entire widget will re-render and then the event
+                        * will be triggered for the nested nav menu to do any initialization.
+                        */
+                       if ( options.triggerRendered && ! containerElement.data( 'customize-partial-content-rendered' ) ) {
+
+                               /**
+                                * Announce when a partial's nested placement has been re-rendered.
+                                */
+                               self.trigger( 'partial-content-rendered', new Placement( {
+                                       partial: partial,
+                                       context: containerContext,
+                                       container: containerElement
+                               } ) );
+                       }
+                       containerElement.data( 'customize-partial-content-rendered', true );
+               } );
+       };
+
+       api.bind( 'preview-ready', function() {
+               var handleSettingChange, watchSettingChange, unwatchSettingChange;
+
+               // Polyfill for IE8 to support the document.head attribute.
+               if ( ! document.head ) {
+                       document.head = $( 'head:first' )[0];
+               }
+
+               _.extend( self.data, _customizePartialRefreshExports );
+
+               // Create the partial JS models.
+               _.each( self.data.partials, function( data, id ) {
+                       var Constructor, partial = self.partial( id );
+                       if ( ! partial ) {
+                               Constructor = self.partialConstructor[ data.type ] || self.Partial;
+                               partial = new Constructor( id, { params: data } );
+                               self.partial.add( id, partial );
+                       } else {
+                               _.extend( partial.params, data );
+                       }
+               } );
+
+               /**
+                * Handle change to a setting.
+                *
+                * Note this is largely needed because adding a 'change' event handler to wp.customize
+                * will only include the changed setting object as an argument, not including the
+                * new value or the old value.
+                *
+                * @since 4.5.0
+                * @this {wp.customize.Setting}
+                *
+                * @param {*|null} newValue New value, or null if the setting was just removed.
+                * @param {*|null} oldValue Old value, or null if the setting was just added.
+                */
+               handleSettingChange = function( newValue, oldValue ) {
+                       var setting = this;
+                       self.partial.each( function( partial ) {
+                               if ( partial.isRelatedSetting( setting, newValue, oldValue ) ) {
+                                       partial.refresh();
+                               }
+                       } );
+               };
+
+               /**
+                * Trigger the initial change for the added setting, and watch for changes.
+                *
+                * @since 4.5.0
+                * @this {wp.customize.Values}
+                *
+                * @param {wp.customize.Setting} setting
+                */
+               watchSettingChange = function( setting ) {
+                       handleSettingChange.call( setting, setting(), null );
+                       setting.bind( handleSettingChange );
+               };
+
+               /**
+                * Trigger the final change for the removed setting, and unwatch for changes.
+                *
+                * @since 4.5.0
+                * @this {wp.customize.Values}
+                *
+                * @param {wp.customize.Setting} setting
+                */
+               unwatchSettingChange = function( setting ) {
+                       handleSettingChange.call( setting, null, setting() );
+                       setting.unbind( handleSettingChange );
+               };
+
+               api.bind( 'add', watchSettingChange );
+               api.bind( 'remove', unwatchSettingChange );
+               api.each( function( setting ) {
+                       setting.bind( handleSettingChange );
+               } );
+
+               // Add (dynamic) initial partials that are declared via data-* attributes.
+               self.addPartials( document.documentElement, {
+                       triggerRendered: false
+               } );
+
+               // Add new dynamic partials when the document changes.
+               if ( 'undefined' !== typeof MutationObserver ) {
+                       self.mutationObserver = new MutationObserver( function( mutations ) {
+                               _.each( mutations, function( mutation ) {
+                                       self.addPartials( $( mutation.target ) );
+                               } );
+                       } );
+                       self.mutationObserver.observe( document.documentElement, {
+                               childList: true,
+                               subtree: true
+                       } );
+               }
+
+               /**
+                * Handle rendering of partials.
+                *
+                * @param {api.selectiveRefresh.Placement} placement
+                */
+               api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
+                       if ( placement.container ) {
+                               self.addPartials( placement.container );
+                       }
+               } );
+
+               api.preview.bind( 'active', function() {
+
+                       // Make all partials ready.
+                       self.partial.each( function( partial ) {
+                               partial.deferred.ready.resolve();
+                       } );
+
+                       // Make all partials added henceforth as ready upon add.
+                       self.partial.bind( 'add', function( partial ) {
+                               partial.deferred.ready.resolve();
+                       } );
+               } );
+
+       } );
+
+       return self;
+}( jQuery, wp.customize ) );
</ins></span></pre></div>
<a id="trunksrcwpincludesscriptloaderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/script-loader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/script-loader.php   2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/src/wp-includes/script-loader.php     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -447,6 +447,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Used for overriding the file types allowed in plupload.
</span><span class="cx" style="display: block; padding: 0 10px">                'allowedFiles'       => __( 'Allowed Files' ),
</span><span class="cx" style="display: block; padding: 0 10px">        ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
</span><span class="cx" style="display: block; padding: 0 10px">        $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/manager.php   2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/tests/phpunit/tests/customize/manager.php     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -425,7 +425,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $data = json_decode( $json, true );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNotEmpty( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices' ), array_keys( $data ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'selectiveRefreshEnabled' ), array_keys( $data ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertEquals( $autofocus, $data['autofocus'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayHasKey( 'save', $data['nonce'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayHasKey( 'preview', $data['nonce'] );
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizenavmenuitemsettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/nav-menu-item-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/nav-menu-item-setting.php     2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/tests/phpunit/tests/customize/nav-menu-item-setting.php       2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -69,7 +69,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, 'nav_menu_item[123]' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( 'nav_menu_item', $setting->type );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( 'postMessage', $setting->transport );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->assertEquals( 123, $setting->post_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNull( $setting->previous_post_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNull( $setting->update_status );
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizenavmenusphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/nav-menus.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/nav-menus.php 2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/tests/phpunit/tests/customize/nav-menus.php   2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -353,11 +353,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $expected = array( 'type' => 'nav_menu_item' );
</span><span class="cx" style="display: block; padding: 0 10px">                $results = $menus->filter_dynamic_setting_args( $this->wp_customize, 'nav_menu_item[123]' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( $expected, $results );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $expected['type'], $results['type'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $expected = array( 'type' => 'nav_menu' );
</span><span class="cx" style="display: block; padding: 0 10px">                $results = $menus->filter_dynamic_setting_args( $this->wp_customize, 'nav_menu[123]' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( $expected, $results );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $expected['type'], $results['type'] );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -523,6 +523,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Test WP_Customize_Nav_Menus::customize_dynamic_partial_args().
+        *
+        * @see WP_Customize_Nav_Menus::customize_dynamic_partial_args()
+        */
+       function test_customize_dynamic_partial_args() {
+               do_action( 'customize_register', $this->wp_customize );
+
+               $args = apply_filters( 'customize_dynamic_partial_args', false, 'nav_menu_instance[68b329da9893e34099c7d8ad5cb9c940]' );
+               $this->assertInternalType( 'array', $args );
+               $this->assertEquals( 'nav_menu_instance', $args['type'] );
+               $this->assertEquals( array( $this->wp_customize->nav_menus, 'render_nav_menu_partial' ), $args['render_callback'] );
+               $this->assertTrue( $args['container_inclusive'] );
+
+               $args = apply_filters( 'customize_dynamic_partial_args', array( 'fallback_refresh' => false ), 'nav_menu_instance[4099c7d8ad5cb9c94068b329da9893e3]' );
+               $this->assertInternalType( 'array', $args );
+               $this->assertEquals( 'nav_menu_instance', $args['type'] );
+               $this->assertEquals( array( $this->wp_customize->nav_menus, 'render_nav_menu_partial' ), $args['render_callback'] );
+               $this->assertTrue( $args['container_inclusive'] );
+               $this->assertFalse( $args['fallback_refresh'] );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Test the customize_preview_init method.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @see WP_Customize_Nav_Menus::customize_preview_init()
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -532,13 +554,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $menus->customize_preview_init();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( 10, has_action( 'template_redirect', array( $menus, 'render_menu' ) ) );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $menus, 'customize_preview_enqueue_deps' ) ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               if ( ! isset( $_REQUEST[ WP_Customize_Nav_Menus::RENDER_QUERY_VAR ] ) ) {
-                       $this->assertEquals( 1000, has_filter( 'wp_nav_menu_args', array( $menus, 'filter_wp_nav_menu_args' ) ) );
-                       $this->assertEquals( 10, has_filter( 'wp_nav_menu', array( $menus, 'filter_wp_nav_menu' ) ) );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( 1000, has_filter( 'wp_nav_menu_args', array( $menus, 'filter_wp_nav_menu_args' ) ) );
+               $this->assertEquals( 10, has_filter( 'wp_nav_menu', array( $menus, 'filter_wp_nav_menu' ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -548,37 +566,36 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        function test_filter_wp_nav_menu_args() {
</span><span class="cx" style="display: block; padding: 0 10px">                do_action( 'customize_register', $this->wp_customize );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $menus = $this->wp_customize->nav_menus;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $results = $menus->filter_wp_nav_menu_args( array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'echo'            => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'fallback_cb'     => 'wp_page_menu',
</span><span class="cx" style="display: block; padding: 0 10px">                        'walker'          => '',
</span><span class="cx" style="display: block; padding: 0 10px">                        'menu'            => wp_create_nav_menu( 'Foo' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( 1, $results['can_partial_refresh'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected = array(
-                       'echo',
-                       'can_partial_refresh',
-                       'fallback_cb',
-                       'instance_number',
-                       'walker',
-               );
</del><span class="cx" style="display: block; padding: 0 10px">                 $results = $menus->filter_wp_nav_menu_args( array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'echo'            => false,
</span><span class="cx" style="display: block; padding: 0 10px">                        'fallback_cb'     => 'wp_page_menu',
</span><span class="cx" style="display: block; padding: 0 10px">                        'walker'          => new Walker_Nav_Menu(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEqualSets( $expected, array_keys( $results ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertArrayNotHasKey( 'customize_preview_nav_menus_args', $results );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertEquals( 'wp_page_menu', $results['fallback_cb'] );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( 0, $results['can_partial_refresh'] );
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNotEmpty( $menus->preview_nav_menu_instance_args[ $results['instance_number'] ] );
-               $preview_nav_menu_instance_args = $menus->preview_nav_menu_instance_args[ $results['instance_number'] ];
-               $this->assertEquals( '', $preview_nav_menu_instance_args['fallback_cb'] );
-               $this->assertEquals( '', $preview_nav_menu_instance_args['walker'] );
-               $this->assertNotEmpty( $preview_nav_menu_instance_args['args_hash'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $nav_menu_term = get_term( wp_create_nav_menu( 'Bar' ) );
+               $results = $menus->filter_wp_nav_menu_args( array(
+                       'echo'            => true,
+                       'fallback_cb'     => 'wp_page_menu',
+                       'walker'          => '',
+                       'menu'            => $nav_menu_term,
+                       'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
+               ) );
+               $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results );
+               $this->assertEquals( $nav_menu_term->term_id, $results['customize_preview_nav_menus_args']['menu'] );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -595,19 +612,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'menu'        => wp_create_nav_menu( 'Foo' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'fallback_cb' => 'wp_page_menu',
</span><span class="cx" style="display: block; padding: 0 10px">                        'walker'      => '',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'items_wrap'  => '<ul id="%1$s" class="%2$s">%3$s</ul>',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                ob_start();
</span><span class="cx" style="display: block; padding: 0 10px">                wp_nav_menu( $args );
</span><span class="cx" style="display: block; padding: 0 10px">                $nav_menu_content = ob_get_clean();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $object_args = json_decode( json_encode( $args ), false );
-               $result = $menus->filter_wp_nav_menu( $nav_menu_content, $object_args );
-               $expected = sprintf(
-                       '<div class="partial-refreshable-nav-menu partial-refreshable-nav-menu-%1$d menu">',
-                       $args['instance_number']
-               );
-               $this->assertStringStartsWith( $expected, $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $result = $menus->filter_wp_nav_menu( $nav_menu_content, (object) $args );
+
+               $this->assertContains( sprintf( ' data-customize-partial-id="nav_menu_instance[%s]"', $args['customize_preview_nav_menus_args']['args_hmac'] ), $result );
+               $this->assertContains( ' data-customize-partial-type="nav_menu_instance"', $result );
+               $this->assertContains( ' data-customize-partial-placement-context="', $result );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -622,31 +638,62 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $menus->customize_preview_enqueue_deps();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( wp_script_is( 'customize-preview-nav-menus' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEquals( 10, has_action( 'wp_print_footer_scripts', array( $menus, 'export_preview_data' ) ) );
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Test the export_preview_data method.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Test WP_Customize_Nav_Menus::export_preview_data() method.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @see WP_Customize_Nav_Menus::export_preview_data()
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        function test_export_preview_data() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                do_action( 'customize_register', $this->wp_customize );
-               $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->setExpectedDeprecated( 'WP_Customize_Nav_Menus::export_preview_data' );
+               $this->wp_customize->nav_menus->export_preview_data();
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $request_uri = $_SERVER['REQUEST_URI'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Test WP_Customize_Nav_Menus::render_nav_menu_partial() method.
+        *
+        * @see WP_Customize_Nav_Menus::render_nav_menu_partial()
+        */
+       function test_render_nav_menu_partial() {
+               $this->wp_customize->nav_menus->customize_preview_init();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                ob_start();
-               $_SERVER['REQUEST_URI'] = '/wp-admin';
-               $menus->export_preview_data();
-               $data = ob_get_clean();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $menu = wp_create_nav_menu( 'Foo' );
+               wp_update_nav_menu_item( $menu, 0, array(
+                       'menu-item-type' => 'custom',
+                       'menu-item-title' => 'WordPress.org',
+                       'menu-item-url' => 'https://wordpress.org',
+                       'menu-item-status' => 'publish',
+               ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $_SERVER['REQUEST_URI'] = $request_uri;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $nav_menu_args = $this->wp_customize->nav_menus->filter_wp_nav_menu_args( array(
+                       'echo'        => true,
+                       'menu'        => $menu,
+                       'fallback_cb' => 'wp_page_menu',
+                       'walker'      => '',
+                       'items_wrap'  => '<ul id="%1$s" class="%2$s">%3$s</ul>',
+               ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertContains( '_wpCustomizePreviewNavMenusExports', $data );
-               $this->assertContains( 'renderQueryVar', $data );
-               $this->assertContains( 'renderNonceValue', $data );
-               $this->assertContains( 'renderNoncePostKey', $data );
-               $this->assertContains( 'navMenuInstanceArgs', $data );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $partial_id = sprintf( 'nav_menu_instance[%s]', $nav_menu_args['customize_preview_nav_menus_args']['args_hmac'] );
+               $partials = $this->wp_customize->selective_refresh->add_dynamic_partials( array( $partial_id ) );
+               $this->assertNotEmpty( $partials );
+               $partial = array_shift( $partials );
+               $this->assertEquals( $partial_id, $partial->id );
+
+               $missing_args_hmac_args = array_merge(
+                       $nav_menu_args['customize_preview_nav_menus_args'],
+                       array( 'args_hmac' => null )
+               );
+               $this->assertFalse( $partial->render( $missing_args_hmac_args ) );
+
+               $args_hmac_mismatch_args = array_merge(
+                       $nav_menu_args['customize_preview_nav_menus_args'],
+                       array( 'args_hmac' => strrev( $nav_menu_args['customize_preview_nav_menus_args']['args_hmac'] ) )
+               );
+               $this->assertFalse( $partial->render( $args_hmac_mismatch_args ) );
+
+               $rendered = $partial->render( $nav_menu_args['customize_preview_nav_menus_args'] );
+               $this->assertContains( 'data-customize-partial-type="nav_menu_instance"', $rendered );
+               $this->assertContains( 'WordPress.org', $rendered );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizepartialphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/customize/partial.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/partial.php                           (rev 0)
+++ trunk/tests/phpunit/tests/customize/partial.php     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,311 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Test_WP_Customize_Partial tests.
+ *
+ * @package WordPress
+ */
+
+/**
+ * Tests for the Test_WP_Customize_Partial class.
+ *
+ * @group customize
+ */
+class Test_WP_Customize_Partial extends WP_UnitTestCase {
+
+       /**
+        * Manager.
+        *
+        * @var WP_Customize_Manager
+        */
+       public $wp_customize;
+
+       /**
+        * Component.
+        *
+        * @var WP_Customize_Selective_Refresh
+        */
+       public $selective_refresh;
+
+       /**
+        * Set up.
+        */
+       function setUp() {
+               parent::setUp();
+               require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
+               // @codingStandardsIgnoreStart
+               $GLOBALS['wp_customize'] = new WP_Customize_Manager();
+               // @codingStandardsIgnoreEnd
+               $this->wp_customize = $GLOBALS['wp_customize'];
+               if ( isset( $this->wp_customize->selective_refresh ) ) {
+                       $this->selective_refresh = $this->wp_customize->selective_refresh;
+               }
+       }
+
+       /**
+        * Test WP_Customize_Partial::__construct().
+        *
+        * @see WP_Customize_Partial::__construct()
+        */
+       function test_construct_default_args() {
+               $partial_id = 'blogname';
+               $partial = new WP_Customize_Partial( $this->selective_refresh, $partial_id );
+               $this->assertEquals( $partial_id, $partial->id );
+               $this->assertEquals( $this->selective_refresh, $partial->component );
+               $this->assertEquals( 'default', $partial->type );
+               $this->assertEmpty( $partial->selector );
+               $this->assertEquals( array( $partial_id ), $partial->settings );
+               $this->assertEquals( $partial_id, $partial->primary_setting );
+               $this->assertEquals( array( $partial, 'render_callback' ), $partial->render_callback );
+               $this->assertEquals( false, $partial->container_inclusive );
+               $this->assertEquals( true, $partial->fallback_refresh );
+       }
+
+       /**
+        * Render post content partial.
+        *
+        * @param WP_Customize_Partial $partial Partial.
+        * @return string|false Content or false if error.
+        */
+       function render_post_content_partial( $partial ) {
+               $id_data = $partial->id_data();
+               $post_id = intval( $id_data['keys'][0] );
+               if ( empty( $post_id ) ) {
+                       return false;
+               }
+               $post = get_post( $post_id );
+               if ( ! $post ) {
+                       return false;
+               }
+               return apply_filters( 'the_content', $post->post_content );
+       }
+
+       /**
+        * Test WP_Customize_Partial::__construct().
+        *
+        * @see WP_Customize_Partial::__construct()
+        */
+       function test_construct_non_default_args() {
+
+               $post_id = self::factory()->post->create( array(
+                       'post_title' => 'Hello World',
+                       'post_content' => 'Lorem Ipsum',
+               ) );
+
+               $partial_id = sprintf( 'post_content[%d]', $post_id );
+               $args = array(
+                       'type' => 'post',
+                       'selector' => "article.post-$post_id .entry-content",
+                       'settings' => array( 'user[1]', "post[$post_id]" ),
+                       'primary_setting' => "post[$post_id]",
+                       'render_callback' => array( $this, 'render_post_content_partial' ),
+                       'container_inclusive' => false,
+                       'fallback_refresh' => false,
+               );
+               $partial = new WP_Customize_Partial( $this->selective_refresh, $partial_id, $args );
+               $this->assertEquals( $partial_id, $partial->id );
+               $this->assertEquals( $this->selective_refresh, $partial->component );
+               $this->assertEquals( $args['type'], $partial->type );
+               $this->assertEquals( $args['selector'], $partial->selector );
+               $this->assertEqualSets( $args['settings'], $partial->settings );
+               $this->assertEquals( $args['primary_setting'], $partial->primary_setting );
+               $this->assertEquals( $args['render_callback'], $partial->render_callback );
+               $this->assertEquals( false, $partial->container_inclusive );
+               $this->assertEquals( false, $partial->fallback_refresh );
+               $this->assertContains( 'Lorem Ipsum', $partial->render() );
+
+               $partial = new WP_Customize_Partial( $this->selective_refresh, $partial_id, array(
+                       'settings' => 'blogdescription',
+               ) );
+               $this->assertEquals( array( 'blogdescription' ), $partial->settings );
+               $this->assertEquals( 'blogdescription', $partial->primary_setting );
+       }
+
+       /**
+        * Test WP_Customize_Partial::id_data().
+        *
+        * @see WP_Customize_Partial::id_data()
+        */
+       function test_id_data() {
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'foo' );
+               $id_data = $partial->id_data();
+               $this->assertEquals( 'foo', $id_data['base'] );
+               $this->assertEquals( array(), $id_data['keys'] );
+
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'bar[baz][quux]' );
+               $id_data = $partial->id_data();
+               $this->assertEquals( 'bar', $id_data['base'] );
+               $this->assertEquals( array( 'baz', 'quux' ), $id_data['keys'] );
+       }
+
+       /**
+        * Keep track of filter calls to customize_partial_render.
+        *
+        * @var int
+        */
+       protected $count_filter_customize_partial_render = 0;
+
+       /**
+        * Keep track of filter calls to customize_partial_render_{$partial->id}.
+        *
+        * @var int
+        */
+       protected $count_filter_customize_partial_render_with_id = 0;
+
+       /**
+        * Filter customize_partial_render.
+        *
+        * @param string|false         $rendered          Content.
+        * @param WP_Customize_Partial $partial           Partial.
+        * @param array                $container_context Data.
+        * @return string|false Content.
+        */
+       function filter_customize_partial_render( $rendered, $partial, $container_context ) {
+               $this->assertTrue( false === $rendered || is_string( $rendered ) );
+               $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
+               $this->assertInternalType( 'array', $container_context );
+               $this->count_filter_customize_partial_render += 1;
+               return $rendered;
+       }
+
+       /**
+        * Filter customize_partial_render_{$partial->id}.
+        *
+        * @param string|false         $rendered          Content.
+        * @param WP_Customize_Partial $partial           Partial.
+        * @param array                $container_context Data.
+        * @return string|false Content.
+        */
+       function filter_customize_partial_render_with_id( $rendered, $partial, $container_context ) {
+               $this->assertEquals( sprintf( 'customize_partial_render_%s', $partial->id ), current_filter() );
+               $this->assertTrue( false === $rendered || is_string( $rendered ) );
+               $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
+               $this->assertInternalType( 'array', $container_context );
+               $this->count_filter_customize_partial_render_with_id += 1;
+               return $rendered;
+       }
+
+       /**
+        * Bad render_callback().
+        *
+        * @return string Content.
+        */
+       function render_echo_and_return() {
+               echo 'foo';
+               return 'bar';
+       }
+
+       /**
+        * Echo render_callback().
+        */
+       function render_echo() {
+               echo 'foo';
+       }
+
+       /**
+        * Return render_callback().
+        *
+        * @return string Content.
+        */
+       function render_return() {
+               return 'bar';
+       }
+
+       /**
+        * Test WP_Customize_Partial::render() with a bad return_callback.
+        *
+        * @see WP_Customize_Partial::render()
+        */
+       function test_render_bad_callback() {
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'foo', array(
+                       'render_callback' => array( $this, 'render_echo_and_return' ),
+               ) );
+               $this->setExpectedIncorrectUsage( 'render' );
+               $partial->render();
+       }
+
+       /**
+        * Test WP_Customize_Partial::render() with a return_callback that echos.
+        *
+        * @see WP_Customize_Partial::render()
+        */
+       function test_render_echo_callback() {
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'foo', array(
+                       'render_callback' => array( $this, 'render_echo' ),
+               ) );
+               $count_filter_customize_partial_render = $this->count_filter_customize_partial_render;
+               $count_filter_customize_partial_render_with_id = $this->count_filter_customize_partial_render_with_id;
+               add_filter( 'customize_partial_render', array( $this, 'filter_customize_partial_render' ), 10, 3 );
+               add_filter( "customize_partial_render_{$partial->id}", array( $this, 'filter_customize_partial_render_with_id' ), 10, 3 );
+               $rendered = $partial->render();
+               $this->assertEquals( 'foo', $rendered );
+               $this->assertEquals( $count_filter_customize_partial_render + 1, $this->count_filter_customize_partial_render );
+               $this->assertEquals( $count_filter_customize_partial_render_with_id + 1, $this->count_filter_customize_partial_render_with_id );
+       }
+
+       /**
+        * Test WP_Customize_Partial::render() with a return_callback that echos.
+        *
+        * @see WP_Customize_Partial::render()
+        */
+       function test_render_return_callback() {
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'foo', array(
+                       'render_callback' => array( $this, 'render_return' ),
+               ) );
+               $count_filter_customize_partial_render = $this->count_filter_customize_partial_render;
+               $count_filter_customize_partial_render_with_id = $this->count_filter_customize_partial_render_with_id;
+               add_filter( 'customize_partial_render', array( $this, 'filter_customize_partial_render' ), 10, 3 );
+               add_filter( "customize_partial_render_{$partial->id}", array( $this, 'filter_customize_partial_render_with_id' ), 10, 3 );
+               $rendered = $partial->render();
+               $this->assertEquals( 'bar', $rendered );
+               $this->assertEquals( $count_filter_customize_partial_render + 1, $this->count_filter_customize_partial_render );
+               $this->assertEquals( $count_filter_customize_partial_render_with_id + 1, $this->count_filter_customize_partial_render_with_id );
+       }
+
+       /**
+        * Test WP_Customize_Partial::render_callback() default.
+        *
+        * @see WP_Customize_Partial::render_callback()
+        */
+       function test_render_callback_default() {
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'foo' );
+               $this->assertFalse( $partial->render_callback() );
+               $this->assertFalse( call_user_func( $partial->render_callback ) );
+       }
+
+       /**
+        * Test WP_Customize_Partial::json() default.
+        *
+        * @see WP_Customize_Partial::json()
+        */
+       function test_json() {
+               $post_id = 123;
+               $partial_id = sprintf( 'post_content[%d]', $post_id );
+               $args = array(
+                       'type' => 'post',
+                       'selector' => "article.post-$post_id .entry-content",
+                       'settings' => array( 'user[1]', "post[$post_id]" ),
+                       'primary_setting' => "post[$post_id]",
+                       'render_callback' => array( $this, 'render_post_content_partial' ),
+                       'container_inclusive' => false,
+                       'fallback_refresh' => false,
+               );
+               $partial = new WP_Customize_Partial( $this->selective_refresh, $partial_id, $args );
+
+               $exported = $partial->json();
+               $this->assertArrayHasKey( 'settings', $exported );
+               $this->assertArrayHasKey( 'primarySetting', $exported );
+               $this->assertArrayHasKey( 'selector', $exported );
+               $this->assertArrayHasKey( 'type', $exported );
+               $this->assertArrayHasKey( 'fallbackRefresh', $exported );
+               $this->assertArrayHasKey( 'containerInclusive', $exported );
+       }
+
+       /**
+        * Tear down.
+        */
+       function tearDown() {
+               $this->wp_customize = null;
+               unset( $GLOBALS['wp_customize'] );
+               parent::tearDown();
+       }
+}
</ins></span></pre></div>
<a id="trunktestsphpunittestscustomizeselectiverefreshajaxphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/customize/selective-refresh-ajax.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/selective-refresh-ajax.php                            (rev 0)
+++ trunk/tests/phpunit/tests/customize/selective-refresh-ajax.php      2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,449 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WP_Customize_Selective_Refresh Ajax tests.
+ *
+ * @package    WordPress
+ * @subpackage UnitTests
+ */
+
+/**
+ * Tests for the WP_Customize_Selective_Refresh class Ajax.
+ *
+ * Note that this is intentionally not extending WP_Ajax_UnitTestCase because it
+ * is not admin ajax.
+ *
+ * @since      4.5.0
+ * @group      ajax
+ */
+class Test_WP_Customize_Selective_Refresh_Ajax extends WP_UnitTestCase {
+
+       /**
+        * Manager.
+        *
+        * @var WP_Customize_Manager
+        */
+       public $wp_customize;
+
+       /**
+        * Component.
+        *
+        * @var WP_Customize_Selective_Refresh
+        */
+       public $selective_refresh;
+
+       /**
+        * Set up the test fixture.
+        */
+       function setUp() {
+               parent::setUp();
+
+               // Define DOING_AJAX so that wp_die() will be used instead of die().
+               if ( ! defined( 'DOING_AJAX' ) ) {
+                       define( 'DOING_AJAX', true );
+               }
+               add_filter( 'wp_die_ajax_handler', array( $this, 'get_wp_die_handler' ), 1, 1 );
+
+               require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
+               // @codingStandardsIgnoreStart
+               $GLOBALS['wp_customize'] = new WP_Customize_Manager();
+               // @codingStandardsIgnoreEnd
+               $this->wp_customize = $GLOBALS['wp_customize'];
+               if ( isset( $this->wp_customize->selective_refresh ) ) {
+                       $this->selective_refresh = $this->wp_customize->selective_refresh;
+               }
+
+       }
+
+       /**
+        * Do Customizer boot actions.
+        */
+       function do_customize_boot_actions() {
+               // Remove actions that call add_theme_support( 'title-tag' ).
+               remove_action( 'after_setup_theme', 'twentyfifteen_setup' );
+               remove_action( 'after_setup_theme', 'twentysixteen_setup' );
+
+               $_SERVER['REQUEST_METHOD'] = 'POST';
+               do_action( 'setup_theme' );
+               do_action( 'after_setup_theme' );
+               do_action( 'init' );
+               do_action( 'customize_register', $this->wp_customize );
+               $this->wp_customize->customize_preview_init();
+               do_action( 'wp', $GLOBALS['wp'] );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request().
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_for_unauthenticated_user() {
+               $_POST[ WP_Customize_Selective_Refresh::RENDER_QUERY_VAR ] = '1';
+
+               // Check current_user_cannot_customize.
+               ob_start();
+               try {
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       unset( $e );
+               }
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertFalse( $output['success'] );
+               $this->assertEquals( 'expected_customize_preview', $output['data'] );
+
+               // Check expected_customize_preview.
+               wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
+               $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->wp_customize->theme()->get_stylesheet() );
+               ob_start();
+               try {
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       unset( $e );
+               }
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertFalse( $output['success'] );
+               $this->assertEquals( 'expected_customize_preview', $output['data'] );
+
+               // Check missing_partials.
+               $this->do_customize_boot_actions();
+               ob_start();
+               try {
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       unset( $e );
+               }
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertFalse( $output['success'] );
+               $this->assertEquals( 'missing_partials', $output['data'] );
+
+               // Check missing_partials.
+               $_POST['partials'] = 'bad';
+               $this->do_customize_boot_actions();
+               ob_start();
+               try {
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertFalse( $output['success'] );
+               $this->assertEquals( 'malformed_partials', $output['data'] );
+       }
+
+       /**
+        * Set the current user to be an admin, add the preview nonce, and set the query var.
+        */
+       function setup_valid_render_partials_request_environment() {
+               wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
+               $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->wp_customize->theme()->get_stylesheet() );
+               $_POST[ WP_Customize_Selective_Refresh::RENDER_QUERY_VAR ] = '1';
+               $this->do_customize_boot_actions();
+       }
+
+       /**
+        * Make sure that the Customizer "signature" is not included in partial render responses.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_removes_customize_signature() {
+               $this->setup_valid_render_partials_request_environment();
+               $this->assertTrue( is_customize_preview() );
+               $this->assertEquals( 1000, has_action( 'shutdown', array( $this->wp_customize, 'customize_preview_signature' ) ) );
+               ob_start();
+               try {
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       unset( $e );
+               }
+               ob_end_clean();
+               $this->assertFalse( has_action( 'shutdown', array( $this->wp_customize, 'customize_preview_signature' ) ) );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for an unrecognized partial.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_for_unrecognized_partial() {
+               $this->setup_valid_render_partials_request_environment();
+               $context_data = array();
+               $placements = array( $context_data );
+
+               $_POST['partials'] = wp_slash( wp_json_encode( array(
+                       'foo' => $placements,
+               ) ) );
+
+               ob_start();
+               try {
+                       $this->expected_partial_ids = array( 'foo' );
+                       add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
+                       add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
+                       add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertTrue( $output['success'] );
+               $this->assertInternalType( 'array', $output['data'] );
+               $this->assertArrayHasKey( 'contents', $output['data'] );
+               $this->assertArrayHasKey( 'errors', $output['data'] );
+               $this->assertArrayHasKey( 'foo', $output['data']['contents'] );
+               $this->assertEquals( null, $output['data']['contents']['foo'] );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial that does not render.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_for_non_rendering_partial() {
+               $this->setup_valid_render_partials_request_environment();
+               $this->wp_customize->selective_refresh->add_partial( 'foo', array( 'settings' => array( 'home' ) ) );
+               $context_data = array();
+               $placements = array( $context_data );
+
+               $_POST['partials'] = wp_slash( wp_json_encode( array(
+                       'foo' => $placements,
+               ) ) );
+
+               $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
+               $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
+               ob_start();
+               try {
+                       $this->expected_partial_ids = array( 'foo' );
+                       add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
+                       add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
+                       add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
+               $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertEquals( array( false ), $output['data']['contents']['foo'] );
+       }
+
+       /**
+        * Get the rendered blogname.
+        *
+        * @param WP_Customize_Partial $partial Partial.
+        * @param array                $context Context data.
+        * @return string
+        */
+       function render_callback_blogname( $partial, $context ) {
+               $this->assertInternalType( 'array', $context );
+               $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
+               return get_bloginfo( 'name', 'display' );
+       }
+
+       /**
+        * Get the rendered blogdescription.
+        *
+        * @param WP_Customize_Partial $partial Partial.
+        * @param array                $context Context data.
+        * @return string
+        */
+       function render_callback_blogdescription( $partial, $context ) {
+               $this->assertInternalType( 'array', $context );
+               $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
+               $x = get_bloginfo( 'description', 'display' );
+               return $x;
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial that does render.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_with_single_valid_placement() {
+               $this->setup_valid_render_partials_request_environment();
+
+               $this->wp_customize->selective_refresh->add_partial( 'test_blogname', array(
+                       'settings' => array( 'blogname' ),
+                       'render_callback' => array( $this, 'render_callback_blogname' ),
+               ) );
+
+               $context_data = array();
+               $placements = array( $context_data );
+
+               $_POST['partials'] = wp_slash( wp_json_encode( array(
+                       'test_blogname' => $placements,
+               ) ) );
+
+               $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
+               $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
+               ob_start();
+               try {
+                       $this->expected_partial_ids = array( 'test_blogname' );
+                       add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
+                       add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
+                       add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
+               $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertEquals( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_blogname'] );
+       }
+
+       /**
+        * Filter customize_dynamic_partial_args.
+        *
+        * @param array  $partial_args Partial args.
+        * @param string $partial_id   Partial ID.
+        *
+        * @return array|false Args.
+        */
+       function filter_customize_dynamic_partial_args( $partial_args, $partial_id ) {
+               if ( 'test_dynamic_blogname' === $partial_id ) {
+                       $partial_args = array(
+                               'settings' => array( 'blogname' ),
+                               'render_callback' => array( $this, 'render_callback_blogname' ),
+                       );
+               }
+               return $partial_args;
+       }
+
+       /**
+        * Filter customize_render_partials_response.
+        *
+        * @param array                          $response            Response.
+        * @param WP_Customize_Selective_Refresh $component Selective refresh component.
+        * @param array                          $partial_placements  Placements' context data for the partials rendered in the request.
+        *                                                            The array is keyed by partial ID, with each item being an array of
+        *                                                            the placements' context data.
+        * @return array Response.
+        */
+       function filter_customize_render_partials_response( $response, $component, $partial_placements ) {
+               $this->assertInternalType( 'array', $response );
+               $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
+               if ( isset( $this->expected_partial_ids ) ) {
+                       $this->assertEqualSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
+               }
+               return $response;
+       }
+
+       /**
+        * Expected partial IDs.
+        *
+        * @var array
+        */
+       protected $expected_partial_ids;
+
+       /**
+        * Handle 'customize_render_partials_before' action.
+        *
+        * @param WP_Customize_Selective_Refresh $component          Selective refresh component.
+        * @param array                          $partial_placements Partial IDs.
+        */
+       function handle_action_customize_render_partials_after( $component, $partial_placements ) {
+               $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
+               if ( isset( $this->expected_partial_ids ) ) {
+                       $this->assertEqualSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
+               }
+       }
+
+       /**
+        * Handle 'customize_render_partials_after' action.
+        *
+        * @param WP_Customize_Selective_Refresh $component          Selective refresh component.
+        * @param array                          $partial_placements Partial IDs.
+        */
+       function handle_action_customize_render_partials_before( $component, $partial_placements ) {
+               $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
+               if ( isset( $this->expected_partial_ids ) ) {
+                       $this->assertEqualSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
+               }
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request()dynamic partials are recognized.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_for_dynamic_partial() {
+               $this->setup_valid_render_partials_request_environment();
+               add_filter( 'customize_dynamic_partial_args', array( $this, 'filter_customize_dynamic_partial_args' ), 10, 2 );
+
+               $context_data = array();
+               $placements = array( $context_data );
+
+               $_POST['partials'] = wp_slash( wp_json_encode( array(
+                       'test_dynamic_blogname' => $placements,
+               ) ) );
+
+               $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
+               $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
+               ob_start();
+               try {
+                       $this->expected_partial_ids = array( 'test_dynamic_blogname' );
+                       add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
+                       add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
+                       add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
+               $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertEquals( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_dynamic_blogname'] );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::handle_render_partials_request() to multiple partials can be requested at once.
+        *
+        * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
+        */
+       function test_handle_render_partials_request_for_multiple_partials_placements() {
+               $this->setup_valid_render_partials_request_environment();
+
+               $this->wp_customize->selective_refresh->add_partial( 'test_blogname', array(
+                       'settings' => array( 'blogname' ),
+                       'render_callback' => array( $this, 'render_callback_blogname' ),
+               ) );
+               $this->wp_customize->selective_refresh->add_partial( 'test_blogdescription', array(
+                       'settings' => array( 'blogdescription' ),
+                       'render_callback' => array( $this, 'render_callback_blogdescription' ),
+               ) );
+
+               $placement_context_data = array();
+
+               $_POST['partials'] = wp_slash( wp_json_encode( array(
+                       'test_blogname' => array( $placement_context_data ),
+                       'test_blogdescription' => array( $placement_context_data, $placement_context_data ),
+               ) ) );
+
+               $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
+               $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
+               ob_start();
+               try {
+                       $this->expected_partial_ids = array( 'test_blogname', 'test_blogdescription' );
+                       add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
+                       add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
+                       add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
+                       $this->selective_refresh->handle_render_partials_request();
+               } catch ( WPDieException $e ) {
+                       $this->assertEquals( '', $e->getMessage() );
+               }
+               $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
+               $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
+               $output = json_decode( ob_get_clean(), true );
+               $this->assertEquals( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_blogname'] );
+               $this->assertEquals( array_fill( 0, 2, get_bloginfo( 'description', 'display' ) ), $output['data']['contents']['test_blogdescription'] );
+       }
+
+       /**
+        * Tear down.
+        */
+       function tearDown() {
+               $this->expected_partial_ids = null;
+               $this->wp_customize = null;
+               unset( $GLOBALS['wp_customize'] );
+               unset( $GLOBALS['wp_scripts'] );
+               parent::tearDown();
+       }
+}
</ins></span></pre></div>
<a id="trunktestsphpunittestscustomizeselectiverefreshphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/customize/selective-refresh.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/selective-refresh.php                         (rev 0)
+++ trunk/tests/phpunit/tests/customize/selective-refresh.php   2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,260 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WP_Customize_Selective_Refresh tests.
+ *
+ * @package WordPress
+ */
+
+/**
+ * Tests for the WP_Customize_Selective_Refresh class.
+ *
+ * @group customize
+ */
+class Test_WP_Customize_Selective_Refresh extends WP_UnitTestCase {
+
+       /**
+        * Manager.
+        *
+        * @var WP_Customize_Manager
+        */
+       public $wp_customize;
+
+       /**
+        * Component.
+        *
+        * @var WP_Customize_Selective_Refresh
+        */
+       public $selective_refresh;
+
+       /**
+        * Set up the test fixture.
+        */
+       function setUp() {
+               parent::setUp();
+               require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
+               // @codingStandardsIgnoreStart
+               $GLOBALS['wp_customize'] = new WP_Customize_Manager();
+               // @codingStandardsIgnoreEnd
+               $this->wp_customize = $GLOBALS['wp_customize'];
+               if ( isset( $this->wp_customize->selective_refresh ) ) {
+                       $this->selective_refresh = $this->wp_customize->selective_refresh;
+               }
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::__construct().
+        *
+        * @see WP_Customize_Selective_Refresh::__construct()
+        */
+       function test_construct() {
+               $this->assertEquals( $this->selective_refresh, $this->wp_customize->selective_refresh );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::register_scripts().
+        *
+        * @see WP_Customize_Selective_Refresh::register_scripts()
+        */
+       function test_register_scripts() {
+               $scripts = new WP_Scripts();
+               $handles = array(
+                       'customize-selective-refresh',
+                       'customize-preview-nav-menus',
+                       'customize-preview-widgets',
+               );
+               foreach ( $handles as $handle ) {
+                       $this->assertArrayHasKey( $handle, $scripts->registered );
+               }
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::partials().
+        *
+        * @see WP_Customize_Selective_Refresh::partials()
+        */
+       function test_partials() {
+               $this->assertInternalType( 'array', $this->selective_refresh->partials() );
+       }
+
+       /**
+        * Test CRUD methods for partials.
+        *
+        * @see WP_Customize_Selective_Refresh::get_partial()
+        * @see WP_Customize_Selective_Refresh::add_partial()
+        * @see WP_Customize_Selective_Refresh::remove_partial()
+        */
+       function test_crud_partial() {
+               $partial = $this->selective_refresh->add_partial( 'foo' );
+               $this->assertEquals( $this->selective_refresh, $partial->component );
+               $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
+               $this->assertEquals( $partial, $this->selective_refresh->get_partial( $partial->id ) );
+               $this->assertArrayHasKey( $partial->id, $this->selective_refresh->partials() );
+
+               $this->selective_refresh->remove_partial( $partial->id );
+               $this->assertEmpty( $this->selective_refresh->get_partial( $partial->id ) );
+               $this->assertArrayNotHasKey( $partial->id, $this->selective_refresh->partials() );
+
+               $partial = new WP_Customize_Partial( $this->selective_refresh, 'bar' );
+               $this->assertEquals( $partial, $this->selective_refresh->add_partial( $partial ) );
+               $this->assertEquals( $partial, $this->selective_refresh->get_partial( 'bar' ) );
+               $this->assertEqualSets( array( 'bar' ), array_keys( $this->selective_refresh->partials() ) );
+
+               add_filter( 'customize_dynamic_partial_args', array( $this, 'filter_customize_dynamic_partial_args' ), 10, 2 );
+               add_filter( 'customize_dynamic_partial_class', array( $this, 'filter_customize_dynamic_partial_class' ), 10, 3 );
+
+               $partial = $this->selective_refresh->add_partial( 'recognized-class' );
+               $this->assertInstanceOf( 'Tested_Custom_Partial', $partial );
+               $this->assertEquals( '.recognized', $partial->selector );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::init_preview().
+        *
+        * @see WP_Customize_Selective_Refresh::init_preview()
+        */
+       function test_init_preview() {
+               $this->selective_refresh->init_preview();
+               $this->assertEquals( 10, has_action( 'template_redirect', array( $this->selective_refresh, 'handle_render_partials_request' ) ) );
+               $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $this->selective_refresh, 'enqueue_preview_scripts' ) ) );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::enqueue_preview_scripts().
+        *
+        * @see WP_Customize_Selective_Refresh::enqueue_preview_scripts()
+        */
+       function test_enqueue_preview_scripts() {
+               $scripts = wp_scripts();
+               $this->assertNotContains( 'customize-selective-refresh', $scripts->queue );
+               $this->selective_refresh->enqueue_preview_scripts();
+               $this->assertContains( 'customize-selective-refresh', $scripts->queue );
+               $this->assertEquals( 1000, has_action( 'wp_footer', array( $this->selective_refresh, 'export_preview_data' ) ) );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::export_preview_data().
+        *
+        * @see WP_Customize_Selective_Refresh::export_preview_data()
+        */
+       function test_export_preview_data() {
+               $this->selective_refresh->add_partial( 'blogname', array(
+                       'selector' => '#site-title',
+               ) );
+               ob_start();
+               $this->selective_refresh->export_preview_data();
+               $html = ob_get_clean();
+               $this->assertTrue( (bool) preg_match( '/_customizePartialRefreshExports = ({.+})/s', $html, $matches ) );
+               $exported_data = json_decode( $matches[1], true );
+               $this->assertInternalType( 'array', $exported_data );
+               $this->assertArrayHasKey( 'partials', $exported_data );
+               $this->assertInternalType( 'array', $exported_data['partials'] );
+               $this->assertArrayHasKey( 'blogname', $exported_data['partials'] );
+               $this->assertEquals( '#site-title', $exported_data['partials']['blogname']['selector'] );
+               $this->assertArrayHasKey( 'renderQueryVar', $exported_data );
+               $this->assertArrayHasKey( 'l10n', $exported_data );
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::add_dynamic_partials().
+        *
+        * @see WP_Customize_Selective_Refresh::add_dynamic_partials()
+        */
+       function test_add_dynamic_partials() {
+               $partial_ids = array( 'recognized', 'recognized-class', 'unrecognized', 'already-added' );
+
+               $partials = $this->selective_refresh->add_dynamic_partials( $partial_ids );
+               $this->assertEmpty( $partials );
+
+               $this->selective_refresh->add_partial( 'already-added' );
+
+               add_filter( 'customize_dynamic_partial_args', array( $this, 'filter_customize_dynamic_partial_args' ), 10, 2 );
+               add_filter( 'customize_dynamic_partial_class', array( $this, 'filter_customize_dynamic_partial_class' ), 10, 3 );
+
+               $partials = $this->selective_refresh->add_dynamic_partials( $partial_ids );
+               $this->assertEqualSets( array( 'recognized', 'recognized-class' ), wp_list_pluck( $partials, 'id' ) );
+
+               $this->assertInstanceOf( 'Tested_Custom_Partial', $this->selective_refresh->get_partial( 'recognized-class' ) );
+               $this->assertNotInstanceOf( 'Tested_Custom_Partial', $this->selective_refresh->get_partial( 'recognized' ) );
+               $this->assertEquals( '.recognized', $this->selective_refresh->get_partial( 'recognized' )->selector );
+       }
+
+       /**
+        * Filter customize_dynamic_partial_args.
+        *
+        * @see Test_WP_Customize_Selective_Refresh::test_add_dynamic_partials()
+        *
+        * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
+        * @param string      $partial_id   ID for dynamic partial.
+        * @return false|array $args Dynamic partial args.
+        */
+       function filter_customize_dynamic_partial_args( $partial_args, $partial_id ) {
+               $this->assertTrue( false === $partial_args || is_array( $partial_args ) );
+               $this->assertInternalType( 'string', $partial_id );
+
+               if ( preg_match( '/^recognized/', $partial_id ) ) {
+                       $partial_args = array(
+                               'selector' => '.recognized',
+                       );
+               }
+
+               return $partial_args;
+       }
+
+       /**
+        * Filter customize_dynamic_partial_class.
+        *
+        * @see Test_WP_Customize_Selective_Refresh::test_add_dynamic_partials()
+        *
+        * @param string $partial_class WP_Customize_Partial or a subclass.
+        * @param string $partial_id    ID for dynamic partial.
+        * @param array  $partial_args  The arguments to the WP_Customize_Partial constructor.
+        * @return string
+        */
+       function filter_customize_dynamic_partial_class( $partial_class, $partial_id, $partial_args ) {
+               $this->assertInternalType( 'array', $partial_args );
+               $this->assertInternalType( 'string', $partial_id );
+               $this->assertInternalType( 'string', $partial_class );
+
+               if ( 'recognized-class' === $partial_id ) {
+                       $partial_class = 'Tested_Custom_Partial';
+               }
+
+               return $partial_class;
+       }
+
+       /**
+        * Test WP_Customize_Selective_Refresh::is_render_partials_request().
+        *
+        * @see WP_Customize_Selective_Refresh::is_render_partials_request()
+        */
+       function test_is_render_partials_request() {
+               $this->assertFalse( $this->selective_refresh->is_render_partials_request() );
+               $_POST[ WP_Customize_Selective_Refresh::RENDER_QUERY_VAR ] = '1';
+               $this->assertTrue( $this->selective_refresh->is_render_partials_request() );
+       }
+
+       /**
+        * Tear down.
+        */
+       function tearDown() {
+               $this->wp_customize = null;
+               unset( $GLOBALS['wp_customize'] );
+               unset( $GLOBALS['wp_scripts'] );
+               parent::tearDown();
+       }
+}
+
+require_once ABSPATH . WPINC . '/customize/class-wp-customize-partial.php';
+
+/**
+ * Class Tested_Custom_Partial
+ */
+class Tested_Custom_Partial extends WP_Customize_Partial {
+
+       /**
+        * Type.
+        *
+        * @var string
+        */
+       public $type = 'custom';
+}
</ins></span></pre></div>
<a id="trunktestsphpunittestscustomizewidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/widgets.php   2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/tests/phpunit/tests/customize/widgets.php     2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -39,6 +39,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // @todo We should not be including a theme anyway
</span><span class="cx" style="display: block; padding: 0 10px">                remove_action( 'after_setup_theme', 'twentyfifteen_setup' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_action( 'after_setup_theme', 'twentysixteen_setup' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_action( 'customize_register', 'twentysixteen_customize_register', 11 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                wp_set_current_user( $user_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -121,7 +122,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $default_args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type' => 'option',
</span><span class="cx" style="display: block; padding: 0 10px">                        'capability' => 'edit_theme_options',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'transport' => 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'transport' => 'postMessage',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'default' => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -150,7 +151,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $default_args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type' => 'option',
</span><span class="cx" style="display: block; padding: 0 10px">                        'capability' => 'edit_theme_options',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'transport' => 'refresh',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'transport' => 'postMessage',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'default' => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'sanitize_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets_js_instance' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -346,4 +347,146 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayHasKey( 'is_widget_customizer_js_value', $post_value );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( $post_value, $this->manager->widgets->sanitize_widget_js_instance( $instance ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Test WP_Customize_Widgets::customize_dynamic_partial_args().
+        *
+        * @see WP_Customize_Widgets::customize_dynamic_partial_args()
+        */
+       function test_customize_dynamic_partial_args() {
+               do_action( 'customize_register', $this->manager );
+
+               $args = apply_filters( 'customize_dynamic_partial_args', false, 'widget[search-2]' );
+               $this->assertInternalType( 'array', $args );
+               $this->assertEquals( 'widget', $args['type'] );
+               $this->assertEquals( array( $this->manager->widgets, 'render_widget_partial' ), $args['render_callback'] );
+               $this->assertTrue( $args['container_inclusive'] );
+
+               $args = apply_filters( 'customize_dynamic_partial_args', array( 'fallback_refresh' => false ), 'widget[search-2]' );
+               $this->assertInternalType( 'array', $args );
+               $this->assertEquals( 'widget', $args['type'] );
+               $this->assertEquals( array( $this->manager->widgets, 'render_widget_partial' ), $args['render_callback'] );
+               $this->assertTrue( $args['container_inclusive'] );
+               $this->assertFalse( $args['fallback_refresh'] );
+       }
+
+       /**
+        * Test WP_Customize_Widgets::selective_refresh_init().
+        *
+        * @see WP_Customize_Widgets::selective_refresh_init()
+        */
+       function test_selective_refresh_init() {
+               $this->manager->widgets->selective_refresh_init();
+               $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $this->manager->widgets, 'customize_preview_enqueue_deps' ) ) );
+               $this->assertEquals( 10, has_action( 'dynamic_sidebar_before', array( $this->manager->widgets, 'start_dynamic_sidebar' ) ) );
+               $this->assertEquals( 10, has_action( 'dynamic_sidebar_after', array( $this->manager->widgets, 'end_dynamic_sidebar' ) ) );
+               $this->assertEquals( 10, has_filter( 'dynamic_sidebar_params', array( $this->manager->widgets, 'filter_dynamic_sidebar_params' ) ) );
+               $this->assertEquals( 10, has_filter( 'wp_kses_allowed_html', array( $this->manager->widgets, 'filter_wp_kses_allowed_data_attributes' ) ) );
+       }
+
+       /**
+        * Test WP_Customize_Widgets::customize_preview_enqueue_deps().
+        *
+        * @see WP_Customize_Widgets::customize_preview_enqueue_deps()
+        */
+       function test_customize_preview_enqueue_deps() {
+               $this->manager->widgets->customize_preview_enqueue_deps();
+               $this->assertTrue( wp_script_is( 'customize-preview-widgets', 'enqueued' ) );
+               $this->assertTrue( wp_style_is( 'customize-preview', 'enqueued' ) );
+               $script = wp_scripts()->registered['customize-preview-widgets'];
+               $this->assertContains( 'customize-selective-refresh', $script->deps );
+       }
+
+       /**
+        * Test extensions to dynamic_sidebar().
+        *
+        * @see WP_Customize_Widgets::filter_dynamic_sidebar_params()
+        * @see WP_Customize_Widgets::start_dynamic_sidebar()
+        * @see WP_Customize_Widgets::end_dynamic_sidebar()
+        */
+       function test_filter_dynamic_sidebar_params() {
+               global $wp_registered_sidebars;
+               register_sidebar( array(
+                       'id' => 'foo',
+               ) );
+
+               $this->manager->widgets->selective_refresh_init();
+
+               $params = array(
+                       array_merge(
+                               $wp_registered_sidebars['foo'],
+                               array(
+                                       'widget_id' => 'search-2',
+                               )
+                       ),
+                       array(),
+               );
+               $this->assertEquals( $params, $this->manager->widgets->filter_dynamic_sidebar_params( $params ), 'Expected short-circuit if not called after dynamic_sidebar_before.' );
+
+               ob_start();
+               do_action( 'dynamic_sidebar_before', 'foo' );
+               $output = ob_get_clean();
+               $this->assertEquals( '<!--dynamic_sidebar_before:foo:1-->', trim( $output ) );
+
+               $bad_params = $params;
+               unset( $bad_params[0]['id'] );
+               $this->assertEquals( $bad_params, $this->manager->widgets->filter_dynamic_sidebar_params( $bad_params ) );
+
+               $bad_params = $params;
+               $bad_params[0]['id'] = 'non-existing';
+               $this->assertEquals( $bad_params, $this->manager->widgets->filter_dynamic_sidebar_params( $bad_params ) );
+
+               $bad_params = $params;
+               $bad_params[0]['before_widget'] = '   <oops>';
+               $this->assertEquals( $bad_params, $this->manager->widgets->filter_dynamic_sidebar_params( $bad_params ) );
+
+               $filtered_params = $this->manager->widgets->filter_dynamic_sidebar_params( $params );
+               $this->assertNotEquals( $params, $filtered_params );
+               ob_start();
+               do_action( 'dynamic_sidebar_after', 'foo' );
+               $output = ob_get_clean();
+               $this->assertEquals( '<!--dynamic_sidebar_after:foo:1-->', trim( $output ) );
+
+               $output = wp_kses_post( $filtered_params[0]['before_widget'] );
+               $this->assertContains( 'data-customize-partial-id="widget[search-2]"', $output );
+               $this->assertContains( 'data-customize-partial-type="widget"', $output );
+       }
+
+       /**
+        * Test WP_Customize_Widgets::render_widget_partial() method.
+        *
+        * @see WP_Customize_Widgets::render_widget_partial()
+        */
+       function test_render_widget_partial() {
+               $this->manager->widgets->selective_refresh_init();
+
+               $partial_id = 'widget[search-2]';
+               $partials = $this->manager->selective_refresh->add_dynamic_partials( array( $partial_id ) );
+               $this->assertNotEmpty( $partials );
+               $partial = array_shift( $partials );
+               $this->assertEquals( $partial_id, $partial->id );
+
+               $this->assertFalse( $this->manager->widgets->render_widget_partial( $partial, array() ) );
+               $this->assertFalse( $this->manager->widgets->render_widget_partial( $partial, array( 'sidebar_id' => 'non-existing' ) ) );
+
+               $output = $this->manager->widgets->render_widget_partial( $partial, array( 'sidebar_id' => 'sidebar-1' ) );
+
+               $this->assertEquals( 1, substr_count( $output, 'data-customize-partial-id' ) );
+               $this->assertEquals( 1, substr_count( $output, 'data-customize-partial-type="widget"' ) );
+               $this->assertContains( ' id="search-2"', $output );
+       }
+
+       /**
+        * Test deprecated methods.
+        */
+       public function test_deprecated_methods() {
+               $this->setExpectedDeprecated( 'WP_Customize_Widgets::setup_widget_addition_previews' );
+               $this->setExpectedDeprecated( 'WP_Customize_Widgets::prepreview_added_sidebars_widgets' );
+               $this->setExpectedDeprecated( 'WP_Customize_Widgets::prepreview_added_widget_instance' );
+               $this->setExpectedDeprecated( 'WP_Customize_Widgets::remove_prepreview_filters' );
+               $this->manager->widgets->setup_widget_addition_previews();
+               $this->manager->widgets->prepreview_added_sidebars_widgets();
+               $this->manager->widgets->prepreview_added_widget_instance();
+               $this->manager->widgets->remove_prepreview_filters();
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsqunitfixturescustomizemenusjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/fixtures/customize-menus.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/fixtures/customize-menus.js     2016-02-19 16:08:51 UTC (rev 36585)
+++ trunk/tests/qunit/fixtures/customize-menus.js       2016-02-19 18:40:06 UTC (rev 36586)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,7 +2,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> window._wpCustomizeNavMenusSettings = {
</span><span class="cx" style="display: block; padding: 0 10px">        'nonce': 'yo',
</span><span class="cx" style="display: block; padding: 0 10px">        'phpIntMax': '2147483647',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        'menuItemTransport': 'postMessage',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'settingTransport': 'postMessage',
</ins><span class="cx" style="display: block; padding: 0 10px">         'allMenus': [{
</span><span class="cx" style="display: block; padding: 0 10px">                'term_id': '2',
</span><span class="cx" style="display: block; padding: 0 10px">                'name': 'Social Menu',
</span></span></pre>
</div>
</div>

</body>
</html>