Index: src/wp-includes/class-wp-customize-control.php
===================================================================
--- src/wp-includes/class-wp-customize-control.php	(revision 32795)
+++ src/wp-includes/class-wp-customize-control.php	(working copy)
@@ -1402,3 +1402,431 @@
 		return $this->manager->widgets->is_widget_rendered( $this->widget_id );
 	}
 }
+
+/**
+ * Customize Menu Panel Class
+ *
+ * Needed to add screen options.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Menus_Panel extends WP_Customize_Panel {
+
+	/**
+	 * Control type.
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'menus';
+
+	/**
+	 * Render screen options for Menus.
+	 */
+	public function render_screen_options() {
+		// Essentially adds the screen options.
+		add_filter( 'manage_nav-menus_columns', array( $this, 'wp_nav_menu_manage_columns' ) );
+
+		// Display screen options.
+		$screen = WP_Screen::get( 'nav-menus.php' );
+		$screen->render_screen_options();
+	}
+
+	/**
+	 * Returns the advanced options for the nav menus page.
+	 *
+	 * Link title attribute added as it's a relatively advanced concept for new users.
+	 *
+	 * @since 4.3.0
+	 *
+	 * @return array The advanced menu properties.
+	 */
+	function wp_nav_menu_manage_columns() {
+		return array(
+			'_title'      => __( 'Show advanced menu properties' ),
+			'cb'          => '<input type="checkbox" />',
+			'link-target' => __( 'Link Target' ),
+			'attr-title'  => __( 'Title Attribute' ),
+			'css-classes' => __( 'CSS Classes' ),
+			'xfn'         => __( 'Link Relationship (XFN)' ),
+			'description' => __( 'Description' ),
+		);
+	}
+
+	/**
+	 * An Underscore (JS) template for this panel's content (but not its container).
+	 *
+	 * Class variables for this panel class are available in the `data` JS object;
+	 * export custom variables by overriding {@see WP_Customize_Panel::json()}.
+	 *
+	 * @see WP_Customize_Panel::print_template()
+	 *
+	 * @since 4.3.0
+	 */
+	protected function content_template() {
+		?>
+		<li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>">
+			<button type="button" class="customize-panel-back" tabindex="-1">
+				<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
+			</button>
+			<div class="accordion-section-title">
+				<span class="preview-notice">
+					<?php
+						/* translators: %s is the site/panel title in the Customizer */
+						printf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
+					?>
+				</span>
+				<button type="button" class="customize-screen-options-toggle" aria-expanded="false">
+					<span class="screen-reader-text"><?php _e( 'Menu Options' ); ?></span>
+				</button>
+				<button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false">
+					<span class="screen-reader-text"><?php _e( 'Help' ); ?></span>
+				</button>
+			</div>
+			<# if ( data.description ) { #>
+			<div class="description customize-panel-description">{{{ data.description }}}</div>
+			<# } #>
+			<?php $this->render_screen_options(); ?>
+		</li>
+		<?php
+	}
+}
+
+/**
+ * Customize Nav Menu Control Class
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Nav_Menu_Control extends WP_Customize_Control {
+
+	/**
+	 * Control type
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'nav_menu';
+
+	/**
+	 * The nav menu setting
+	 *
+	 * @var WP_Customize_Nav_Menu_Setting
+	 */
+	public $setting;
+
+	/**
+	 * Don't render the control's content - it uses a JS template instead.
+	 */
+	public function render_content() {}
+
+	/**
+	 * JS/Underscore template for the control UI.
+	 */
+	public function content_template() {
+		?>
+		<button type="button" class="button-secondary add-new-menu-item">
+			<?php _e( 'Add Items' ); ?>
+		</button>
+		<button type="button" class="not-a-button reorder-toggle">
+			<span class="reorder"><?php _ex( 'Reorder', 'Reorder menu items in Customizer' ); ?></span>
+			<span class="reorder-done"><?php _ex( 'Done', 'Cancel reordering menu items in Customizer' ); ?></span>
+		</button>
+		<span class="add-menu-item-loading spinner"></span>
+		<span class="menu-delete-item">
+			<button type="button" class="not-a-button menu-delete">
+				<?php _e( 'Delete menu' ); ?> <span class="screen-reader-text">{{ data.menu_name }}</span>
+			</button>
+		</span>
+		<?php if ( current_theme_supports( 'menus' ) ) : ?>
+		<ul class="menu-settings">
+			<li class="customize-control">
+				<span class="customize-control-title"><?php _e( 'Menu locations' ); ?></span>
+			</li>
+
+			<?php foreach ( get_registered_nav_menus() as $location => $description ) : ?>
+			<li class="customize-control customize-control-checkbox assigned-menu-location">
+				<label>
+					<input type="checkbox" data-menu-id="{{ data.menu_id }}" data-location-id="<?php echo esc_attr( $location ); ?>" class="menu-location" /> <?php echo $description; ?>
+					<span class="theme-location-set"><?php printf( _x( '(Current: %s)', 'Current menu location' ), '<span class="current-menu-location-name-' . esc_attr( $location ) . '"></span>' ); ?></span>
+				</label>
+			</li>
+			<?php endforeach; ?>
+
+		</ul>
+		<?php endif; ?>
+		<p>
+			<label>
+				<input type="checkbox" class="auto_add">
+				<?php _e( 'Automatically add new top-level pages to this menu.' ) ?>
+			</label>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Return params for this control.
+	 *
+	 * @return array
+	 */
+	function json() {
+		$exported = parent::json();
+		$exported['menu_id'] = $this->setting->term_id;
+		return $exported;
+	}
+}
+
+/**
+ * Customize control to represent the name field for a given menu.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control {
+
+	/**
+	 * Control type
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'nav_menu_item';
+
+	/**
+	 * The nav menu item setting
+	 *
+	 * @var WP_Customize_Nav_Menu_Item_Setting
+	 */
+	public $setting;
+
+	/**
+	 * Constructor.
+	 *
+	 * @uses WP_Customize_Control::__construct()
+	 *
+	 * @param WP_Customize_Manager $manager An instance of the WP_Customize_Manager class.
+	 * @param string               $id      The control ID.
+	 * @param array                $args    Optional. Overrides class property defaults.
+	 */
+	public function __construct( $manager, $id, $args = array() ) {
+		parent::__construct( $manager, $id, $args );
+	}
+
+	/**
+	 * Don't render the control's content - it's rendered with a JS template.
+	 */
+	public function render_content() {}
+
+	/**
+	 * JS/Underscore template for the control UI.
+	 */
+	public function content_template() {
+		?>
+		<dl class="menu-item-bar">
+			<dt class="menu-item-handle">
+				<span class="item-type">{{ data.item_type_label }}</span>
+				<span class="item-title">
+					<span class="spinner"></span>
+					<span class="menu-item-title">{{ data.title }}</span>
+				</span>
+				<span class="item-controls">
+					<button type="button" class="not-a-button item-edit"><span class="screen-reader-text"><?php _e( 'Edit Menu Item' ); ?></span></button>
+					<button type="button" class="not-a-button item-delete submitdelete deletion"><span class="screen-reader-text"><?php _e( 'Remove Menu Item' ); ?></span></button>
+				</span>
+			</dt>
+		</dl>
+
+		<div class="menu-item-settings" id="menu-item-settings-{{ data.menu_item_id }}">
+			<# if ( 'custom' === data.item_type ) { #>
+			<p class="field-url description description-thin">
+				<label for="edit-menu-item-url-{{ data.menu_item_id }}">
+					<?php _e( 'URL' ); ?><br />
+					<input class="widefat code edit-menu-item-url" type="text" id="edit-menu-item-url-{{ data.menu_item_id }}" name="menu-item-url" />
+				</label>
+			</p>
+		<# } #>
+			<p class="description description-thin">
+				<label for="edit-menu-item-title-{{ data.menu_item_id }}">
+					<?php _e( 'Navigation Label' ); ?><br />
+					<input type="text" id="edit-menu-item-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-title" name="menu-item-title" />
+				</label>
+			</p>
+			<p class="field-link-target description description-thin">
+				<label for="edit-menu-item-target-{{ data.menu_item_id }}">
+					<input type="checkbox" id="edit-menu-item-target-{{ data.menu_item_id }}" class="edit-menu-item-target" value="_blank" name="menu-item-target" />
+					<?php _e( 'Open link in a new tab' ); ?>
+				</label>
+			</p>
+			<p class="field-attr-title description description-thin">
+				<label for="edit-menu-item-attr-title-{{ data.menu_item_id }}">
+					<?php _e( 'Title Attribute' ); ?><br />
+					<input type="text" id="edit-menu-item-attr-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-attr-title" name="menu-item-attr-title" />
+				</label>
+			</p>
+			<p class="field-css-classes description description-thin">
+				<label for="edit-menu-item-classes-{{ data.menu_item_id }}">
+					<?php _e( 'CSS Classes' ); ?><br />
+					<input type="text" id="edit-menu-item-classes-{{ data.menu_item_id }}" class="widefat code edit-menu-item-classes" name="menu-item-classes" />
+				</label>
+			</p>
+			<p class="field-xfn description description-thin">
+				<label for="edit-menu-item-xfn-{{ data.menu_item_id }}">
+					<?php _e( 'Link Relationship (XFN)' ); ?><br />
+					<input type="text" id="edit-menu-item-xfn-{{ data.menu_item_id }}" class="widefat code edit-menu-item-xfn" name="menu-item-xfn" />
+				</label>
+			</p>
+			<p class="field-description description description-thin">
+				<label for="edit-menu-item-description-{{ data.menu_item_id }}">
+					<?php _e( 'Description' ); ?><br />
+					<textarea id="edit-menu-item-description-{{ data.menu_item_id }}" class="widefat edit-menu-item-description" rows="3" cols="20" name="menu-item-description">{{ data.description }}</textarea>
+					<span class="description"><?php _e( 'The description will be displayed in the menu if the current theme supports it.' ); ?></span>
+				</label>
+			</p>
+
+			<div class="menu-item-actions description-thin submitbox">
+				<# if ( 'custom' != data.item_type && '' != data.original_title ) { #>
+				<p class="link-to-original">
+					<?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{{ data.original_title }}}</a>' ); ?>
+				</p>
+				<# } #>
+
+				<button type="button" class="not-a-button item-delete submitdelete deletion"><?php _e( 'Remove' ); ?></button>
+				<span class="spinner"></span>
+			</div>
+			<input type="hidden" name="menu-item-db-id[{{ data.menu_item_id }}]" class="menu-item-data-db-id" value="{{ data.menu_item_id }}" />
+			<input type="hidden" name="menu-item-parent-id[{{ data.menu_item_id }}]" class="menu-item-data-parent-id" value="{{ data.parent }}" />
+		</div><!-- .menu-item-settings-->
+		<ul class="menu-item-transport"></ul>
+		<?php
+	}
+
+	/**
+	 * Return params for this control.
+	 *
+	 * @return array
+	 */
+	function json() {
+		$exported = parent::json();
+		$exported['menu_item_id'] = $this->setting->post_id;
+		return $exported;
+	}
+}
+
+/**
+ * Customize Menu Location Control Class
+ *
+ * This custom control is only needed for JS.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Nav_Menu_Location_Control extends WP_Customize_Control {
+
+	/**
+	 * Control type
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'nav_menu_location';
+
+	/**
+	 * Location ID
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $location_id = '';
+
+	/**
+	 * Refresh the parameters passed to JavaScript via JSON.
+	 *
+	 * @uses WP_Customize_Control::to_json()
+	 */
+	public function to_json() {
+		parent::to_json();
+		$this->json['locationId'] = $this->location_id;
+	}
+
+	/**
+	 * Render content just like a normal select control.
+	 */
+	public function render_content() {
+		if ( empty( $this->choices ) ) {
+			return;
+		}
+		?>
+		<label>
+			<?php if ( ! empty( $this->label ) ) : ?>
+			<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
+			<?php endif; ?>
+
+			<?php if ( ! empty( $this->description ) ) : ?>
+			<span class="description customize-control-description"><?php echo $this->description; ?></span>
+			<?php endif; ?>
+
+			<select <?php $this->link(); ?>>
+				<?php
+				foreach ( $this->choices as $value => $label ) :
+					echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
+				endforeach;
+				?>
+			</select>
+		</label>
+		<?php
+	}
+}
+
+/**
+ * Customize control to represent the name field for a given menu.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Nav_Menu_Name_Control extends WP_Customize_Control {
+
+	/**
+	 * Type of control, used by JS.
+	 *
+	 * @var string
+	 */
+	public $type = 'nav_menu_name';
+
+	/**
+	 * No-op since we're using JS template.
+	 */
+	protected function render_content() {}
+
+	/**
+	 * Render the Underscore template for this control.
+	 */
+	protected function content_template() {
+		?>
+		<label>
+			<input type="text" class="menu-name-field live-update-section-title" />
+		</label>
+		<?php
+	}
+}
+
+/**
+ * Customize control class for new menus.
+ *
+ * @since 4.3.0
+ */
+class WP_New_Menu_Customize_Control extends WP_Customize_Control {
+
+	/**
+	 * Control type.
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'new_menu';
+
+	/**
+	 * Render the control's content.
+	 */
+	public function render_content() {
+		?>
+		<button type="button" class="button button-primary" id="create-new-menu-submit"><?php _e( 'Create Menu' ); ?></button>
+		<span class="spinner"></span>
+		<?php
+	}
+}
Index: src/wp-includes/class-wp-customize-menus.php
===================================================================
--- src/wp-includes/class-wp-customize-menus.php	(revision 0)
+++ src/wp-includes/class-wp-customize-menus.php	(working copy)
@@ -0,0 +1,840 @@
+<?php
+/**
+ * Base Customize Menus
+ *
+ * @package WordPress
+ * @subpackage Customize
+ */
+
+/**
+ * Base Customize Menus class which implements menu management in the Customizer.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Menus {
+
+	/**
+	 * WP_Customize_Manager instance.
+	 *
+	 * @access public
+	 * @var WP_Customize_Manager
+	 */
+	public $manager;
+
+	/**
+	 * Previewed Menus.
+	 *
+	 * @access public
+	 * @var array
+	 */
+	public $previewed_menus;
+
+	/**
+	 * Constructor
+	 *
+	 * @access public
+	 * @param object $manager An instance of the WP_Customize_Manager class.
+	 */
+	public function __construct( $manager ) {
+		$this->previewed_menus = array();
+		$this->manager         = $manager;
+
+		add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) );
+		add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) );
+		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+		add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); // Needs to run after core Navigation section is set up.
+		add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 );
+		add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 );
+		add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) );
+		add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
+		add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
+	}
+
+	/**
+	 * Ajax handler for loading available menu items.
+	 *
+	 * @access public
+	 */
+	public function ajax_load_available_items() {
+		check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
+
+		if ( ! current_user_can( 'edit_theme_options' ) ) {
+			wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
+		}
+		if ( empty( $_POST['obj_type'] ) || empty( $_POST['type'] ) ) {
+			wp_send_json_error( array( 'message' => __( 'Missing obj_type or type param.' ) ) );
+		}
+
+		$obj_type = sanitize_key( $_POST['obj_type'] );
+		if ( ! in_array( $obj_type, array( 'post_type', 'taxonomy' ) ) ) {
+			wp_send_json_error( array( 'message' => __( 'Invalid obj_type param: ' . $obj_type ) ) );
+		}
+		$taxonomy_or_post_type = sanitize_key( $_POST['type'] );
+		$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
+		$items = array();
+
+		if ( 'post_type' === $obj_type ) {
+			if ( ! get_post_type_object( $taxonomy_or_post_type ) ) {
+				wp_send_json_error( array( 'message' => __( 'Unknown post type.' ) ) );
+			}
+
+			if ( 0 === $page && 'page' === $taxonomy_or_post_type ) {
+				// Add "Home" link. Treat as a page, but switch to custom on add.
+				$items[] = array(
+					'id'         => 'home',
+					'title'      => _x( 'Home', 'nav menu home label' ),
+					'type'       => 'custom',
+					'type_label' => __( 'Custom Link' ),
+					'object'     => '',
+					'url'        => home_url(),
+				);
+			}
+
+			$posts = get_posts( array(
+				'numberposts' => 10,
+				'offset'      => 10 * $page,
+				'orderby'     => 'date',
+				'order'       => 'DESC',
+				'post_type'   => $taxonomy_or_post_type,
+			) );
+			foreach ( $posts as $post ) {
+				$items[] = array(
+					'id'         => "post-{$post->ID}",
+					'title'      => html_entity_decode( get_the_title( $post ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
+					'type'       => 'post_type',
+					'type_label' => get_post_type_object( $post->post_type )->labels->singular_name,
+					'object'     => $post->post_type,
+					'object_id'  => (int) $post->ID,
+				);
+			}
+		} else if ( 'taxonomy' === $obj_type ) {
+			$terms = get_terms( $taxonomy_or_post_type, array(
+				'child_of'     => 0,
+				'exclude'      => '',
+				'hide_empty'   => false,
+				'hierarchical' => 1,
+				'include'      => '',
+				'number'       => 10,
+				'offset'       => 10 * $page,
+				'order'        => 'DESC',
+				'orderby'      => 'count',
+				'pad_counts'   => false,
+			) );
+			if ( is_wp_error( $terms ) ) {
+				wp_send_json_error( array( 'message' => wp_strip_all_tags( $terms->get_error_message(), true ) ) );
+			}
+
+			foreach ( $terms as $term ) {
+				$items[] = array(
+					'id'         => "term-{$term->term_id}",
+					'title'      => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
+					'type'       => 'taxonomy',
+					'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
+					'object'     => $term->taxonomy,
+					'object_id'  => $term->term_id,
+				);
+			}
+		}
+
+		wp_send_json_success( array( 'items' => $items ) );
+	}
+
+	/**
+	 * Ajax handler for searching available menu items.
+	 */
+	public function ajax_search_available_items() {
+		check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
+
+		if ( ! current_user_can( 'edit_theme_options' ) ) {
+			wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
+		}
+		if ( empty( $_POST['search'] ) ) {
+			wp_send_json_error( array( 'message' => __( 'Error: missing search parameter.' ) ) );
+		}
+
+		$p = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
+		if ( $p < 1 ) {
+			$p = 1;
+		}
+
+		$s = sanitize_text_field( wp_unslash( $_POST['search'] ) );
+		$results = $this->search_available_items_query( array( 'pagenum' => $p, 's' => $s ) );
+
+		if ( empty( $results ) ) {
+			wp_send_json_error( array( 'message' => __( 'No results found.' ) ) );
+		} else {
+			wp_send_json_success( array( 'items' => $results ) );
+		}
+	}
+
+	/**
+	 * Performs post queries for available-item searching.
+	 *
+	 * Based on WP_Editor::wp_link_query().
+	 *
+	 * @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
+	 * @return array Results.
+	 */
+	public function search_available_items_query( $args = array() ) {
+		$results = array();
+
+		$post_type_objects = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
+		$query = array(
+			'post_type'              => array_keys( $post_type_objects ),
+			'suppress_filters'       => true,
+			'update_post_term_cache' => false,
+			'update_post_meta_cache' => false,
+			'post_status'            => 'publish',
+			'posts_per_page'         => 20,
+		);
+
+		$args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
+		$query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
+
+		if ( isset( $args['s'] ) ) {
+			$query['s'] = $args['s'];
+		}
+
+		// Query posts.
+		$get_posts = new WP_Query( $query );
+
+		// Check if any posts were found.
+		if ( $get_posts->post_count ) {
+			foreach ( $get_posts->posts as $post ) {
+				$results[] = array(
+					'id'         => 'post-' . $post->ID,
+					'type'       => 'post_type',
+					'type_label' => $post_type_objects[ $post->post_type ]->labels->singular_name,
+					'object'     => $post->post_type,
+					'object_id'  => intval( $post->ID ),
+					'title'      => html_entity_decode( get_the_title( $post ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
+				);
+			}
+		}
+
+		// Query taxonomy terms.
+		$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'names' );
+		$terms = get_terms( $taxonomies, array(
+			'name__like' => $args['s'],
+			'number'     => 20,
+			'offset'     => 20 * ($args['pagenum'] - 1),
+		) );
+
+		// Check if any taxonomies were found.
+		if ( ! empty( $terms ) ) {
+			foreach ( $terms as $term ) {
+				$results[] = array(
+					'id'         => 'term-' . $term->term_id,
+					'type'       => 'taxonomy',
+					'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
+					'object'     => $term->taxonomy,
+					'object_id'  => intval( $term->term_id ),
+					'title'      => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
+				);
+			}
+		}
+
+		return $results;
+	}
+
+	/**
+	 * Enqueue scripts and styles for Customizer pane.
+	 *
+	 * @since Menu Customizer 0.0
+	 */
+	public function enqueue_scripts() {
+		wp_enqueue_style( 'menu-customizer' );
+		wp_enqueue_script( 'menu-customizer' );
+
+		$temp_nav_menu_setting      = new WP_Customize_Nav_Menu_Setting( $this->manager, 'nav_menu[-1]' );
+		$temp_nav_menu_item_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->manager, 'nav_menu_item[-1]' );
+
+		// Pass data to JS.
+		$settings = array(
+			'nonce'                => wp_create_nonce( 'customize-menus' ),
+			'allMenus'             => wp_get_nav_menus(),
+			'itemTypes'            => $this->available_item_types(),
+			'l10n'                 => array(
+				'untitled'          => _x( '(no label)', 'Missing menu item navigation label.' ),
+				'custom_label'      => _x( 'Custom', 'Custom menu item type label.' ),
+				'menuLocation'      => _x( '(Currently set to: %s)', 'Current menu location.' ),
+				'deleteWarn'        => __( 'You are about to permanently delete this menu. "Cancel" to stop, "OK" to delete.' ),
+				'itemAdded'         => __( 'Menu item added' ),
+				'itemDeleted'       => __( 'Menu item deleted' ),
+				'menuAdded'         => __( 'Menu created' ),
+				'menuDeleted'       => __( 'Menu deleted' ),
+				'movedUp'           => __( 'Menu item moved up' ),
+				'movedDown'         => __( 'Menu item moved down' ),
+				'movedLeft'         => __( 'Menu item moved out of submenu' ),
+				'movedRight'        => __( 'Menu item is now a sub-item' ),
+				'customizingMenus'  => _x( 'Customizing &#9656; Menus', '&#9656 is the unicode right-pointing triangle' ),
+				'invalidTitleTpl'   => __( '%s (Invalid)' ),
+				'pendingTitleTpl'   => __( '%s (Pending)' ),
+				'taxonomyTermLabel' => __( 'Taxonomy' ),
+				'postTypeLabel'     => __( 'Post Type' ),
+			),
+			'menuItemTransport'    => 'postMessage',
+			'phpIntMax'            => PHP_INT_MAX,
+			'defaultSettingValues' => array(
+				'nav_menu'      => $temp_nav_menu_setting->default,
+				'nav_menu_item' => $temp_nav_menu_item_setting->default,
+			),
+		);
+
+		$data = sprintf( 'var _wpCustomizeMenusSettings = %s;', json_encode( $settings ) );
+		wp_scripts()->add_data( 'menu-customizer', 'data', $data );
+
+		// This is copied from nav-menus.php, and it has an unfortunate object name of `menus`.
+		$nav_menus_l10n = array(
+			'oneThemeLocationNoMenus' => null,
+			'moveUp'       => __( 'Move up one' ),
+			'moveDown'     => __( 'Move down one' ),
+			'moveToTop'    => __( 'Move to the top' ),
+			/* translators: %s: previous item name */
+			'moveUnder'    => __( 'Move under %s' ),
+			/* translators: %s: previous item name */
+			'moveOutFrom'  => __( 'Move out from under %s' ),
+			/* translators: %s: previous item name */
+			'under'        => __( 'Under %s' ),
+			/* translators: %s: previous item name */
+			'outFrom'      => __( 'Out from under %s' ),
+			/* translators: 1: item name, 2: item position, 3: total number of items */
+			'menuFocus'    => __( '%1$s. Menu item %2$d of %3$d.' ),
+			/* translators: 1: item name, 2: item position, 3: parent item name */
+			'subMenuFocus' => __( '%1$s. Sub item number %2$d under %3$s.' ),
+		);
+		wp_localize_script( 'nav-menu', 'menus', $nav_menus_l10n );
+	}
+
+	/**
+	 * Filter a dynamic setting's constructor args.
+	 *
+	 * For a dynamic setting 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_Setting constructor.
+	 *
+	 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
+	 * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
+	 * @return array|false
+	 */
+	public function filter_dynamic_setting_args( $setting_args, $setting_id ) {
+		if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
+			$setting_args = array(
+				'type' => WP_Customize_Nav_Menu_Setting::TYPE,
+			);
+		} else if ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
+			$setting_args = array(
+				'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
+			);
+		}
+		return $setting_args;
+	}
+
+	/**
+	 * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
+	 *
+	 * @param string $setting_class WP_Customize_Setting or a subclass.
+	 * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
+	 * @param array  $setting_args  WP_Customize_Setting or a subclass.
+	 * @return string
+	 */
+	public function filter_dynamic_setting_class( $setting_class, $setting_id, $setting_args ) {
+		unset( $setting_id );
+
+		if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Setting::TYPE === $setting_args['type'] ) {
+			$setting_class = 'WP_Customize_Nav_Menu_Setting';
+		} else if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Item_Setting::TYPE === $setting_args['type'] ) {
+			$setting_class = 'WP_Customize_Nav_Menu_Item_Setting';
+		}
+		return $setting_class;
+	}
+
+	/**
+	 * Add the customizer settings and controls.
+	 *
+	 * @since Menu Customizer 0.0
+	 */
+	public function customize_register() {
+
+		// Require JS-rendered control types.
+		$this->manager->register_panel_type( 'WP_Customize_Menus_Panel' );
+		$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Control' );
+		$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Name_Control' );
+		$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Item_Control' );
+
+		// Create a panel for Menus.
+		$this->manager->add_panel( new WP_Customize_Menus_Panel( $this->manager, 'menus', array(
+			'title'       => __( 'Menus' ),
+			'description' => '<p>' . __( 'This panel is used for managing navigation menus for content you have already published on your site. You can create menus and add items for existing content such as pages, posts, categories, tags, formats, or custom links.' ) . '</p><p>' . __( 'Menus can be displayed in locations defined by your theme or in widget areas by adding a "Custom Menu" widget.' ) . '</p>',
+			'priority'    => 30,
+			// 'theme_supports' => 'menus|widgets', @todo allow multiple theme supports
+		) ) );
+		$menus = wp_get_nav_menus();
+
+		// Menu loactions.
+		$this->manager->remove_section( 'nav' ); // Remove old core section. @todo core merge remove corresponding code from WP_Customize_Manager::register_controls().
+		$locations     = get_registered_nav_menus();
+		$num_locations = count( array_keys( $locations ) );
+		$description   = '<p>' . sprintf( _n( 'Your theme contains %s menu location. Select which menu you would like to use.', 'Your theme contains %s menu locations. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );
+		$description  .= '</p><p>' . __( 'You can also place menus in widget areas with the Custom Menu widget.' ) . '</p>';
+
+		$this->manager->add_section( 'menu_locations', array(
+			'title'       => __( 'Menu Locations' ),
+			'panel'       => 'menus',
+			'priority'    => 5,
+			'description' => $description,
+		) );
+
+		// @todo if ( ! $menus ) : make a "default" menu
+		if ( $menus ) {
+			$choices = array( '0' => __( '&mdash; Select &mdash;' ) );
+			foreach ( $menus as $menu ) {
+				$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
+			}
+
+			foreach ( $locations as $location => $description ) {
+				$setting_id = "nav_menu_locations[{$location}]";
+
+				$setting = $this->manager->get_setting( $setting_id );
+				if ( $setting ) {
+					$setting->transport = 'postMessage';
+					remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
+					add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
+				} else {
+					$this->manager->add_setting( $setting_id, array(
+						'sanitize_callback' => array( $this, 'intval_base10' ),
+						'theme_supports'    => 'menus',
+						'type'              => 'theme_mod',
+						'transport'         => 'postMessage',
+					) );
+				}
+
+				$this->manager->add_control( new WP_Customize_Nav_Menu_Location_Control( $this->manager, $setting_id, array(
+					'label'       => $description,
+					'location_id' => $location,
+					'section'     => 'menu_locations',
+					'choices'     => $choices,
+				) ) );
+			}
+		}
+
+		// Register each menu as a Customizer section, and add each menu item to each menu.
+		foreach ( $menus as $menu ) {
+			$menu_id = $menu->term_id;
+
+			// Create a section for each menu.
+			$section_id = 'nav_menu[' . $menu_id . ']';
+			$this->manager->add_section( new WP_Customize_Nav_Menu_Section( $this->manager, $section_id, array(
+				'title'     => $menu->name,
+				'priority'  => 10,
+				'panel'     => 'menus',
+			) ) );
+
+			$nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
+			$this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id ) );
+
+			// Add the menu contents.
+			$menu_items = (array) wp_get_nav_menu_items( $menu_id );
+
+			foreach ( array_values( $menu_items ) as $i => $item ) {
+
+				// Create a setting for each menu item (which doesn't actually manage data, currently).
+				$menu_item_setting_id = 'nav_menu_item[' . $item->ID . ']';
+				$this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id ) );
+
+				// Create a control for each menu item.
+				$this->manager->add_control( new WP_Customize_Nav_Menu_Item_Control( $this->manager, $menu_item_setting_id, array(
+					'label'    => $item->title,
+					'section'  => $section_id,
+					'priority' => 10 + $i,
+				) ) );
+			}
+
+			// Note: other controls inside of this section get added dynamically in JS via the MenuSection.ready() function.
+		}
+
+		// Add the add-new-menu section and controls.
+		$this->manager->add_section( new WP_Customize_New_Menu_Section( $this->manager, 'add_menu', array(
+			'title'    => __( 'Add a Menu' ),
+			'panel'    => 'menus',
+			'priority' => 999,
+		) ) );
+
+		$this->manager->add_setting( 'new_menu_name', array(
+			'type'      => 'new_menu',
+			'default'   => '',
+			'transport' => 'postMessage',
+		) );
+
+		$this->manager->add_control( 'new_menu_name', array(
+			'label'       => '',
+			'section'     => 'add_menu',
+			'type'        => 'text',
+			'input_attrs' => array(
+				'class'       => 'menu-name-field',
+				'placeholder' => __( 'New menu name' ),
+			),
+		) );
+
+		$this->manager->add_setting( 'create_new_menu', array(
+			'type' => 'new_menu',
+		) );
+
+		$this->manager->add_control( new WP_New_Menu_Customize_Control( $this->manager, 'create_new_menu', array(
+			'section' => 'add_menu',
+		) ) );
+	}
+
+	/**
+	 * Get the base10 intval.
+	 *
+	 * This is used as a setting's sanitize_callback; we can't use just plain
+	 * intval because the second argument is not what intval() expects.
+	 *
+	 * @param mixed $value Number to convert.
+	 *
+	 * @return int
+	 */
+	function intval_base10( $value ) {
+		return intval( $value, 10 );
+	}
+
+	/**
+	 * Return an array of all the available item types.
+	 *
+	 * @since Menu Customizer 0.0
+	 */
+	public function available_item_types() {
+		$items = array(
+			'postTypes'  => array(),
+			'taxonomies' => array(),
+		);
+
+		$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
+		foreach ( $post_types as $slug => $post_type ) {
+			$items['postTypes'][ $slug ] = array(
+				'label' => $post_type->labels->singular_name,
+			);
+		}
+
+		$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
+		foreach ( $taxonomies as $slug => $taxonomy ) {
+			if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) {
+				continue;
+			}
+			$items['taxonomies'][ $slug ] = array(
+				'label' => $taxonomy->labels->singular_name,
+			);
+		}
+		return $items;
+	}
+
+	/**
+	 * Print the JavaScript templates used to render Menu Customizer components.
+	 *
+	 * Templates are imported into the JS use wp.template.
+	 *
+	 * @since Menu Customizer 0.0
+	 */
+	public function print_templates() {
+		?>
+		<script type="text/html" id="tmpl-available-menu-item">
+			<div id="menu-item-tpl-{{ data.id }}" class="menu-item-tpl" data-menu-item-id="{{ data.id }}">
+				<dl class="menu-item-bar">
+					<dt class="menu-item-handle">
+						<span class="item-type">{{ data.type_label }}</span>
+						<span class="item-title">{{ data.title || wp.customize.Menus.data.l10n.untitled }}</span>
+						<button type="button" class="not-a-button item-add"><span class="screen-reader-text"><?php _e( 'Add Menu Item' ) ?></span></button>
+					</dt>
+				</dl>
+			</div>
+		</script>
+
+		<script type="text/html" id="tmpl-available-menu-item-type">
+			<div id="available-menu-items-{{ data.type }}" class="accordion-section">
+				<h4 class="accordion-section-title">{{ data.type_label }}</h4>
+				<div class="accordion-section-content">
+				</div>
+			</div>
+		</script>
+
+		<script type="text/html" id="tmpl-menu-item-reorder-nav">
+			<div class="menu-item-reorder-nav">
+				<?php
+				printf(
+					'<button type="button" class="menus-move-up">%1$s</button><button type="button" class="menus-move-down">%2$s</button><button type="button" class="menus-move-left">%3$s</button><button type="button" class="menus-move-right">%4$s</button>',
+					esc_html__( 'Move up' ),
+					esc_html__( 'Move down' ),
+					esc_html__( 'Move one level up' ),
+					esc_html__( 'Move one level down' )
+				);
+				?>
+			</div>
+		</script>
+	<?php
+	}
+
+	/**
+	 * Print the html template used to render the add-menu-item frame.
+	 */
+	public function available_items_template() {
+		?>
+		<div id="available-menu-items" class="accordion-container">
+			<div class="customize-section-title">
+				<button type="button" class="customize-section-back" tabindex="-1">
+					<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
+				</button>
+				<h3>
+					<span class="customize-action">
+						<?php
+							/* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
+							printf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'menus' )->title ) );
+						?>
+					</span>
+					<?php _e( 'Add Menu Items' ); ?>
+				</h3>
+			</div>
+			<div id="available-menu-items-search" class="accordion-section cannot-expand">
+				<div class="accordion-section-title">
+					<label class="screen-reader-text" for="menu-items-search"><?php _e( 'Search Menu Items' ); ?></label>
+					<input type="text" id="menu-items-search" placeholder="<?php esc_attr_e( 'Search menu items&hellip;' ) ?>" />
+					<span class="spinner"></span>
+				</div>
+				<div class="accordion-section-content" data-type="search"></div>
+			</div>
+			<div id="new-custom-menu-item" class="accordion-section">
+				<h4 class="accordion-section-title"><?php _e( 'Links' ); ?><button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
+				<div class="accordion-section-content">
+					<input type="hidden" value="custom" id="custom-menu-item-type" name="menu-item[-1][menu-item-type]" />
+					<p id="menu-item-url-wrap">
+						<label class="howto" for="custom-menu-item-url">
+							<span><?php _e( 'URL' ); ?></span>
+							<input id="custom-menu-item-url" name="menu-item[-1][menu-item-url]" type="text" class="code menu-item-textbox" value="http://">
+						</label>
+					</p>
+					<p id="menu-item-name-wrap">
+						<label class="howto" for="custom-menu-item-name">
+							<span><?php _e( 'Link Text' ); ?></span>
+							<input id="custom-menu-item-name" name="menu-item[-1][menu-item-title]" type="text" class="regular-text menu-item-textbox">
+						</label>
+					</p>
+					<p class="button-controls">
+						<span class="add-to-menu">
+							<input type="submit" class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-custom-menu-item" id="custom-menu-item-submit">
+							<span class="spinner"></span>
+						</span>
+					</p>
+				</div>
+			</div>
+			<?php
+
+			// @todo: consider using add_meta_box/do_accordion_section and making screen-optional?
+			// Containers for per-post-type item browsing; items added with JS.
+			$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
+			if ( $post_types ) :
+				foreach ( $post_types as $type ) :
+					?>
+					<div id="available-menu-items-<?php echo esc_attr( $type->name ); ?>" class="accordion-section">
+						<h4 class="accordion-section-title"><?php echo esc_html( $type->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
+						<div class="accordion-section-content" data-type="<?php echo esc_attr( $type->name ); ?>" data-obj_type="post_type"></div>
+					</div>
+				<?php
+				endforeach;
+			endif;
+
+			$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
+			if ( $taxonomies ) :
+				foreach ( $taxonomies as $tax ) :
+					?>
+					<div id="available-menu-items-<?php echo esc_attr( $tax->name ); ?>" class="accordion-section">
+						<h4 class="accordion-section-title"><?php echo esc_html( $tax->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
+						<div class="accordion-section-content" data-type="<?php echo esc_attr( $tax->name ); ?>" data-obj_type="taxonomy"></div>
+					</div>
+				<?php
+				endforeach;
+			endif;
+			?>
+		</div><!-- #available-menu-items -->
+	<?php
+	}
+
+	// Start functionality specific to partial-refresh of menu changes in Customizer preview.
+	const RENDER_AJAX_ACTION = 'customize_render_menu_partial';
+	const RENDER_NONCE_POST_KEY = 'render-menu-nonce';
+	const RENDER_QUERY_VAR = 'wp_customize_menu_render';
+
+	/**
+	 * The number of wp_nav_menu() calls which have happened in the preview.
+	 *
+	 * @var int
+	 */
+	public $preview_nav_menu_instance_number = 0;
+
+	/**
+	 * Nav menu args used for each instance.
+	 *
+	 * @var array[]
+	 */
+	public $preview_nav_menu_instance_args = array();
+
+	/**
+	 * Add hooks for the Customizer preview.
+	 */
+	function customize_preview_init() {
+		add_action( 'template_redirect', array( $this, 'render_menu' ) );
+		add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
+
+		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 );
+		}
+	}
+
+	/**
+	 * Keep track of the arguments that are being passed to wp_nav_menu().
+	 *
+	 * @see wp_nav_menu()
+	 *
+	 * @param array $args  An array containing wp_nav_menu() arguments.
+	 * @return array
+	 */
+	function filter_wp_nav_menu_args( $args ) {
+		$this->preview_nav_menu_instance_number += 1;
+		$args['instance_number'] = $this->preview_nav_menu_instance_number;
+
+		$can_partial_refresh = (
+			$args['echo']
+			&&
+			is_string( $args['fallback_cb'] )
+			&&
+			is_string( $args['walker'] )
+		);
+		$args['can_partial_refresh'] = $can_partial_refresh;
+
+		if ( ! $can_partial_refresh ) {
+			unset( $args['fallback_cb'] );
+			unset( $args['walker'] );
+		}
+
+		ksort( $args );
+		$args['args_hash'] = $this->hash_nav_menu_args( $args );
+
+		$this->preview_nav_menu_instance_args[ $this->preview_nav_menu_instance_number ] = $args;
+		return $args;
+	}
+
+	/**
+	 * Prepare wp_nav_menu() calls for partial refresh. Wraps output in container for refreshing.
+	 *
+	 * @see wp_nav_menu()
+	 *
+	 * @param string $nav_menu_content The HTML content for the navigation menu.
+	 * @param object $args             An object containing wp_nav_menu() arguments.
+	 * @return null
+	 */
+	function filter_wp_nav_menu( $nav_menu_content, $args ) {
+		if ( ! empty( $args->can_partial_refresh ) && ! empty( $args->instance_number ) ) {
+			$nav_menu_content = sprintf(
+				'<div id="partial-refresh-menu-container-%1$d" class="partial-refresh-menu-container" data-instance-number="%1$d">%2$s</div>',
+				$args->instance_number,
+				$nav_menu_content
+			);
+		}
+		return $nav_menu_content;
+	}
+
+	/**
+	 * Hash (hmac) the arguments with the nonce and secret auth key to ensure they
+	 * are not tampered with when submitted in the Ajax request.
+	 *
+	 * @param array $args The arguments to hash.
+	 * @return string
+	 */
+	function hash_nav_menu_args( $args ) {
+		return wp_hash( wp_create_nonce( self::RENDER_AJAX_ACTION ) . serialize( $args ) );
+	}
+
+	/**
+	 * Enqueue scripts for the Customizer preview.
+	 */
+	function customize_preview_enqueue_deps() {
+		wp_enqueue_script( 'customize-menus-preview' );
+		wp_enqueue_style( 'customize-menus-preview' );
+
+		add_action( 'wp_print_footer_scripts', array( $this, 'export_preview_data' ) );
+	}
+
+	/**
+	 * Export data from PHP to JS.
+	 */
+	function export_preview_data() {
+
+		// 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,
+			'requestUri'            => '/',
+			'theme'                 => array(
+				'stylesheet' => $this->manager->get_stylesheet(),
+				'active'     => $this->manager->is_theme_active(),
+			),
+			'previewCustomizeNonce' => wp_create_nonce( 'preview-customize_' . $this->manager->get_stylesheet() ),
+			'navMenuInstanceArgs'   => $this->preview_nav_menu_instance_args,
+		);
+
+		if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
+			$exports['requestUri'] = esc_url_raw( home_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
+		}
+
+		printf( '<script>var _wpCustomizePreviewMenusExports = %s;</script>', wp_json_encode( $exports ) );
+	}
+
+	/**
+	 * Render a specific menu via wp_nav_menu() using the supplied arguments.
+	 *
+	 * @see wp_nav_menu()
+	 */
+	function render_menu() {
+		if ( empty( $_POST[ self::RENDER_QUERY_VAR ] ) ) {
+			return;
+		}
+
+		$this->manager->remove_preview_signature();
+
+		if ( empty( $_POST[ self::RENDER_NONCE_POST_KEY ] ) ) {
+			wp_send_json_error( 'missing_nonce_param' );
+		}
+		if ( ! is_customize_preview() ) {
+			wp_send_json_error( 'expected_customize_preview' );
+		}
+		if ( ! check_ajax_referer( self::RENDER_AJAX_ACTION, self::RENDER_NONCE_POST_KEY, false ) ) {
+			wp_send_json_error( 'nonce_check_fail' );
+		}
+		if ( ! current_user_can( 'edit_theme_options' ) ) {
+			wp_send_json_error( 'unauthorized' );
+		}
+		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_hash = sanitize_text_field( wp_unslash( $_POST['wp_nav_menu_args_hash'] ) );
+		$wp_nav_menu_args      = json_decode( wp_unslash( $_POST['wp_nav_menu_args'] ), true );
+
+		if ( json_last_error() ) {
+			wp_send_json_error( 'json_parse_error' );
+		}
+		if ( ! is_array( $wp_nav_menu_args ) ) {
+			wp_send_json_error( 'wp_nav_menu_args_not_array' );
+		}
+		if ( $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 ) );
+	}
+}
Index: src/wp-includes/class-wp-customize-section.php
===================================================================
--- src/wp-includes/class-wp-customize-section.php	(revision 32795)
+++ src/wp-includes/class-wp-customize-section.php	(working copy)
@@ -501,3 +501,66 @@
 		return $this->manager->widgets->is_sidebar_rendered( $this->sidebar_id );
 	}
 }
