Index: src/wp-admin/js/customize-controls.js
===================================================================
--- src/wp-admin/js/customize-controls.js	(revision 41728)
+++ src/wp-admin/js/customize-controls.js	(working copy)
@@ -1665,14 +1665,15 @@
 				section.closeDetails();
 			});
 
-			// Filter-search all theme objects loaded in the section.
-			section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
-					section.filterSearch( event.currentTarget );
-			});
+			if ( 'local' === section.params.filter_type ) {
+				// Filter-search all theme objects loaded in the section.
+				section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
+						section.filterSearch( event.currentTarget.value );
+				});
 
-			// Event listeners for remote wporg queries with user-entered terms.
-			if ( 'wporg' === section.params.action ) {
+			} else if ( 'remote' === section.params.filter_type ) {
 
+				// Event listeners for remote queries with user-entered terms.
 				// Search terms.
 				debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
 				section.contentContainer.on( 'input', '.wp-filter-search', function() {
@@ -1688,29 +1689,29 @@
 					section.filtersChecked();
 					section.checkTerm( section );
 				});
+			}
 
-				// Toggle feature filter sections.
-				section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
-					$( e.currentTarget )
-						.toggleClass( 'open' )
-						.attr( 'aria-expanded', function( i, attr ) {
-							return 'true' === attr ? 'false' : 'true';
-						})
-						.next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
-							if ( 0 === section.filtersHeight ) {
-								section.filtersHeight = $( this ).height();
+			// Toggle feature filters.
+			section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
+				$( e.currentTarget )
+					.toggleClass( 'open' )
+					.attr( 'aria-expanded', function( i, attr ) {
+						return 'true' === attr ? 'false' : 'true';
+					})
+					.next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
+						if ( 0 === section.filtersHeight ) {
+							section.filtersHeight = $( this ).height();
 
-								// First time, so it's opened.
-								section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
-							}
-						});
-					if ( $( e.currentTarget ).hasClass( 'open' ) ) {
-						section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
-					} else {
-						section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
-					}
-				});
-			}
+							// First time, so it's opened.
+							section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
+						}
+					});
+				if ( $( e.currentTarget ).hasClass( 'open' ) ) {
+					section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
+				} else {
+					section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
+				}
+			});
 
 			// Setup section cross-linking.
 			section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() {
@@ -1754,7 +1755,7 @@
 
 				// Try to load controls if none are loaded yet.
 				if ( 0 === section.loaded ) {
-					section.loadControls();
+					section.loadThemes();
 				}
 
 				// Collapse any sibling sections/panels
@@ -1769,13 +1770,16 @@
 							section.contentContainer.find( '.wp-filter-search' ).val( searchTerm );
 
 							// Directly initialize an empty remote search to avoid a race condition.
-							if ( '' === searchTerm && '' !== section.term && 'installed' !== section.params.action ) {
+							if ( '' === searchTerm && '' !== section.term && 'local' !== section.params.filter_type ) {
 								section.term = '';
 								section.initializeNewQuery( section.term, section.tags );
 							} else {
-								section.checkTerm( section );
+								if ( 'remote' === section.params.filter_type ) {
+									section.checkTerm( section );
+								} else if ( 'local' === section.params.filter_type ) {
+									section.filterSearch( searchTerm );
+								}
 							}
-							section.filterSearch( section.contentContainer.find( '.wp-filter-search' ).get( 0 ) );
 						}
 						otherSection.collapse( { duration: args.duration } );
 					}
@@ -1825,7 +1829,7 @@
 		 *
 		 * @returns {void}
 		 */
