diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4c8695324e2f969223d71add68b5fc8389beb0f4..383dd29a37ccfb2f2030214371e92395fc9b9271 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3759,15 +3759,15 @@ 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>
- *     tag. Defaults to FALSE.
- *   - async: If set to TRUE, the async attribute is set on the <script>
- *     tag. Defaults 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.
  *   - preprocess: If TRUE and JavaScript aggregation is enabled, the script
  *     file will be aggregated. Defaults to TRUE.
+ *   - attributes: An associative array of attributes for the <script> tag. This
+ *     may be used to add 'defer', 'async', or custom attributes. Note that
+ *     setting any attributes will disable preprocessing as though the
+ *     'preprocess' option was set to FALSE.
  *   - browsers: An array containing information specifying which browsers
  *     should load the JavaScript item. See
  *     drupal_pre_render_conditional_comments() for details.
@@ -3793,8 +3793,8 @@ function drupal_add_js($data = NULL, $options = NULL) {
   }
   $options += drupal_js_defaults($data);
 
-  // Preprocess can only be set if caching is enabled.
-  $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE;
+  // Preprocess can only be set if caching is enabled and no attributes are set.
+  $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
 
   // Tweak the weight so that files of the same weight are included in the
   // order of the calls to drupal_add_js().
@@ -3861,9 +3861,8 @@ function drupal_js_defaults($data = NULL) {
     'weight' => 0,
     'scope' => 'header',
     'cache' => TRUE,
-    'defer' => FALSE,
-    'async' => FALSE,
     'preprocess' => TRUE,
+    'attributes' => array(),
     'version' => NULL,
     'data' => $data,
     'browsers' => array(),
@@ -4090,16 +4089,9 @@ function drupal_pre_render_scripts($elements) {
             break;
         }
 
-        // The defer and async attributes must not be specified if the src
-        // attribute is not present.
-        if (!empty($element['#attributes']['src'])) {
-          // Both may be specified for legacy browser fallback purposes.
-          if (!empty($item['async'])) {
-            $element['#attributes']['async'] = 'async';
-          }
-          if (!empty($item['defer'])) {
-            $element['#attributes']['defer'] = 'defer';
-          }
+        // Attributes may only be set if this script is output independently.
+        if (!empty($element['#attributes']['src']) && !empty($item['attributes'])) {
+          $element['#attributes'] += $item['attributes'];
         }
 
         $elements[] = $element;
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
index 4c4198cd98831b37bd09683ad28b41bc6b745d19..f52907f7ab939c6f5de6d6c58c69015ebde84930 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
@@ -97,39 +97,42 @@ function testAddExternal() {
   }
 
   /**
-   * Tests adding external JavaScript Files with the async attribute.
+   * Tests adding JavaScript files with additional attributes.
    */
-  function testAsyncAttribute() {
+  function testAttributes() {
     $default_query_string = variable_get('css_js_query_string', '0');
 
     drupal_add_library('system', 'drupal');
-    drupal_add_js('http://example.com/script.js', array('async' => TRUE));
-    drupal_add_js('core/misc/collapse.js', array('async' => TRUE));
+    drupal_add_js('http://example.com/script.js', array('attributes' => array('defer' => 'defer')));
+    drupal_add_js('core/misc/collapse.js', array('attributes' => array('defer' => 'defer')));
     $javascript = drupal_get_js();
 
-    $expected_1 = '<script src="http://example.com/script.js?' . $default_query_string . '" async="async"></script>';
-    $expected_2 = '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" async="async"></script>';
+    $expected_1 = '<script src="http://example.com/script.js?' . $default_query_string . '" defer="defer"></script>';
+    $expected_2 = '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" defer="defer"></script>';
 
-    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct async attribute.');
-    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct async attribute.');
+    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute.');
+    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute.');
   }
 
   /**
-   * Tests adding external JavaScript Files with the defer attribute.
+   * Tests that attributes are maintained when JS aggregation is enabled.
    */
-  function testDeferAttribute() {
+  function testAggregatedAttributes() {
+    // Enable aggregation.
+    config('system.performance')->set('preprocess.js', 1)->save();
+
     $default_query_string = variable_get('css_js_query_string', '0');
 
     drupal_add_library('system', 'drupal');
-    drupal_add_js('http://example.com/script.js', array('defer' => TRUE));
-    drupal_add_js('core/misc/collapse.js', array('defer' => TRUE));
+    drupal_add_js('http://example.com/script.js', array('attributes' => array('defer' => 'defer')));
+    drupal_add_js('core/misc/collapse.js', array('attributes' => array('defer' => 'defer')));
     $javascript = drupal_get_js();
 
     $expected_1 = '<script src="http://example.com/script.js?' . $default_query_string . '" defer="defer"></script>';
     $expected_2 = '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" defer="defer"></script>';
 
-    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute.');
-    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute.');
+    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute with aggregation enabled.');
+    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute with aggregation enabled.');
   }
 
   /**