+
+/**
+ * Customize Menu Section Class
+ *
+ * Custom section only needed in JS.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_Nav_Menu_Section extends WP_Customize_Section {
+
+	/**
+	 * Control type
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'nav_menu';
+
+	/**
+	 * Get section params for JS.
+	 *
+	 * @return array
+	 */
+	function json() {
+		$exported = parent::json();
+		$exported['menu_id'] = intval( preg_replace( '/^nav_menu\[(\d+)\]/', '$1', $this->id ) );
+
+		return $exported;
+	}
+}
+
+/**
+ * Customize Menu Section Class
+ *
+ * Implements the new-menu-ui toggle button instead of a regular section.
+ *
+ * @since 4.3.0
+ */
+class WP_Customize_New_Menu_Section extends WP_Customize_Section {
+
+	/**
+	 * Control type.
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'new_menu';
+
+	/**
+	 * Render the section, and the controls that have been added to it.
+	 */
+	protected function render() {
+		?>
+		<li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section-new-menu">
+			<button type="button" class="button-secondary add-new-menu-item add-menu-toggle">
+				<?php echo esc_html( $this->title ); ?>
+				<span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span>
+			</button>
+			<ul class="new-menu-section-content"></ul>
+		</li>
+		<?php
+	}
+}
Index: src/wp-includes/class-wp-customize-setting.php
===================================================================
--- src/wp-includes/class-wp-customize-setting.php	(revision 32795)
+++ src/wp-includes/class-wp-customize-setting.php	(working copy)
@@ -630,3 +630,1065 @@
 		remove_theme_mod( 'background_image_thumb' );
 	}
 }
