diff --git a/includes/common.inc b/includes/common.inc
index d7189ab..2924b3b 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3976,6 +3976,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 +3988,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'preprocess' => TRUE,
           'cache' => TRUE,
           'defer' => FALSE,
+          'browsers' => array(),
         ),
       );
       // Register all required libraries.
@@ -4034,6 +4036,7 @@ function drupal_js_defaults($data = NULL) {
     'preprocess' => TRUE,
     'version' => NULL,
     'data' => $data,
+    'browsers' => array(),
   );
 }
 
@@ -4089,12 +4092,51 @@ 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);
+}
+
+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
@@ -4104,121 +4146,157 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   // get REQUEST_TIME as query-string instead, to enforce reload on every
   // 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':
+          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;            
+          }
+          break;
+    }
+  }
+
+  return $new_elements;
+}
 
+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_css() 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 items.
+        $group_keys = FALSE;
         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));
-      }
+    // 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;
   }
+  
+  return $groups;
+}
 
-  // 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;
+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) {
+          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(
