diff --git a/includes/common.inc b/includes/common.inc
index d7189ab..10c859e 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3928,6 +3928,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 JS item. See drupal_pre_render_conditional_comments()
+ *     for details.
  *
  * @return
  *   The current array of JavaScript files, settings, and in-line code,
@@ -3976,6 +3979,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'group' => JS_LIBRARY,
           'every_page' => TRUE,
           'weight' => 0,
+          'browsers' => array(),
         ),
         'misc/drupal.js' => array(
           'data' => 'misc/drupal.js',
@@ -3987,6 +3991,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'preprocess' => TRUE,
           'cache' => TRUE,
           'defer' => FALSE,
+          'browsers' => array(),
         ),
       );
       // Register all required libraries.
@@ -4034,6 +4039,7 @@ function drupal_js_defaults($data = NULL) {
     'preprocess' => TRUE,
     'version' => NULL,
     'data' => $data,
+    'browsers' => array(),
   );
 }
 
@@ -4089,12 +4095,72 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
     }
   }
 
-  $output = '';
+  // 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.
+  $elements = array(
+    '#type' => 'scripts',
+    '#items' => $items,
+  );
+
+  return drupal_render($elements);
+}
+
+/**
+ * #pre_render callback to add the elements needed for JS 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 JS items as returned by drupal_add_js() and altered by
+ *     drupal_get_js().
+ *   - '#group_callback': A function to call to group #items to enable the use
+ *     of fewer tags by aggregating files statements within a single tag.
+ *   - '#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 JS 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']);
+  }
+
   // The index counter is used to keep aggregated and non-aggregated files in
   // order by weight.
   $index = 1;
   $processed = array();
   $files = array();
+  $new_elements = array();
   $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
 
   // A dummy query-string is added to filenames, to gain control over
@@ -4105,120 +4171,207 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   // page request.
   $default_query_string = variable_get('css_js_query_string', '0');
 
+  // Since JavaScript may look for arguments in the URL and act on them, some
+  // third-party code might require the use of a different query string.
+  $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
+
   // For inline JavaScript to validate as XHTML, all JavaScript containing
   // XHTML needs to be wrapped in CDATA. To make that backwards compatible
   // with HTML 4, we need to comment out the CDATA-tag.
   $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
   $embed_suffix = "\n//--><!]]>\n";
 
-  // Since JavaScript may look for arguments in the URL and act on them, some
-  // 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(
     '#tag' => 'script',
+    '#type' => 'html_tag',
     '#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']) {
+  // Loop through each group.
+  foreach ($elements['#groups'] as $group) {
+    $query_string =  empty($group['version']) ? $default_query_string : $js_version_string . $group['version'];
+
+    switch ($group['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'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($group['items'][0]['data'])) . ");";
         $js_element['#value_suffix'] = $embed_suffix;
-        $output .= theme('html_tag', array('element' => $js_element));
+        $new_elements[] = $js_element;
         break;
-
       case 'inline':
-        $js_element = $element;
-        if ($item['defer']) {
-          $js_element['#attributes']['defer'] = 'defer';
-        }
-        $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;
-
-      case 'file':
-        $js_element = $element;
-        if (!$item['preprocess'] || !$preprocess_js) {
+        foreach ($group['items'] as $item) {
+          $js_element = $element;
           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));
+          $js_element['#value_prefix'] = $embed_prefix;
+          $js_element['#value'] = $item['data'];
+          $js_element['#value_suffix'] = $embed_suffix;
+          $new_elements[] = $js_element;
+        }
+        break;
+      case 'file':
+        // The group has been aggregated into a single file: output a LINK tag
+        // for the aggregate file.
+        if (isset($group['data'])) {
+          $js_element = $element;
+          $query_string_separator = (strpos($group['data'], '?') !== FALSE) ? '&' : '?';
+          $js_element['#attributes']['src'] = file_create_url($group['data']) . $query_string_separator . ($group['cache'] ? $query_string : REQUEST_TIME);
+          $js_element['#browsers'] = $group['browsers'];
+          $new_elements[] = $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;
+          foreach($group['items'] as $item) {
+            $js_element = $element;
+            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);
+            $js_element['#browsers'] = $item['browsers'];
+            $new_elements[] = $js_element;
+          }
         }
         break;
-
       case 'external':
-        $js_element = $element;
-        // Preprocessing for external JavaScript files is ignored.
-        if ($item['defer']) {
-          $js_element['#attributes']['defer'] = 'defer';
+        foreach ($group['items'] as $item) {
+          $js_element = $element;
+          // Preprocessing for external JavaScript files is ignored.
+          if ($item['defer']) {
+            $js_element['#attributes']['defer'] = 'defer';
+          }
+          $js_element['#attributes']['src'] = $item['data'];
+          $new_elements[] = $js_element;
         }
-        $js_element['#attributes']['src'] = $item['data'];
-        $processed[$index++] = theme('html_tag', array('element' => $js_element));
         break;
     }
   }
 
-  // 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 $new_elements;
+}
+
+/**
+ * Default callback to group JS items.
+ *
+ * This function arranges the JS items that are in the #items property of the
+ * script element into groups. Arranging the JS items into groups serves two
+ * purposes. 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' type
+ * are never groupable, items of the 'settings' type are never groupable and
+ * items of the 'external' type are never 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 $javascripts
+ *   An array of JS items, as returned by drupal_add_js(), but after
+ *   alteration performed by drupal_get_js().
+ *
+ * @return
+ *   An array of JS groups. Each group contains the same keys (e.g., 
+ *   'data', etc.) as a JS item from the $javascripts 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 $javascripts that are in the group.
+ *
+ * @see drupal_pre_render_scripts()
+ */
+function drupal_group_js($javascripts) {
+  $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 ($javascripts as $item) {
+    ksort($item['browsers']);
+
+    switch ($item['type']) {
+      case 'file':
+        // 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':
+      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;
   }
 
-  // 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;
+  return $groups;
+}
+
+/**
+ * Default callback to aggregate JS files and inline content.
+ *
+ * Having the browser load fewer JS 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. Additionally, this functions aggregates inline
+ * content together, regardless of the site-wide aggregation setting.
+ *
+ * @param $js_groups
+ *   An array of JS groups as returned by drupal_group_js(). This function
+ *   modifies the group's 'data' property for each group that is aggregated.
+ *
+ * @see drupal_group_js()
+ * @see drupal_pre_render_scripts()
+ */
+function drupal_aggregate_js(&$js_groups) {
+  $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  // For each group that needs aggregation, aggregate its items.
+  foreach ($js_groups as $key => $group) {
+    switch ($group['type']) {
+      // If a file group can be aggregated into a single file, do so, and set
+      // the group's data property to the file path of the aggregate file.
+      case 'file':
+        if ($group['preprocess'] & $preprocess_js) {
+          // TODO: The key needs to be the data attribute of the item, in order
+          // to aggragate them properly. This should be properly set up in drupal_group_js
+          // as we don't want to loop the files yet again here.
+          foreach ($group['items'] as $js_keys => $file) {
+            unset($group['items'][$js_keys]);
+            $group['items'][$file['data']] = $file;
+          }
+          $js_groups[$key]['data'] = drupal_build_js_cache($group['items']);
+        }
+        break;
+    }
+  }
 }
 
 /**
diff --git a/modules/system/system.module b/modules/system/system.module
index 7d423ab..e9a025c 100644
--- a/modules/system/system.module
+++ b/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(
