 core/modules/system/system.admin.inc  |   24 ++++++++++++--
 core/modules/system/system.base.css   |   10 ++++++
 core/modules/system/system.module     |    2 +-
 core/modules/system/system.modules.js |   56 +++++++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+), 3 deletions(-)

diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 37a2bf2..88b869c 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -809,6 +809,26 @@ function system_modules($form, $form_state = array()) {
     return system_modules_confirm_form($visible_files, $form_state['storage']);
   }
 
+  // JavaScript-only table filters.
+  $form['filters'] = array(
+    '#type' => 'container',
+    '#attributes' => array(
+      'class' => array('table-filter', 'js-show'),
+    ),
+  );
+  $form['filters']['text'] = array(
+    '#type' => 'search',
+    '#title' => t('Search module list'),
+    '#title_display' => 'invisible',
+    '#size' => 20,
+    '#placeholder' => t('Search modules...'),
+    '#attributes' => array(
+      'class' => array('table-filter-text'),
+      'data-table' => '#system-modules',
+      'autocomplete' => 'off',
+    ),
+  );
+
   $modules = array();
   $form['modules'] = array('#tree' => TRUE);
 
@@ -2336,11 +2356,11 @@ function theme_system_modules_details($variables) {
     $row[] = array('class' => array('checkbox'), 'data' => drupal_render($module['enable']));
 
     // Add the module label and expand/collapse functionalty.
-    $col2 = '<label id="' . $key . '" for="' . $module['enable']['#id'] . '" class="module-name">' . drupal_render($module['name']) . '</label>';
+    $col2 = '<label id="' . $key . '" for="' . $module['enable']['#id'] . '" class="module-name table-filter-text-source">' . drupal_render($module['name']) . '</label>';
     $row[] = array('class' => array('module'), 'data' => $col2);
 
     // Add the description, along with any modules it requires.
-    $description = '<span class="details"><span class="text">' . drupal_render($module['description']) . '</span></span>';
+    $description = '<span class="details"><span class="text table-filter-text-source">' . drupal_render($module['description']) . '</span></span>';
     if ($version || $requires || $required_by) {
       $description .= ' <div class="requirements">';
       if ($version) {
diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css
index 233c6d7..c386b3e 100644
--- a/core/modules/system/system.base.css
+++ b/core/modules/system/system.base.css
@@ -204,6 +204,16 @@ tr .ajax-progress-throbber .throbber {
 }
 
 /**
+ * For anything you want to show on page load only when JS is enabled.
+ */
+.js-show {
+  display: none;
+}
+.js .js-show {
+  display: block;
+}
+
+/**
  * Hide elements from all users.
  *
  * Used for elements which should not be immediately displayed to any user. An
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c621915..6d25a04 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2133,7 +2133,7 @@ function system_library_info() {
     ),
   );
   $libraries['drupal.system.modules'] = array(
-    'title' => 'System cron',
+    'title' => 'System modules',
     'version' => VERSION,
     'js' => array(
       drupal_get_path('module', 'system') . '/system.modules.js' => array(),
diff --git a/core/modules/system/system.modules.js b/core/modules/system/system.modules.js
index 0df6629..eeba481 100644
--- a/core/modules/system/system.modules.js
+++ b/core/modules/system/system.modules.js
@@ -64,4 +64,60 @@ Drupal.behaviors.hideModuleInformation = {
   }
 };
 
+/**
+ * Filters the module list table by a text input search string.
+ *
+ * Additionally accounts for multiple tables being wrapped in "package" details
+ * elements.
+ *
+ * Text search input: input.table-filter-text
+ * Target table:      input.table-filter-text[data-table]
+ * Source text:       .table-filter-text-source
+ */
+Drupal.behaviors.tableFilterByText = {
+  attach: function (context, settings) {
+    var $input = $('input.table-filter-text').once('table-filter-text');
+    var $table = $($input.attr('data-table'));
+    var $rows, $rowsAndDetails, $details;
+
+    function hidePackageDetails(index, element) {
+      var $details = $(element);
+      var $visibleRows = $details.find('table:not(.sticky-header)').find('tbody tr:visible');
+      $details.toggle($visibleRows.length > 0);
+    }
+
+    function filterModuleList (e) {
+      var query = $(e.target).val().toLowerCase();
+
+      function showModuleRow (index, row) {
+        var $row = $(row);
+        var $sources = $row.find('.table-filter-text-source');
+        var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+        $row.toggle(textMatch);
+      }
+
+      // Filter if the length of the query is at least 2 characters.
+      if (query.length >= 2) {
+        $rows.each(showModuleRow);
+
+        // Hide the package <details> if they don't have any visible rows.
+        // Note that we first show() all <details> to be able to use ':visible'.
+        $details.show().each(hidePackageDetails);
+      }
+      else {
+        $rowsAndDetails.show();
+      }
+    }
+
+    if ($table.length) {
+      $rowsAndDetails = $table.find('tr, details');
+      $rows = $table.find('tbody tr');
+      $details = $table.find('details');
+
+      // @todo Use autofocus attribute when possible.
+      $input.focus().on('keyup', filterModuleList);
+    }
+  }
+};
+
 }(jQuery, Drupal));