-		loadControls: function() {
+		loadThemes: function() {
 			var section = this, params, page, request;
 
 			if ( section.loading ) {
@@ -1842,8 +1846,8 @@
 				'page': page
 			};
 
-			// Add fields for wporg actions.
-			if ( 'wporg' === section.params.action ) {
+			// Add fields for remote filtering.
+			if ( 'remote' === section.params.filter_type ) {
 				params.search = section.term;
 				params.tags = section.tags;
 			}
@@ -1854,7 +1858,7 @@
 			section.container.find( '.no-themes' ).hide();
 			request = wp.ajax.post( 'customize_load_themes', params );
 			request.done(function( data ) {
-				var themes = data.themes, newThemeControls;
+				var themes = data.themes;
 
 				// Stop and try again if the term changed while loading.
 				if ( '' !== section.nextTerm || '' !== section.nextTags ) {
@@ -1867,27 +1871,14 @@
 					section.nextTerm = '';
 					section.nextTags = '';
 					section.loading = false;
-					section.loadControls();
+					section.loadThemes();
 					return;
 				}
 
 				if ( 0 !== themes.length ) {
-					newThemeControls = [];
 
-					// Add controls for each theme.
-					_.each( themes, function( theme ) {
-						var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
-							type: 'theme',
-							section: section.params.id,
-							theme: theme,
-							priority: section.loaded + 1
-						} );
-
-						api.control.add( themeControl );
-						newThemeControls.push( themeControl );
-						section.loaded = section.loaded + 1;
-					});
-
+					section.loadControls( themes, page );
+					
 					if ( 1 === page ) {
 
 						// Pre-load the first 3 theme screenshots.
@@ -1898,15 +1889,14 @@
 								img.src = src;
 							}
 						});
-						if ( 'installed' !== section.params.action ) {
+						if ( 'local' !== section.params.filter_type ) {
 							wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
 						}
-					} else {
-						Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
 					}
+
 					_.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible.
 
-					if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
+					if ( 'local' === section.params.filter_type || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
 						section.fullyLoaded = true;
 					}
 				} else {
@@ -1917,7 +1907,7 @@
 						section.fullyLoaded = true;
 					}
 				}
-				if ( 'installed' === section.params.action ) {
+				if ( 'local' === section.params.filter_type ) {
 					section.updateCount(); // Count of visible theme controls.
 				} else {
 					section.updateCount( data.info.results ); // Total number of results including pages not yet loaded.
@@ -1943,6 +1933,37 @@
 		},
 
 		/**
+		 * Loads controls into the section from data received from loadThemes().
+		 *
+		 * @since 4.9.0
+		 * @param {Array} themes - Array of theme data to create controls with.
+		 * @param {integer} page - Page of results being loaded.
+		 * @returns {void}
+		 */
+		loadControls: function( themes, page ) {
+			var newThemeControls = [],
+				section = this;
+
+			// Add controls for each theme.
+			_.each( themes, function( theme ) {
+				var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
+					type: 'theme',
+					section: section.params.id,
+					theme: theme,
+					priority: section.loaded + 1
+				} );
+
+				api.control.add( themeControl );
+				newThemeControls.push( themeControl );
+				section.loaded = section.loaded + 1;
+			});
+
+			if ( 1 !== page ) {
+				Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
+			}
+		},
+
+		/**
 		 * Determines whether more themes should be loaded, and loads them.
 		 *
 		 * @since 4.9.0
@@ -1957,7 +1978,7 @@
 				threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance.
 
 				if ( bottom > threshold ) {
-					section.loadControls();
+					section.loadThemes();
 				}
 			}
 		},
@@ -1967,15 +1988,14 @@
 		 *
 		 * @since 4.9.0
 		 *
-		 * @param {Element} el - The search input element as a raw JS object.
+		 * @param {string} term - The raw search input value.
 		 * @returns {void}
 		 */
-		filterSearch: function( el ) {
+		filterSearch: function( term ) {
 			var count = 0,
 				visible = false,
 				section = this,
-				noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'wporg' !== section.params.action ) ? '.no-themes-local' : '.no-themes',
-				term = el.value.toLowerCase().trim().replace( '-', ' ' ),
+				noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'remote' !== section.params.filter_type ) ? '.no-themes-local' : '.no-themes',
 				controls = section.controls();
 
 			if ( section.loading ) {
@@ -1982,8 +2002,11 @@
 				return;
 			}
 