+
+/**
+ * Customize Setting to represent a nav_menu.
+ *
+ * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
+ * the IDs for the nav_menu_items associated with the nav menu.
+ *
+ * @since 4.3.0
+ *
+ * @see wp_get_nav_menu_items()
+ * @see WP_Customize_Setting
+ */
+class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
+
+	const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/';
+
+	const POST_TYPE = 'nav_menu_item';
+
+	const TYPE = 'nav_menu_item';
+
+	/**
+	 * Setting type.
+	 *
+	 * @var string
+	 */
+	public $type = self::TYPE;
+
+	/**
+	 * Default setting value.
+	 *
+	 * @see wp_setup_nav_menu_item()
+	 * @var array
+	 */
+	public $default = array(
+		// The $menu_item_data for wp_update_nav_menu_item().
+		'object_id'        => 0,
+		'object'           => '', // Taxonomy name.
+		'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included.
+		'position'         => 0, // A.K.A. menu_order.
+		'type'             => 'custom', // Note that type_label is not included here.
+		'title'            => '',
+		'url'              => '',
+		'target'           => '',
+		'attr_title'       => '',
+		'description'      => '',
+		'classes'          => '',
+		'xfn'              => '',
+		'status'           => 'publish',
+		'original_title'   => '',
+		'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item().
+		// @todo also expose invalid?
+	);
+
+	/**
+	 * Default transport.
+	 *
+	 * @var string
+	 */
+	public $transport = 'postMessage';
+
+	/**
+	 * The post ID represented by this setting instance. This is the db_id.
+	 *
+	 * A negative value represents a placeholder ID for a new menu not yet saved.
+	 *
+	 * @todo Should this be $db_id, and also use this for WP_Customize_Nav_Menu_Setting::$term_id
+	 *
+	 * @var int
+	 */
+	public $post_id;
+
+	/**
+	 * Previous (placeholder) post ID used before creating a new menu item.
+	 *
+	 * This value will be exported to JS via the customize_save_response filter
+	 * so that JavaScript can update the settings to refer to the newly-assigned
+	 * post ID. This value is always negative to indicate it does not refer to
+	 * a real post.
+	 *
+	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
+	 *
+	 * @var int
+	 */
+	public $previous_post_id;
+
+	/**
+	 * When previewing or updating a menu item, this stores the previous nav_menu_term_id
+	 * which ensures that we can apply the proper filters.
+	 *
+	 * @var int
+	 */
+	public $original_nav_menu_term_id;
+
+	/**
+	 * Whether or not preview() was called.
+	 *
+	 * @var bool
+	 */
+	protected $is_previewed = false;
+
+	/**
+	 * Whether or not update() was called.
+	 *
+	 * @var bool
+	 */
+	protected $is_updated = false;
+
+	/**
+	 * Status for calling the update method, used in customize_save_response filter.
+	 *
+	 * When status is inserted, the placeholder post ID is stored in $previous_post_id.
+	 * When status is error, the error is stored in $update_error.
+	 *
+	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
+	 *
+	 * @var string updated|inserted|deleted|error
+	 */
+	public $update_status;
+
+	/**
+	 * Any error object returned by wp_update_nav_menu_item() when setting is updated.
+	 *
+	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
+	 *
+	 * @var WP_Error
+	 */
+	public $update_error;
+
+	/**
+	 * Constructor.
+	 *
+	 * Any supplied $args override class property defaults.
+	 *
+	 * @param WP_Customize_Manager $manager Manager instance.
+	 * @param string               $id      An specific ID of the setting. Can be a
+	 *                                      theme mod or option name.
+	 * @param array                $args    Optional. Setting arguments.
+	 * @throws Exception If $id is not valid for this setting type.
+	 */
+	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
+		if ( empty( $manager->menus ) ) {
+			throw new Exception( 'Expected WP_Customize_Manager::$menus to be set.' );
+		}
+
+		if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
+			throw new Exception( "Illegal widget setting ID: $id" );
+		}
+
+		$this->post_id = intval( $matches['id'] );
+
+		$menu = $this->value();
+		$this->original_nav_menu_term_id = $menu['nav_menu_term_id'];
+
+		parent::__construct( $manager, $id, $args );
+	}
+
+	/**
+	 * Get the instance data for a given widget setting.
+	 *
+	 * @see wp_setup_nav_menu_item()
+	 * @return array
+	 */
+	public function value() {
+		if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
+			$undefined  = new stdClass(); // Symbol.
+			$post_value = $this->post_value( $undefined );
+
+			if ( $undefined === $post_value ) {
+				$value = $this->_original_value;
+			} else {
+				$value = $post_value;
+			}
+		} else {
+			$value = false;
+
+			// Note that a ID of less than one indicates a nav_menu not yet inserted.
+			if ( $this->post_id > 0 ) {
+				$post = get_post( $this->post_id );
+				if ( $post && self::POST_TYPE === $post->post_type ) {
+					$item  = wp_setup_nav_menu_item( $post );
+					$value = wp_array_slice_assoc(
+						(array) $item,
+						array_keys( $this->default )
+					);
+					$value['position']       = $item->menu_order;
+					$value['status']         = $item->post_status;
+					$value['original_title'] = '';
+
+					$menus = wp_get_post_terms( $post->ID, WP_Customize_Nav_Menu_Setting::TAXONOMY, array(
+						'fields' => 'ids',
+					) );
+
+					if ( ! empty( $menus ) ) {
+						$value['nav_menu_term_id'] = array_shift( $menus );
+					} else {
+						$value['nav_menu_term_id'] = 0;
+					}
+
+					if ( 'post_type' === $value['type'] ) {
+						$original_title = get_the_title( $value['object_id'] );
+					} else if ( 'taxonomy' === $value['type'] ) {
+						$original_title = get_term_field( 'name', $value['object_id'], $value['object'], 'raw' );
+						if ( is_wp_error( $original_title ) ) {
+							$original_title = '';
+						}
+					}
+
+					if ( ! empty( $original_title ) ) {
+						$value['original_title'] = $original_title;
+					}
+				}
+			}
+
+			if ( ! is_array( $value ) ) {
+				$value = $this->default;
+			}
+		}
+
+		if ( is_array( $value ) ) {
+			foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
+				$value[ $key ] = intval( $value[ $key ] );
+			}
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Handle previewing the setting.
+	 *
+	 * @see WP_Customize_Manager::post_value()
+	 */
+	public function preview() {
+		if ( $this->is_previewed ) {
+			return;
+		}
+
+		$this->is_previewed              = true;
+		$this->_original_value           = $this->value();
+		$this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id'];
+		$this->_previewed_blog_id        = get_current_blog_id();
+
+		add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 );
+
+		$sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' );
+		if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) {
+			add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 );
+		}
+
+		// @todo Add get_post_metadata filters for plugins to add their data.
+	}
+
+	/**
+	 * Filter the wp_get_nav_menu_items() result to supply the previewed menu items.
+	 *
+	 * @see wp_get_nav_menu_items()
+	 * @param array  $items An array of menu item post objects.
+	 * @param object $menu  The menu object.
+	 * @param array  $args  An array of arguments used to retrieve menu item objects.
+	 * @return array
+	 */
+	function filter_wp_get_nav_menu_items( $items, $menu, $args ) {
+		$this_item = $this->value();
+		$current_nav_menu_term_id = $this_item['nav_menu_term_id'];
+		unset( $this_item['nav_menu_term_id'] );
+
+		$should_filter = (
+			$menu->term_id === $this->original_nav_menu_term_id
+			||
+			$menu->term_id === $current_nav_menu_term_id
+		);
+		if ( ! $should_filter ) {
+			return $items;
+		}
+
+		// Handle deleted menu item, or menu item moved to another menu.
+		$should_remove = (
+			false === $this_item
+			||
+			(
+				$this->original_nav_menu_term_id === $menu->term_id
+				&&
+				$current_nav_menu_term_id !== $this->original_nav_menu_term_id
+			)
+		);
+		if ( $should_remove ) {
+			$filtered_items = array();
+			foreach ( $items as $item ) {
+				if ( $item->db_id !== $this->post_id ) {
+					$filtered_items[] = $item;
+				}
+			}
+			return $filtered_items;
+		}
+
+		$mutated = false;
+		$should_update = (
+			is_array( $this_item )
+			&&
+			$current_nav_menu_term_id === $menu->term_id
+		);
+		if ( $should_update ) {
+			foreach ( $items as $item ) {
+				if ( $item->db_id === $this->post_id ) {
+					foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) {
+						$item->$key = $value;
+					}
+					$mutated = true;
+				}
+			}
+
+			// Not found so we have to append it..
+			if ( ! $mutated ) {
+				$items[] = $this->value_as_wp_post_nav_menu_item();
+			}
+		}
+
+		return $items;
+	}
+
+	/**
+	 * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items().
+	 *
+	 * @see wp_get_nav_menu_items()
+	 *
+	 * @param array  $items An array of menu item post objects.
+	 * @param object $menu  The menu object.
+	 * @param array  $args  An array of arguments used to retrieve menu item objects.
+	 * @return array
+	 */
+	static function sort_wp_get_nav_menu_items( $items, $menu, $args ) {
+		// @todo We should probably re-apply some constraints imposed by $args.
+		unset( $args['include'] );
+
+		// Remove invalid items only in frontend.
+		if ( ! is_admin() ) {
+			$items = array_filter( $items, '_is_valid_nav_menu_item' );
+		}
+
+		if ( ARRAY_A === $args['output'] ) {
+			$GLOBALS['_menu_item_sort_prop'] = $args['output_key'];
+			usort( $items, '_sort_nav_menu_items' );
+			$i = 1;
+
+			foreach ( $items as $k => $item ) {
+				$items[ $k ]->$args['output_key'] = $i++;
+			}
+		}
+
+		return $items;
+	}
+
+	/**
+	 * Get the value emulated into a WP_Post and set up as a nav_menu_item.
+	 *
+	 * @return WP_Post With {@see wp_setup_nav_menu_item()} applied.
+	 */
+	public function value_as_wp_post_nav_menu_item() {
+		$item = (object) $this->value();
+		unset( $item->nav_menu_term_id );
+
+		$item->post_status = $item->status;
+		unset( $item->status );
+
+		$item->post_type = 'nav_menu_item';
+		$item->menu_order = $item->position;
+		unset( $item->position );
+
+		$item->post_author = get_current_user_id();
+
+		if ( $item->title ) {
+			$item->post_title = $item->title;
+		}
+
+		$item->ID = $this->post_id;
+		$post = new WP_Post( (object) $item );
+		$post = wp_setup_nav_menu_item( $post );
+
+		return $post;
+	}
+
+	/**
+	 * Sanitize an input.
+	 *
+	 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
+	 * we remove that in this override.
+	 *
+	 * @param array $menu_item_value The value to sanitize.
+	 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value.
+	 */
+	public function sanitize( $menu_item_value ) {
+		// Menu is marked for deletion.
+		if ( false === $menu_item_value ) {
+			return $menu_item_value;
+		}
+
+		// Invalid.
+		if ( ! is_array( $menu_item_value ) ) {
+			return null;
+		}
+
+		$default = array(
+			'object_id'        => 0,
+			'object'           => '',
+			'menu_item_parent' => 0,
+			'position'         => 0,
+			'type'             => 'custom',
+			'title'            => '',
+			'url'              => '',
+			'target'           => '',
+			'attr_title'       => '',
+			'description'      => '',
+			'classes'          => '',
+			'xfn'              => '',
+			'status'           => 'publish',
+			'original_title'   => '',
+			'nav_menu_term_id' => 0,
+		);
+		$menu_item_value = array_merge( $default, $menu_item_value );
+		$menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) );
+		$menu_item_value['position'] = max( 0, intval( $menu_item_value['position'] ) );
+
+		foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
+			// Note we need to allow negative-integer IDs for previewed objects not inserted yet.
+			$menu_item_value[ $key ] = intval( $menu_item_value[ $key ] );
+		}
+
+		foreach ( array( 'type', 'object', 'target' ) as $key ) {
+			$menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] );
+		}
+
+		foreach ( array( 'xfn', 'classes' ) as $key ) {
+			$value = $menu_item_value[ $key ];
+			if ( ! is_array( $value ) ) {
+				$value = explode( ' ', $value );
+			}
+			$menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) );
+		}
+
+		foreach ( array( 'title', 'attr_title', 'description', 'original_title' ) as $key ) {
+			// @todo Should esc_attr() the attr_title as well?
+			$menu_item_value[ $key ] = sanitize_text_field( $menu_item_value[ $key ] );
+		}
+
+		$menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
+		if ( ! get_post_status_object( $menu_item_value['status'] ) ) {
+			$menu_item_value['status'] = 'publish';
+		}
+
+		/** This filter is documented in wp-includes/class-wp-customize-setting.php */
+		return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this );
+	}
+
+	/**
+	 * Create/update the nav_menu_item post for this setting.
+	 *
+	 * Any created menu items will have their assigned post IDs exported to the client
+	 * via the customize_save_response filter. Likewise, any errors will be exported
+	 * to the client via the customize_save_response() filter.
+	 *
+	 * To delete a menu, the client can send false as the value.
+	 *
+	 * @see wp_update_nav_menu_item()
+	 *
+	 * @param array|false $value The menu item array to update. If false, then the menu item will be deleted entirely.
+	 *                           See {@see WP_Customize_Nav_Menu_Item_Setting::$default} for what the value should
+	 *                           consist of.
+	 * @return void
+	 */
+	protected function update( $value ) {
+		if ( $this->is_updated ) {
+			return;
+		}
+
+		$this->is_updated = true;
+		$is_placeholder   = ( $this->post_id < 0 );
+		$is_delete        = ( false === $value );
+
+		add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
+
+		if ( $is_delete ) {
+			// If the current setting post is a placeholder, a delete request is a no-op.
+			if ( $is_placeholder ) {
+				$this->update_status = 'deleted';
+			} else {
+				$r = wp_delete_post( $this->post_id, true );
+
+				if ( false === $r ) {
+					$this->update_error  = new WP_Error( 'delete_failure' );
+					$this->update_status = 'error';
+				} else {
+					$this->update_status = 'deleted';
+				}
+				// @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer?
+			}
+		} else {
+
+			// Handle saving menu items for menus that are being newly-created.
+			if ( $value['nav_menu_term_id'] < 0 ) {
+				$nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] );
+				$nav_menu_setting    = $this->manager->get_setting( $nav_menu_setting_id );
+
+				if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'unexpected_nav_menu_setting' );
+					return;
+				}
+
+				if ( false === $nav_menu_setting->save() ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'nav_menu_setting_failure' );
+				}
+
+				if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'unexpected_previous_term_id' );
+					return;
+				}
+
+				$value['nav_menu_term_id'] = $nav_menu_setting->term_id;
+			}
+
+			// Handle saving a nav menu item that is a child of a nav menu item being newly-created.
+			if ( $value['menu_item_parent'] < 0 ) {
+				$parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] );
+				$parent_nav_menu_item_setting    = $this->manager->get_setting( $parent_nav_menu_item_setting_id );
+
+				if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'unexpected_nav_menu_item_setting' );
+					return;
+				}
+
+				if ( false === $parent_nav_menu_item_setting->save() ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'nav_menu_item_setting_failure' );
+				}
+
+				if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) {
+					$this->update_status = 'error';
+					$this->update_error  = new WP_Error( 'unexpected_previous_post_id' );
+					return;
+				}
+
+				$value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id;
+			}
+
+			// Insert or update menu.
+			$menu_item_data = array(
+				'menu-item-object-id'   => $value['object_id'],
+				'menu-item-object'      => $value['object'],
+				'menu-item-parent-id'   => $value['menu_item_parent'],
+				'menu-item-position'    => $value['position'],
+				'menu-item-type'        => $value['type'],
+				'menu-item-title'       => $value['title'],
+				'menu-item-url'         => $value['url'],
+				'menu-item-description' => $value['description'],
+				'menu-item-attr-title'  => $value['attr_title'],
+				'menu-item-target'      => $value['target'],
+				'menu-item-classes'     => $value['classes'],
+				'menu-item-xfn'         => $value['xfn'],
+				'menu-item-status'      => $value['status'],
+			);
+
+			$r = wp_update_nav_menu_item(
+				$value['nav_menu_term_id'],
+				$is_placeholder ? 0 : $this->post_id,
+				$menu_item_data
+			);
+
+			if ( is_wp_error( $r ) ) {
+				$this->update_status = 'error';
+				$this->update_error = $r;
+			} else {
+				if ( $is_placeholder ) {
+					$this->previous_post_id = $this->post_id;
+					$this->post_id = $r;
+					$this->update_status = 'inserted';
+				} else {
+					$this->update_status = 'updated';
+				}
+			}
+		}
+
+	}
+
+	/**
+	 * Export data for the JS client.
+	 *
+	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
+	 *
+	 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
+	 * @return array
+	 */
+	function amend_customize_save_response( $data ) {
+		if ( ! isset( $data['nav_menu_item_updates'] ) ) {
+			$data['nav_menu_item_updates'] = array();
+		}
+
+		$data['nav_menu_item_updates'][] = array(
+			'post_id'          => $this->post_id,
+			'previous_post_id' => $this->previous_post_id,
+			'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
+			'status'           => $this->update_status,
+		);
+
+		return $data;
+	}
+}
+
+/**
+ * Customize Setting to represent a nav_menu.
+ *
+ * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
+ * the IDs for the nav_menu_items associated with the nav menu.
+ *
+ * @since 4.3.0
+ *
+ * @see wp_get_nav_menu_object()
+ * @see WP_Customize_Setting
+ */
+class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting {
+
+	const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/';
+
+	const TAXONOMY = 'nav_menu';
+
+	const TYPE = 'nav_menu';
+
+	/**
+	 * Setting type.
+	 *
+	 * @var string
+	 */
+	public $type = self::TYPE;
+
+	/**
+	 * Default setting value;
+	 *
+	 * @see wp_get_nav_menu_object()
+	 *
+	 * @var array
+	 */
+	public $default = array(
+		'name'        => '',
+		'description' => '',
+		'parent'      => 0,
+		'auto_add'    => false,
+	);
+
+	/**
+	 * Default transport.
+	 *
+	 * @var string
+	 */
+	public $transport = 'postMessage';
+
+	/**
+	 * The term ID represented by this setting instance.
+	 *
+	 * A negative value represents a placeholder ID for a new menu not yet saved.
+	 *
+	 * @var int
+	 */
+	public $term_id;
+
+	/**
+	 * Previous (placeholder) term ID used before creating a new menu.
+	 *
+	 * This value will be exported to JS via the customize_save_response filter
+	 * so that JavaScript can update the settings to refer to the newly-assigned
+	 * term ID. This value is always negative to indicate it does not refer to
+	 * a real term.
+	 *
+	 * @see WP_Customize_Nav_Menu_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
+	 *
+	 * @var int
+	 */
+	public $previous_term_id;
+
+	/**
+	 * Whether or not preview() was called.
+	 *
+	 * @var bool
+	 */
+	protected $is_previewed = false;
+
+	/**
+	 * Whether or not update() was called.
+	 *
+	 * @var bool
+	 */
+	protected $is_updated = false;
+
+	/**
+	 * Status for calling the update method, used in customize_save_response filter.
+	 *
+	 * When status is inserted, the placeholder term ID is stored in $previous_term_id.
+	 * When status is error, the error is stored in $update_error.
+	 *
+	 * @see WP_Customize_Nav_Menu_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
+	 *
+	 * @var string updated|inserted|deleted|error
+	 */
+	public $update_status;
+
+	/**
+	 * Any error object returned by wp_update_nav_menu_object() when setting is updated.
+	 *
+	 * @see WP_Customize_Nav_Menu_Setting::update()
+	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
+	 *
+	 * @var WP_Error
+	 */
+	public $update_error;
+
+	/**
+	 * Constructor.
+	 *
+	 * Any supplied $args override class property defaults.
+	 *
+	 * @param WP_Customize_Manager $manager Manager instance.
+	 * @param string               $id      An specific ID of the setting. Can be a
+	 *                                      theme mod or option name.
+	 * @param array                $args    Optional. Setting arguments.
+	 * @throws Exception If $id is not valid for this setting type.
+	 */
+	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
+		if ( empty( $manager->menus ) ) {
+			throw new Exception( 'Expected WP_Customize_Manager::$menus to be set.' );
+		}
+
+		if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
+			throw new Exception( "Illegal widget setting ID: $id" );
+		}
+
+		$this->term_id = intval( $matches['id'] );
+
+		parent::__construct( $manager, $id, $args );
+	}
+
+	/**
+	 * Get the instance data for a given widget setting.
+	 *
+	 * @see wp_get_nav_menu_object()
+	 * @return array
+	 */
+	public function value() {
+		if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
+			$undefined  = new stdClass(); // Symbol.
+			$post_value = $this->post_value( $undefined );
+
+			if ( $undefined === $post_value ) {
+				$value = $this->_original_value;
+			} else {
+				$value = $post_value;
+			}
+		} else {
+			$value = false;
+
+			// Note that a term_id of less than one indicates a nav_menu not yet inserted.
+			if ( $this->term_id > 0 ) {
+				$term = wp_get_nav_menu_object( $this->term_id );
+
+				if ( $term ) {
+					$value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) );
+
+					$nav_menu_options  = (array) get_option( 'nav_menu_options', array() );
+					$value['auto_add'] = false;
+
+					if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) {
+						$value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] );
+					}
+				}
+			}
+
+			if ( ! is_array( $value ) ) {
+				$value = $this->default;
+			}
+		}
+		return $value;
+	}
+
+	/**
+	 * Handle previewing the setting.
+	 *
+	 * @see WP_Customize_Manager::post_value()
+	 */
+	public function preview() {
+		if ( $this->is_previewed ) {
+			return;
+		}
+
+		$this->is_previewed       = true;
+		$this->_original_value    = $this->value();
+		$this->_previewed_blog_id = get_current_blog_id();
+
+		add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 );
+		add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
+		add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
+	}
+
+	/**
+	 * Filter the wp_get_nav_menu_object() result to supply the previewed menu object.
+	 *
+	 * Requesting a nav_menu object by anything but ID is not supported.
+	 *
+	 * @see wp_get_nav_menu_object()
+	 *
+	 * @param object|null $menu_obj Object returned by wp_get_nav_menu_object().
+	 * @param string      $menu_id  ID of the nav_menu term. Requests by slug or name will be ignored.
+	 * @return object|null
+	 */
+	function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) {
+		$ok = (
+			get_current_blog_id() === $this->_previewed_blog_id
+			&&
+			is_int( $menu_id )
+			&&
+			$menu_id === $this->term_id
+		);
+		if ( ! $ok ) {
+			return $menu_obj;
+		}
+
+		$setting_value = $this->value();
+
+		// Handle deleted menus.
+		if ( false === $setting_value ) {
+			return false;
+		}
+
+		// Handle sanitization failure by preventing short-circuiting.
+		if ( null === $setting_value ) {
+			return $menu_obj;
+		}
+
+		$menu_obj = (object) array_merge( array(
+				'term_id'          => $this->term_id,
+				'term_taxonomy_id' => $this->term_id,
+				'slug'             => sanitize_title( $setting_value['name'] ),
+				'count'            => 0,
+				'term_group'       => 0,
+				'taxonomy'         => self::TAXONOMY,
+				'filter'           => 'raw',
+			), $setting_value );
+
+		return $menu_obj;
+	}
+
+	/**
+	 * Filter the nav_menu_options option to include this menu's auto_add preference.
+	 *
+	 * @param array $nav_menu_options Nav menu options including auto_add.
+	 * @return array
+	 */
+	function filter_nav_menu_options( $nav_menu_options ) {
+		if ( $this->_previewed_blog_id !== get_current_blog_id() ) {
+			return $nav_menu_options;
+		}
+
+		$menu = $this->value();
+		$nav_menu_options = $this->filter_nav_menu_options_value(
+			$nav_menu_options,
+			$this->term_id,
+			false === $menu ? false : $menu['auto_add']
+		);
+
+		return $nav_menu_options;
+	}
+
+	/**
+	 * Sanitize an input.
+	 *
+	 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
+	 * we remove that in this override.
+	 *
+	 * @param array $value The value to sanitize.
+	 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value.
+	 */
+	public function sanitize( $value ) {
+		// Menu is marked for deletion.
+		if ( false === $value ) {
+			return $value;
+		}
+
+		// Invalid.
+		if ( ! is_array( $value ) ) {
+			return null;
+		}
+
+		$default = array(
+			'name'        => '',
+			'description' => '',
+			'parent'      => 0,
+			'auto_add'    => false,
+		);
+		$value = array_merge( $default, $value );
+		$value = wp_array_slice_assoc( $value, array_keys( $default ) );
+
+		$value['name']        = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php.
+		$value['description'] = sanitize_text_field( $value['description'] );
+		$value['parent']      = max( 0, intval( $value['parent'] ) );
+		$value['auto_add']    = ! empty( $value['auto_add'] );
+
+		/** This filter is documented in wp-includes/class-wp-customize-setting.php */
+		return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
+	}
+
+	/**
+	 * Create/update the nav_menu term for this setting.
+	 *
+	 * Any created menus will have their assigned term IDs exported to the client
+	 * via the customize_save_response filter. Likewise, any errors will be exported
+	 * to the client via the customize_save_response() filter.
+	 *
+	 * To delete a menu, the client can send false as the value.
+	 *
+	 * @see wp_update_nav_menu_object()
+	 *
+	 * @param array|false $value {
+	 *     The value to update. Note that slug cannot be updated via wp_update_nav_menu_object().
+	 *     If false, then the menu will be deleted entirely.
+	 *
+	 *     @type string $name        The name of the menu to save.
+	 *     @type string $description The term description. Default empty string.
+	 *     @type int    $parent      The id of the parent term. Default 0.
+	 *     @type bool   $auto_add    Whether pages will auto_add to this menu. Default false.
+	 * }
+	 * @return void
+	 */
+	protected function update( $value ) {
+		if ( $this->is_updated ) {
+			return;
+		}
+
+		$this->is_updated = true;
+		$is_placeholder   = ( $this->term_id < 0 );
+		$is_delete        = ( false === $value );
+
+		add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
+
+		$auto_add = null;
+		if ( $is_delete ) {
+			// If the current setting term is a placeholder, a delete request is a no-op.
+			if ( $is_placeholder ) {
+				$this->update_status = 'deleted';
+			} else {
+				$r = wp_delete_nav_menu( $this->term_id );
+
+				if ( is_wp_error( $r ) ) {
+					$this->update_status = 'error';
+					$this->update_error  = $r;
+				} else {
+					$this->update_status = 'deleted';
+					$auto_add = false;
+				}
+			}
+		} else {
+			// Insert or update menu.
+			$menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) );
+			if ( isset( $value['name'] ) ) {
+				$menu_data['menu-name'] = $value['name'];
+			}
+
+			$r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data );
+			if ( is_wp_error( $r ) ) {
+				$this->update_status = 'error';
+				$this->update_error  = $r;
+			} else {
+				if ( $is_placeholder ) {
+					$this->previous_term_id = $this->term_id;
+					$this->term_id          = $r;
+					$this->update_status    = 'inserted';
+				} else {
+					$this->update_status = 'updated';
+				}
+
+				$auto_add = $value['auto_add'];
+			}
+		}
+
+		if ( null !== $auto_add ) {
+			$nav_menu_options = $this->filter_nav_menu_options_value(
+				(array) get_option( 'nav_menu_options', array() ),
+				$this->term_id,
+				$auto_add
+			);
+			update_option( 'nav_menu_options', $nav_menu_options );
+		}
+
+		// Make sure that new menus assigned to nav menu locations use their new IDs.
+		if ( 'inserted' === $this->update_status ) {
+			foreach ( $this->manager->settings() as $setting ) {
+				if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) {
+					continue;
+				}
+
+				$post_value = $setting->post_value( null );
+				if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) {
+					$this->manager->set_post_value( $setting->id, $this->term_id );
+					$setting->save();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Update a nav_menu_options array.
+	 *
+	 * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options()
+	 * @see WP_Customize_Nav_Menu_Setting::update()
+	 *
+	 * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ).
+	 * @param int   $menu_id          The term ID for the given menu.
+	 * @param bool  $auto_add         Whether to auto-add or not.
+	 * @return array
+	 */
+	protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) {
+		$nav_menu_options = (array) $nav_menu_options;
+		if ( ! isset( $nav_menu_options['auto_add'] ) ) {
+			$nav_menu_options['auto_add'] = array();
+		}
+
+		$i = array_search( $menu_id, $nav_menu_options['auto_add'] );
+		if ( $auto_add && false === $i ) {
+			array_push( $nav_menu_options['auto_add'], $this->term_id );
+		} else if ( ! $auto_add && false !== $i ) {
+			array_splice( $nav_menu_options['auto_add'], $i, 1 );
+		}
+
+		return $nav_menu_options;
+	}
+
+	/**
+	 * Export data for the JS client.
+	 *
+	 * @see WP_Customize_Nav_Menu_Setting::update()
+	 *
+	 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
+	 * @return array
+	 */
+	function amend_customize_save_response( $data ) {
+		if ( ! isset( $data['nav_menu_updates'] ) ) {
+			$data['nav_menu_updates'] = array();
+		}
+
+		$data['nav_menu_updates'][] = array(
+			'term_id'          => $this->term_id,
+			'previous_term_id' => $this->previous_term_id,
+			'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
+			'status'           => $this->update_status,
+		);
+
+		return $data;
+	}
+}
Index: src/wp-includes/script-loader.php
===================================================================
--- src/wp-includes/script-loader.php	(revision 32795)
+++ src/wp-includes/script-loader.php	(working copy)
@@ -406,6 +406,9 @@
 	$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 );
 	$scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
 
