diff --git a/includes/common.inc b/includes/common.inc
index 20cc82b..32e8a4b 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -4140,8 +4140,13 @@ function drupal_region_class($region) {
  *       else being the same, JavaScript added by a call to drupal_add_js() that
  *       happened later in the page request gets added to the page after one for
  *       which drupal_add_js() happened earlier in the page request.
- *   - defer: If set to TRUE, the defer attribute is set on the <script>
+ *   - defer: If set to TRUE, the defer attribute is set on the SCRIPT
  *     tag. Defaults to FALSE.
+ *   - async: If set to TRUE, the async attribute is set on the SCRIPT
+ *     tag. Defaults to FALSE.
+ *   - attributes: An associative array of attributes for the SCRIPT tag.
+ *     Note that setting any attributes will disable preprocessing as though
+ *     the 'preprocess' option was set to FALSE.
  *   - cache: If set to FALSE, the JavaScript file is loaded anew on every page
  *     call; in other words, it is not cached. Used only when 'type' references
  *     a JavaScript file. Defaults to TRUE.
@@ -4206,6 +4211,8 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'preprocess' => TRUE,
           'cache' => TRUE,
           'defer' => FALSE,
+          'async' => FALSE,
+          'attributes' => array(),
         ),
       );
       // Register all required libraries.
@@ -4251,9 +4258,11 @@ function drupal_js_defaults($data = NULL) {
     'scope' => 'header',
     'cache' => TRUE,
     'defer' => FALSE,
+    'async' => FALSE,
     'preprocess' => TRUE,
     'version' => NULL,
     'data' => $data,
+    'attributes' => array(),
   );
 }
 
@@ -4366,10 +4375,20 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   );
   foreach ($items as $item) {
     $query_string =  empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
+    $js_element = $element;
+    if (!empty($item['defer'])) {
+      $js_element['#attributes']['defer'] = 'defer';
+    }
+    if (!empty($item['async'])) {
+      $js_element['#attributes']['async'] = 'async';
+    }
+    if (!empty($item['attributes'])) {
+      $js_element['#attributes'] += $item['attributes'];
+      $item['preprocess'] = FALSE;
+    }
 
     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;
@@ -4377,10 +4396,6 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         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;
@@ -4388,11 +4403,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         break;
 
       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));
@@ -4406,17 +4417,19 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
           // 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;
+          if (!empty($item['defer'])) {
+            $key = 'defer_' . $key;
+          }
+          if (!empty($item['async'])) {
+            $key = 'async_' . $key;
+          }
           $processed[$key] = '';
           $files[$key][$item['data']] = $item;
         }
         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));
         break;
@@ -4433,6 +4446,13 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         $preprocess_file = file_create_url($uri);
         $js_element = $element;
         $js_element['#attributes']['src'] = $preprocess_file;
+        $item = reset($file_set);
+        if (!empty($item['defer'])) {
+          $js_element['#attributes']['defer'] = 'defer';
+        }
+        if (!empty($item['async'])) {
+          $js_element['#attributes']['async'] = 'async';
+        }
         $processed[$key] = theme('html_tag', array('element' => $js_element));
       }
     }
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index eebfdbe..60622f4 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -1541,6 +1541,57 @@ class JavaScriptTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Tests adding JavaScript files with additional attributes.
+   */
+  function testAttributes() {
+    $js = 'misc/collapse.js';
+    drupal_add_js($js, array('async' => 'async', 'defer' => 'defer'));
+    $javascript = drupal_get_js();
+    preg_match('#<script.+src="' . preg_quote(file_create_url($js)) . '[^>]*>#', $javascript, $match);
+    $match = !empty($match[0]) ? $match[0] : '';
+    $this->assertTrue(strpos($match, 'defer="defer"') && strpos($match, 'async="async"'), 'Rendered internal JavaScript with correct defer attribute.');
+
+    $js = 'http://example.com/script.js';
+    drupal_add_js($js, array('async' => 'async', 'defer' => 'defer'));
+    $javascript = drupal_get_js();
+    preg_match('#<script.+src="' . preg_quote($js) . '[^>]*>#', $javascript, $match);
+    $match = !empty($match[0]) ? $match[0] : '';
+    $this->assertTrue(strpos($match, 'defer="defer"') && strpos($match, 'async="async"'), 'Rendered external JavaScript with correct defer attribute.');
+  }
+
+  /**
+   * Tests that attributes are maintained when JS aggregation is enabled.
+   */
+  function testAggregatedAttributes() {
+    // Enable aggregation.
+    variable_set('preprocess_js', TRUE);
+
+    // Async & Defer allowed to aggregate
+    $js = 'misc/collapse.js';
+    drupal_add_js($js, array('async' => 'async', 'defer' => 'defer'));
+    $javascript = drupal_get_js();
+    preg_match('#<script.+src="js_[^"]+"[^>]*>#', $javascript, $match);
+    $match = !empty($match[0]) ? $match[0] : '';
+    $this->assertTrue(strpos($match, 'defer="defer"') && strpos($match, 'async="async"'), 'Rendered aggregated internal JavaScript with correct defer attribute.');
+
+    // Attributes break out of aggregation for safety / extensibility
+    $js = 'misc/collapse.js';
+    drupal_add_js($js, array('attributes' => array('async' => 'async', 'defer' => 'defer')));
+    $javascript = drupal_get_js();
+    preg_match('#<script.+src="' . preg_quote(file_create_url($js)) . '[^>]*>#', $javascript, $match);
+    $match = !empty($match[0]) ? $match[0] : '';
+    $this->assertTrue(strpos($match, 'defer="defer"') && strpos($match, 'async="async"'), 'Rendered non-aggregated internal JavaScript with correct defer attribute.');
+
+    // External does not aggregate
+    $js = 'http://example.com/script.js';
+    drupal_add_js($js, array('async' => 'async', 'defer' => 'defer', 'type' => 'external'));
+    $javascript = drupal_get_js();
+    preg_match('#<script.+src="' . preg_quote($js) . '[^>]*>#', $javascript, $match);
+    $match = !empty($match[0]) ? $match[0] : '';
+    $this->assertTrue(strpos($match, 'defer="defer"') && strpos($match, 'async="async"'), 'Rendered external JavaScript with correct defer attribute.');
+  }
+
+  /**
    * Test altering a JavaScript's weight via hook_js_alter().
    *
    * @see simpletest_js_alter()
