diff --git a/core/includes/common.inc b/core/includes/common.inc
index 75dacc2..3e01739 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -70,7 +70,7 @@ define('CSS_DEFAULT', 0);
 define('CSS_THEME', 100);
 
 /**
- * The default group for JavaScript libraries, settings or jQuery plugins added
+ * The default group for JavaScript libraries or jQuery plugins added
  * to the page.
  */
 define('JS_LIBRARY', -100);
@@ -86,6 +86,11 @@ define('JS_DEFAULT', 0);
 define('JS_THEME', 100);
 
 /**
+ * The default group for JavaScript settings added to the page.
+ */
+define('JS_SETTING', 200);
+
+/**
  * Error code indicating that the request made by drupal_http_request() exceeded
  * the specified timeout.
  */
@@ -3921,6 +3926,9 @@ function drupal_region_class($region) {
  *     a JavaScript file. Defaults to TRUE.
  *   - preprocess: If TRUE and JavaScript aggregation is enabled, the script
  *     file will be aggregated. Defaults to TRUE.
+ *   - browsers: An array containing information specifying which browsers
+ *     should load the JavaScript item. See
+ *     drupal_pre_render_conditional_comments() for details.
  *
  * @return
  *   The current array of JavaScript files, settings, and in-line code,
@@ -3966,9 +3974,10 @@ function drupal_add_js($data = NULL, $options = NULL) {
           ),
           'type' => 'setting',
           'scope' => 'header',
-          'group' => JS_LIBRARY,
+          'group' => JS_SETTING,
           'every_page' => TRUE,
           'weight' => 0,
+          'browsers' => array(),
         ),
         'core/misc/drupal.js' => array(
           'data' => 'core/misc/drupal.js',
@@ -3980,6 +3989,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'preprocess' => TRUE,
           'cache' => TRUE,
           'defer' => FALSE,
+          'browsers' => array(),
         ),
       );
       // Register all required libraries.
@@ -4027,6 +4037,7 @@ function drupal_js_defaults($data = NULL) {
     'preprocess' => TRUE,
     'version' => NULL,
     'data' => $data,
+    'browsers' => array(),
   );
 }
 
@@ -4082,13 +4093,66 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
     }
   }
 