+	$scripts->add( 'menu-customizer', "/wp-admin/js/menu-customizer$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu', 'wp-a11y' ) );
+	$scripts->add( 'customize-menus-preview', "/wp-admin/js/customize-menus-preview$suffix.js", array( 'customize-preview', 'wp-util' ), false, 1 );
+
 	$scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
 
 	$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
@@ -656,13 +659,15 @@
 	$suffix = SCRIPT_DEBUG ? '' : '.min';
 
 	// Admin CSS
-	$styles->add( 'wp-admin',           "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
-	$styles->add( 'login',              "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
-	$styles->add( 'install',            "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
-	$styles->add( 'wp-color-picker',    "/wp-admin/css/color-picker$suffix.css" );
-	$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
-	$styles->add( 'customize-widgets',  "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
-	$styles->add( 'press-this',         "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
+	$styles->add( 'wp-admin',                "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
+	$styles->add( 'login',                   "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
+	$styles->add( 'install',                 "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
+	$styles->add( 'wp-color-picker',         "/wp-admin/css/color-picker$suffix.css" );
+	$styles->add( 'customize-controls',      "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
+	$styles->add( 'customize-widgets',       "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
+	$styles->add( 'menu-customizer',         "/wp-admin/css/menu-customizer$suffix.css" );
+	$styles->add( 'customize-menus-preview', "/wp-admin/css/customize-menus-preview$suffix.css" );
+	$styles->add( 'press-this',              "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
 
 	$styles->add( 'ie',                 "/wp-admin/css/ie$suffix.css" );
 	$styles->add_data( 'ie', 'conditional', 'lte IE 7' );