+			// Standardize search term format and split into an array of individual words.
+			term = term.toLowerCase().trim().replace( '-', ' ' ).split( ' ' );
+
 			_.each( controls, function( control ) {
-				visible = control.filter( term );
+				visible = control.filter( term ); // Shows/hides and sorts control based on the applicability of the search term.
 				if ( visible ) {
 					count = count + 1;
 				}
@@ -1997,6 +2020,7 @@
 			}
 
 			section.renderScreenshots();
+			api.reflowPaneContents();
 
 			// Update theme count.
 			section.updateCount( count );
@@ -2012,7 +2036,7 @@
 		 */
 		checkTerm: function( section ) {
 			var newTerm;
-			if ( 'wporg' === section.params.action ) {
+			if ( 'remote' === section.params.filter_type ) {
 				newTerm = section.contentContainer.find( '.wp-filter-search' ).val();
 				if ( section.term !== newTerm ) {
 					section.initializeNewQuery( newTerm, section.tags );
@@ -2052,7 +2076,11 @@
 				if ( section.loading ) {
 					section.nextTags = tags;
 				} else {
-					section.initializeNewQuery( section.term, tags );
+					if ( 'remote' === section.params.filter_type ) {
+						section.initializeNewQuery( section.term, tags );
+					} else if ( 'local' === section.params.filter_type ) {
+						section.filterSearch( tags.join( ' ' ) );
+					}
 				}
 			}
 		},
@@ -2078,14 +2106,14 @@
 			section.fullyLoaded = false;
 			section.screenshotQueue = null;
 
-			// Run a new query, with loadControls handling paging, etc.
+			// Run a new query, with loadThemes handling paging, etc.
 			if ( ! section.loading ) {
 				section.term = newTerm;
 				section.tags = newTags;
-				section.loadControls();
+				section.loadThemes();
 			} else {
-				section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded.
-				section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded.
+				section.nextTerm = newTerm; // This will reload from loadThemes() with the newest term once the current batch is loaded.
+				section.nextTags = newTags; // This will reload from loadThemes() with the newest tags once the current batch is loaded.
 			}
 			if ( ! section.expanded() ) {
 				section.expand(); // Expand the section if it isn't expanded.
@@ -4665,20 +4693,50 @@
 		 * Show or hide the theme based on the presence of the term in the title, description, tags, and author.
 		 *
 		 * @since 4.2.0
+		 * @param {Array} terms - An array of terms to search for.
 		 * @returns {boolean} Whether a theme control was activated or not.
 		 */
-		filter: function( term ) {
+		filter: function( terms ) {
 			var control = this,
+				matchCount = 0,
 				haystack = control.params.theme.name + ' ' +
 					control.params.theme.description + ' ' +
 					control.params.theme.tags + ' ' +
-					control.params.theme.author;
+					control.params.theme.author + ' ';
 			haystack = haystack.toLowerCase().replace( '-', ' ' );
-			if ( -1 !== haystack.search( term ) ) {
+
+			// Back-compat for behavior in WordPress 4.2.0 to 4.8.X.
+			if ( ! terms instanceof Array ) {
+				terms = [terms];
+			}
+
+			// Always give exact name matches highest ranking.
+			if ( control.params.theme.name.toLowerCase() === terms.join( ' ' ) ) {
+				matchCount = 100;
+			} else {
+
+				// Search for and weight (by 10) complete term matches.
+				matchCount = matchCount + 10 * ( haystack.split( terms.join( ' ' ) ).length - 1 );
+
+				// Search for each term individually (as whole-word and partial match) and sum weighted match counts.
+				_.each( terms, function( term ) {
+					matchCount = matchCount + 2 * ( haystack.split( term + ' ' ).length - 1 ); // Whole-word, double-weighted.
+					matchCount = matchCount + haystack.split( term ).length - 1; // Partial word, to minimize empty intermediate searches while typing.
+				});
+
+				// Upper limit on match ranking.
+				if ( matchCount > 99 ) {
+					matchCount = 99;
+				}
+			}
+
+			if ( 0 !== matchCount ) {
 				control.activate();
+				control.params.priority = 101 - matchCount; // Sort results by match count.
 				return true;
 			} else {
-				control.deactivate();
+				control.deactivate(); // Hide control
+				control.params.priority = 101;
 				return false;
 			}
 		},
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 41728)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -4275,6 +4275,7 @@
 			$this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
 				'title'       => __( 'WordPress.org themes' ),
 				'action'      => 'wporg',
+				'filter_type' => 'remote',
 				'capability'  => 'install_themes',
 				'panel'       => 'themes',
 				'priority'    => 5,
@@ -4806,8 +4807,30 @@
 		}
 		$theme_action = sanitize_key( $_POST['theme_action'] );
 		$themes = array();
+		$args = array();
 
+		// Define query filters based on user input.
+		if ( ! array_key_exists( 'search', $_POST ) ) {
+			$args['search'] = '';
+		} else {
+			$args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
+		}
+
+		if ( ! array_key_exists( 'tags', $_POST ) ) {
+			$args['tag'] = '';
+		} else {
+			$args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
+		}
+
+		if ( ! array_key_exists( 'page', $_POST ) ) {
+			$args['page'] = 1;
+		} else {
+			$args['page'] = absint( $_POST['page'] );
+		}
+
 		require_once ABSPATH . 'wp-admin/includes/theme.php';
+
+		// Load all installed themes from wp_prepare_themes_for_js().
 		if ( 'installed' === $theme_action ) {
 			$themes = array( 'themes' => wp_prepare_themes_for_js() );
 			foreach ( $themes['themes'] as &$theme ) {
@@ -4814,6 +4837,8 @@
 				$theme['type'] = 'installed';
 				$theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
 			}
+
+		// Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
 		} elseif ( 'wporg' === $theme_action ) {
 			if ( ! current_user_can( 'install_themes' ) ) {
 				wp_die( -1 );
@@ -4820,9 +4845,8 @@
 			}
 
 			// Arguments for all queries.
-			$args = array(
+			$wporg_args = array(
 				'per_page' => 100,
-				'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
 				'fields' => array(
 					'screenshot_url' => true,
 					'description' => true,
@@ -4838,19 +4862,8 @@
 				),
 			);
 
-			// Define query filters based on user input.
-			if ( ! array_key_exists( 'search', $_POST ) ) {
-				$args['search'] = '';
-			} else {
-				$args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
-			}
+			$args = array_merge( $wporg_args, $args );
 
-			if ( ! array_key_exists( 'tags', $_POST ) ) {
-				$args['tag'] = '';
-			} else {
-				$args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
-			}
-
 			if ( '' === $args['search'] && '' === $args['tag'] ) {
 				$args['browse'] = 'new'; // Sort by latest themes by default.
 			}
@@ -4920,6 +4933,24 @@
 				unset( $theme->author );
 			} // End foreach().
 		} // End if().
+
+
+		/**
+		 * Filters the theme data loaded in the customizer.
+		 *
+		 * This allows theme data to be loading from an external source,
+		 * or modification of data loaded from wp_prepare_themes_for_JS
+		 * or WordPress.org.
+		 *
+		 * @since 4.9.0
+		 *
+		 * @see WP_Customize_Manager::__construct()
+		 *
+		 * @param array $themes Nested array of theme data.
+		 * @param array $args   List of arguments, such as page, search term, and tags to query for.
+		 */
+		$themes = apply_filters( 'customize_load_themes_ajax', $themes, $args );
+
 		wp_send_json_success( $themes );
 	}
 
Index: src/wp-includes/customize/class-wp-customize-themes-section.php
===================================================================
--- src/wp-includes/customize/class-wp-customize-themes-section.php	(revision 41728)
+++ src/wp-includes/customize/class-wp-customize-themes-section.php	(working copy)
@@ -37,6 +37,17 @@
 	public $action = '';
 
 	/**
+	 * Theme section filter type.
+	 *
+	 * Determines whether filters are applied to loaded (local) themes or by initiating a new remote query (remote).
+	 * When filtering is local, the initial themes query is not paginated by default.
+	 *
+	 * @since 4.9.0
+	 * @var string
+	 */
+	public $filter_type = 'local';
+
+	/**
 	 * Get section parameters for JS.
 	 *
 	 * @since 4.9.0
@@ -45,6 +56,7 @@
 	public function json() {
 		$exported = parent::json();
 		$exported['action'] = $this->action;
+		$exported['filter_type'] = $this->filter_type;
 
 		return $exported;
 	}