-  $output = '';
-  // The index counter is used to keep aggregated and non-aggregated files in
-  // order by weight.
-  $index = 1;
-  $processed = array();
-  $files = array();
-  $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  // Sort the JavaScript so that it appears in the correct order.
+  uasort($items, 'drupal_sort_css_js');
+
+  // Provide the page with information about the individual JavaScript files
+  // used, information not otherwise available when aggregation is enabled.
+  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
+  unset($setting['ajaxPageState']['js']['settings']);
+  drupal_add_js($setting, 'setting');
+
+  // If we're outputting the header scope, then this might be the final time
+  // that drupal_get_js() is running, so add the setting to this output as well
+  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
+  // because drupal_get_js() was intentionally passed a $javascript argument
+  // stripped of settings, potentially in order to override how settings get
+  // output, so in this case, do not add the setting to this output.
+  if ($scope == 'header' && isset($items['settings'])) {
+    $items['settings']['data'][] = $setting;
+  }
+
+  // Render the HTML needed to load the JavaScript.
+  $elements = array(
+    '#type' => 'scripts',
+    '#items' => $items,
+  );
+
+  return drupal_render($elements);
+}
+
+/**
+ * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
+ *
+ * This function evaluates the aggregation enabled/disabled condition on a group
+ * by group basis by testing whether an aggregate file has been made for the
+ * group rather than by testing the site-wide aggregation setting. This allows
+ * this function to work correctly even if modules have implemented custom
+ * logic for grouping and aggregating files.
+ *
+ * @param $element
+ *   A render array containing:
+ *   - #items: The JavaScript items as returned by drupal_add_js() and
+ *     altered by drupal_get_js().
+ *   - #group_callback: A function to call to group #items. Following
+ *     this function, #aggregate_callback is called to aggregate items within
+ *     the same group into a single file.
+ *   - #aggregate_callback: A function to call to aggregate the items within
+ *     the groups arranged by the #group_callback function.
+ *
+ * @return
+ *   A render array that will render to a string of JavaScript tags.
+ *
+ * @see drupal_get_js()
+ */
+function drupal_pre_render_scripts($elements) {
+  // Group and aggregate the items.
+  if (isset($elements['#group_callback'])) {
+    $elements['#groups'] = $elements['#group_callback']($elements['#items']);
+  }
+  if (isset($elements['#aggregate_callback'])) {
+    $elements['#aggregate_callback']($elements['#groups']);
+  }
 
   // A dummy query-string is added to filenames, to gain control over
   // browser-caching. The string changes on every update or full cache
@@ -4108,110 +4172,182 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   // third-party code might require the use of a different query string.
   $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
 
-  // Sort the JavaScript so that it appears in the correct order.
-  uasort($items, 'drupal_sort_css_js');
-
-  // Provide the page with information about the individual JavaScript files
-  // used, information not otherwise available when aggregation is enabled.
-  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
-  unset($setting['ajaxPageState']['js']['settings']);
-  drupal_add_js($setting, 'setting');
-
-  // If we're outputting the header scope, then this might be the final time
-  // that drupal_get_js() is running, so add the setting to this output as well
-  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
-  // because drupal_get_js() was intentionally passed a $javascript argument
-  // stripped off settings, potentially in order to override how settings get
-  // output, so in this case, do not add the setting to this output.
-  if ($scope == 'header' && isset($items['settings'])) {
-    $items['settings']['data'][] = $setting;
-  }
-
-  // Loop through the JavaScript to construct the rendered output.
-  $element = array(
+  // Defaults for each SCRIPT element.
+  $element_defaults = array(
+    '#type' => 'html_tag',
     '#tag' => 'script',
     '#value' => '',
     '#attributes' => array(
       'type' => 'text/javascript',
     ),
   );
-  foreach ($items as $item) {
-    $query_string =  empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
 
-    switch ($item['type']) {
-      case 'setting':
-        $js_element = $element;
-        $js_element['#value_prefix'] = $embed_prefix;
-        $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
-        $js_element['#value_suffix'] = $embed_suffix;
-        $output .= theme('html_tag', array('element' => $js_element));
-        break;
+  // Loop through each group.
+  foreach ($elements['#groups'] as $group) {
+    // If a group of files has been aggregated into a single file,
+    // $group['data'] contains the URI of the aggregate file. Add a single
+    // script element for this file.
+    if ($group['type'] == 'file' && isset($group['data'])) {
+      $element = $element_defaults;
+      $element['#attributes']['src'] = file_create_url($group['data']);
+      $element['#browsers'] = $group['browsers'];
+      $elements[] = $element;
+    }
+    // For non-file types, and non-aggregated files, add a script element per
+    // item.
+    else {
+      foreach ($group['items'] as $item) {
+        // Element properties that do not depend on item type.
+        $element = $element_defaults;
+        if (!empty($item['defer'])) {
+          $element['#attributes']['defer'] = 'defer';
+        }
+        $element['#browsers'] = $item['browsers'];
 
-      case 'inline':
-        $js_element = $element;
-        if ($item['defer']) {
-          $js_element['#attributes']['defer'] = 'defer';
+        // Element properties that depend on item type.
+        switch ($item['type']) {
+          case 'setting':
+            $element['#value_prefix'] = $embed_prefix;
+            $element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
+            $element['#value_suffix'] = $embed_suffix;
+            break;
+
+          case 'inline':
+            $element['#value_prefix'] = $embed_prefix;
+            $element['#value'] = $item['data'];
+            $element['#value_suffix'] = $embed_suffix;
+            break;
+
+          case 'file':
+            $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
+            $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
+            $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
+            break;
+
+          case 'external':
+            $element['#attributes']['src'] = $item['data'];
+            break;
         }
-        $js_element['#value_prefix'] = $embed_prefix;
-        $js_element['#value'] = $item['data'];
-        $js_element['#value_suffix'] = $embed_suffix;
-        $processed[$index++] = theme('html_tag', array('element' => $js_element));
-        break;
 
+        $elements[] = $element;
+      }
+    }
+  }
+
+  return $elements;
+}
+
+/**
+ * Default callback to group JavaScript items.
+ *
+ * This function arranges the JavaScript items that are in the #items property
+ * of the scripts element into groups. When aggregation is enabled, files within
+ * a group are aggregated into a single file, significantly improving page
+ * loading performance by minimizing network traffic overhead.
+ *
+ * This function puts multiple items into the same group if they are groupable
+ * and if they are for the same browsers. Items of the 'file' type are groupable
+ * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
+ * 'external' type are not groupable.
+ *
+ * This function also ensures that the process of grouping items does not change
+ * their relative order. This requirement may result in multiple groups for the
+ * same type and browsers, if needed to accommodate other items in
+ * between.
+ *
+ * @param $javascript
+ *   An array of JavaScript items, as returned by drupal_add_js(), but after
+ *   alteration performed by drupal_get_js().
+ *
+ * @return
+ *   An array of JavaScript groups. Each group contains the same keys (e.g.,
+ *   'data', etc.) as a JavaScript item from the $javascript parameter, with the
+ *   value of each key applying to the group as a whole. Each group also
+ *   contains an 'items' key, which is the subset of items from $javascript that
+ *   are in the group.
+ *
+ * @see drupal_pre_render_scripts()
+ */
+function drupal_group_js($javascript) {
+  $groups = array();
+  // If a group can contain multiple items, we track the information that must
+  // be the same for each item in the group, so that when we iterate the next
+  // item, we can determine if it can be put into the current group, or if a
+  // new group needs to be made for it.
+  $current_group_keys = NULL;
+  $index = -1;
+  foreach ($javascript as $item) {
+    // The browsers for which the JavaScript item needs to be loaded is part of
+    // the information that determines when a new group is needed, but the order
+    // of keys in the array doesn't matter, and we don't want a new group if all
+    // that's different is that order.
+    ksort($item['browsers']);
+
+    switch ($item['type']) {
       case 'file':
-        $js_element = $element;
-        if (!$item['preprocess'] || !$preprocess_js) {
-          if ($item['defer']) {
-            $js_element['#attributes']['defer'] = 'defer';
-          }
-          $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
-          $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
-          $processed[$index++] = theme('html_tag', array('element' => $js_element));
-        }
-        else {
-          // By increasing the index for each aggregated file, we maintain
-          // the relative ordering of JS by weight. We also set the key such
-          // that groups are split by items sharing the same 'group' value and
-          // 'every_page' flag. While this potentially results in more aggregate
-          // files, it helps make each one more reusable across a site visit,
-          // leading to better front-end performance of a website as a whole.
-          // See drupal_add_js() for details.
-          $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index;
-          $processed[$key] = '';
-          $files[$key][$item['data']] = $item;
-        }
+        // Group file items if their 'preprocess' flag is TRUE.
+        // Help ensure maximum reuse of aggregate files by only grouping
+        // together items that share the same 'group' value and 'every_page'
+        // flag. See drupal_add_js() for details about that.
+        $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE;
         break;
 
       case 'external':
-        $js_element = $element;
-        // Preprocessing for external JavaScript files is ignored.
-        if ($item['defer']) {
-          $js_element['#attributes']['defer'] = 'defer';
-        }
-        $js_element['#attributes']['src'] = $item['data'];
-        $processed[$index++] = theme('html_tag', array('element' => $js_element));
+      case 'setting':
+      case 'inline':
+        // Do not group external, settings, and inline items.
+        $group_keys = FALSE;
         break;
     }
+
+    // If the group keys don't match the most recent group we're working with,
+    // then a new group must be made.
+    if ($group_keys !== $current_group_keys) {
+      $index++;
+      // Initialize the new group with the same properties as the first item
+      // being placed into it. The item's 'data' and 'weight' properties are
+      // unique to the item and should not be carried over to the group.
+      $groups[$index] = $item;
+      unset($groups[$index]['data'], $groups[$index]['weight']);
+      $groups[$index]['items'] = array();
+      $current_group_keys = $group_keys ? $group_keys : NULL;
+    }
+
+    // Add the item to the current group.
+    $groups[$index]['items'][] = $item;
   }
 
-  // Aggregate any remaining JS files that haven't already been output.
-  if ($preprocess_js && count($files) > 0) {
-    foreach ($files as $key => $file_set) {
-      $uri = drupal_build_js_cache($file_set);
-      // Only include the file if was written successfully. Errors are logged
-      // using watchdog.
-      if ($uri) {
-        $preprocess_file = file_create_url($uri);
-        $js_element = $element;
-        $js_element['#attributes']['src'] = $preprocess_file;
-        $processed[$key] = theme('html_tag', array('element' => $js_element));
+  return $groups;
+}
+
+/**
+ * Default callback to aggregate JavaScript files.
+ *
+ * Having the browser load fewer JavaScript files results in much faster page
+ * loads than when it loads many small files. This function aggregates files
+ * within the same group into a single file unless the site-wide setting to do
+ * so is disabled (commonly the case during site development). To optimize
+ * download, it also compresses the aggregate files by removing comments,
+ * whitespace, and other unnecessary content.
+ *
+ * @param $js_groups
+ *   An array of JavaScript groups as returned by drupal_group_js(). For each
+ *   group that is aggregated, this function sets the value of the group's
+ *   'data' key to the URI of the aggregate file.
+ *
+ * @see drupal_group_js()
+ * @see drupal_pre_render_scripts()
+ */
+function drupal_aggregate_js(&$js_groups) {
+  // Only aggregate when the site is configured to do so, and not during an
+  // update.
+  if (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
+    foreach ($js_groups as $key => $group) {
+      if ($group['type'] == 'file' && $group['preprocess']) {
+        $js_groups[$key]['data'] = drupal_build_js_cache($group['items']);
       }
     }
   }
-
-  // Keep the order of JS files consistent as some are preprocessed and others are not.
-  // Make sure any inline or JS setting variables appear last after libraries have loaded.
-  return implode('', $processed) . $output;
 }
 
 /**
@@ -4745,10 +4881,10 @@ function drupal_build_js_cache($files) {
 
   if (empty($uri) || !file_exists($uri)) {
     // Build aggregate JS file.
-    foreach ($files as $path => $info) {
+    foreach ($files as $info) {
       if ($info['preprocess']) {
         // Append a ';' and a newline after each JS file to prevent them from running together.
-        $contents .= file_get_contents($path) . ";\n";
+        $contents .= file_get_contents($info['data']) . ";\n";
       }
     }
     // Prefix filename to prevent blocking by firewalls which reject files
diff --git a/core/modules/simpletest/tests/common.test b/core/modules/simpletest/tests/common.test
index 7a68f44..c9640ba 100644
--- a/core/modules/simpletest/tests/common.test
+++ b/core/modules/simpletest/tests/common.test
@@ -1316,6 +1316,75 @@ class JavaScriptTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Test adding JavaScript within conditional comments.
+   *
+   * @see drupal_pre_render_conditional_comments()
+   */
+  function testBrowserConditionalComments() {
+    $default_query_string = variable_get('css_js_query_string', '0');
+
+    drupal_add_js('core/misc/collapse.js', array('browsers' => array('IE' => 'lte IE 8', '!IE' => FALSE)));
+    drupal_add_js('jQuery(function () { });', array('type' => 'inline', 'browsers' => array('IE' => FALSE)));
+    $javascript = drupal_get_js();
+
+    $expected_1 = "<!--[if lte IE 8]>\n" . '<script type="text/javascript" src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
+    $expected_2 = "<!--[if !IE]><!-->\n" . '<script type="text/javascript">' . "\n<!--//--><![CDATA[//><!--\n" . 'jQuery(function () { });' . "\n//--><!]]>\n" . '</script>' . "\n<!--<![endif]-->";
+
+    $this->assertTrue(strpos($javascript, $expected_1) > 0, t('Rendered JavaScript within downlevel-hidden conditional comments.'));
+    $this->assertTrue(strpos($javascript, $expected_2) > 0, t('Rendered JavaScript within downlevel-revealed conditional comments.'));
+  }
+
+  /**
+   * Test JavaScript versioning.
+   */
+  function testVersionQueryString() {
+    drupal_add_js('core/misc/collapse.js', array('version' => 'foo'));
+    drupal_add_js('core/misc/ajax.js', array('version' => 'bar'));
+    $javascript = drupal_get_js();
+    $this->assertTrue(strpos($javascript, 'core/misc/collapse.js?v=foo') > 0 && strpos($javascript, 'core/misc/ajax.js?v=bar') > 0 , t('JavaScript version identifiers correctly appended to URLs'));
+  }
+
+  /**
+   * Test JavaScript grouping and aggregation.
+   */
+  function testAggregation() {
+    $default_query_string = variable_get('css_js_query_string', '0');
+
+    // To optimize aggregation, items with the 'every_page' option are ordered
+    // ahead of ones without. The order of JavaScript execution must be the
+    // same regardless of whether aggregation is enabled, so ensure this
+    // expected order, first with aggregation off.
+    drupal_add_js('core/misc/ajax.js');
+    drupal_add_js('core/misc/authorize.js', array('every_page' => TRUE));
+    drupal_add_js('core/misc/autocomplete.js');
+    drupal_add_js('core/misc/batch.js', array('every_page' => TRUE));
+    $javascript = drupal_get_js();
+    $expected = implode("\n", array(
+      '<script type="text/javascript" src="' . file_create_url('core/misc/authorize.js') . '?' . $default_query_string . '"></script>',
+      '<script type="text/javascript" src="' . file_create_url('core/misc/batch.js') . '?' . $default_query_string . '"></script>',
+      '<script type="text/javascript" src="' . file_create_url('core/misc/ajax.js') . '?' . $default_query_string . '"></script>',
+      '<script type="text/javascript" src="' . file_create_url('core/misc/autocomplete.js') . '?' . $default_query_string . '"></script>',
+    ));
+    $this->assertTrue(strpos($javascript, $expected) > 0, t('Unaggregated JavaScript is added in the expected group order.'));
+
+    // Now ensure that with aggregation on, one file is made for the
+    // 'every_page' files, and one file is made for the others.
+    drupal_static_reset('drupal_add_js');
+    variable_set('preprocess_js', 1);
+    drupal_add_js('core/misc/ajax.js');
+    drupal_add_js('core/misc/authorize.js', array('every_page' => TRUE));
+    drupal_add_js('core/misc/autocomplete.js');
+    drupal_add_js('core/misc/batch.js', array('every_page' => TRUE));
+    $js_items = drupal_add_js();
+    $javascript = drupal_get_js();
+    $expected = implode("\n", array(
+      '<script type="text/javascript" src="' . file_create_url(drupal_build_js_cache(array('core/misc/authorize.js' => $js_items['core/misc/authorize.js'], 'core/misc/batch.js' => $js_items['core/misc/batch.js']))) . '"></script>',
+      '<script type="text/javascript" src="' . file_create_url(drupal_build_js_cache(array('core/misc/ajax.js' => $js_items['core/misc/ajax.js'], 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']))) . '"></script>',
+    ));
+    $this->assertTrue(strpos($javascript, $expected) > 0, t('JavaScript is aggregated in the expected groups and order.'));
+  }
+
+  /**
    * Test JavaScript ordering.
    */
   function testRenderOrder() {
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index e2e16ae..940097d 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -315,6 +315,12 @@ function system_element_info() {
     '#group_callback' => 'drupal_group_css',
     '#aggregate_callback' => 'drupal_aggregate_css',
   );
+  $types['scripts'] = array(
+    '#items' => array(),
+    '#pre_render' => array('drupal_pre_render_scripts'),
+    '#group_callback' => 'drupal_group_js',
+    '#aggregate_callback' => 'drupal_aggregate_js',
+  );
 
   // Input elements.
   $types['submit'] = array(
